目錄
- 屬性演變過程
- 屬性定義
- 屬性擴(kuò)展
屬性演變過程
上篇博文《OC學(xué)習(xí)備忘錄:成員變量、方法》中演示了如何聲明成員變量即硼,并在方法中使用鹿响。
這里繼續(xù)以WZKPerson類為例郑现,上篇博文中我們聲明了一個(gè)lisi的對象,并初始化了該對象的name和age兩個(gè)成員變量未荒。
WZKPerson *lisi=[[WZKPerson alloc]initWithName:@"李四" age:30];
[lisi sayMyInfo];
假如我們想給lisi進(jìn)行改名字专挪,該怎么辦呢?
由于成員變量在類的內(nèi)部茄猫,無法直接進(jìn)行修改狈蚤,又該怎么辦呢?
一種簡單的辦法是划纽,可以利用對象方法進(jìn)行修改成員變量脆侮。
首先需要在WZKPerson.h文件中添加如下方法:
-(void)setName:(NSString *)name;
-(void)setAge:(NSInteger)age;
對應(yīng)的需要在.m文件中實(shí)現(xiàn)這兩個(gè)方法:
-(void)setName:(NSString *)name
{
_name=name;
}
-(void)setAge:(NSInteger)age
{
_age=age;
}
最后就可以在調(diào)用類中使用這兩個(gè)方法了。
//設(shè)置lisi的name為李斯
[lisi setName:@"李斯"];
//設(shè)置lisi的age為90
[lisi setAge:90];
//輸出設(shè)置name和age信息
[lisi sayMyInfo];
運(yùn)行效果如下:
第二個(gè)運(yùn)行結(jié)果是沒有改變lisi的name和age之前的結(jié)果勇劣,第三個(gè)結(jié)果是改變之后的運(yùn)行結(jié)果靖避。
通過方法修改成員變量的值還是比較簡單的,那么問題又來了比默,現(xiàn)在我們想獲取lisi的name和age值幻捏,又該怎么辦呢?同樣的方式命咐,可以寫一個(gè)新的方法進(jìn)行獲取篡九。
在WZKPerson.h文件中添加如下方法:
-(NSString *)name;
-(NSInteger)age;
對應(yīng)的需要在.m文件中實(shí)現(xiàn)這兩個(gè)方法:
-(NSString *)name
{
return _name;
}
-(NSInteger)age
{
return _age;
}
最后就可以在調(diào)用類中使用這兩個(gè)方法了。
NSString *lisiName=[lisi name];
NSInteger lisiAge=[lisi age];
NSLog(@"lisiName= %@ , lisiAge= %li",lisiName,lisiAge);
運(yùn)行效果如下:
這種通過定義方法的方式訪問類的成員變量醋奠,其實(shí)就是利用到了面向?qū)ο笾蟹庋b特性
到現(xiàn)在為止榛臼,通過定義方法的方法的方式設(shè)置和獲取成員變量的值看起來沒有什么問題伊佃,但是會引出一個(gè)問題。
假如又定義了一個(gè)NSMutableString類型的變量personName沛善,將personName值賦值給wangwu的name變量航揉。
NSMutableString *personName=[NSMutableString stringWithString:@"王五"];
[wangwu setName:personName];
NSLog(@"personName= %@,wangwu's name=%@",personName,[wangwu name]);
輸出結(jié)果肯定是:“personName= 王五,wangwu's name=王五”。
現(xiàn)在我希望personName值為“王五123”金刁,wangwu 的name值還是為“王五”帅涂。可能我們會用下面代碼來改變personName的值尤蛮。
[personName appendString:@"123"];
添加完這行代碼后媳友,重新運(yùn)行一下,發(fā)現(xiàn)輸出的結(jié)果為:“personName= 王五123,wangwu's name=王五123”抵屿,和我們預(yù)期的結(jié)果不一樣庆锦,這是什么原因造成呢?
還記得在WZKPerson.m文件中的這兩段代碼嗎轧葛?
-(id)initWithName:(NSString *)name age:(NSInteger)age
{
self=[super init];
if (self) {
_name=name;
_age=age;
}
return self;
}
-(void)setName:(NSString *)name
{
_name=name;
}
造成的原因是因?yàn)開name=name導(dǎo)致的搂抒,這又是為什么呢?
這里先來看一下personName和[wangwu name]兩個(gè)值的內(nèi)存地址尿扯。
NSLog(@"%p",personName);
NSLog(@"%p",[wangwu name]);
運(yùn)行后求晶,發(fā)現(xiàn)personName內(nèi)存地址為0x100300aa0,另外[wangwu name]也是0x100300aa0衷笋,這兩個(gè)內(nèi)存地址一樣芳杏,說明通過_name=name賦值方式,[wangwu name]的內(nèi)存地址指向了personName的內(nèi)存地址辟宗,這種方式叫做淺拷貝爵赵。
終于找到原因了,那有沒有解決辦法來實(shí)現(xiàn)我們的預(yù)期結(jié)果呢泊脐?
當(dāng)前可以了空幻,既然有淺拷貝,那必然會有一個(gè)叫深拷貝(copy)的東東容客。那么怎么使用copy呢秕铛?很簡單,直接看代碼缩挑。
-(id)initWithName:(NSString *)name age:(NSInteger)age
{
self=[super init];
if (self) {
_name=[name copy];
_age=age;
}
return self;
}
-(void)setName:(NSString *)name
{
_name=[name copy];
}
只需要將原來的_name=name代碼改成_name=[name copy]就可以了但两。
重新運(yùn)行一下,發(fā)現(xiàn)輸出的結(jié)果變?yōu)椋骸皃ersonName= 王五123,wangwu's name=王五”供置,達(dá)到了預(yù)期結(jié)果谨湘。
再來看一下內(nèi)存地址,驗(yàn)證一下是不是指向兩個(gè)不同的內(nèi)存地址。
發(fā)現(xiàn)personName內(nèi)存地址變成了0x1003004d0悲关,而[wangwu name]內(nèi)存地址變成了0x100300a90谎僻。
內(nèi)存地址也不一樣,完全達(dá)到了預(yù)期結(jié)果寓辱。
age成員變量由于是NSInteger類型,不會涉及深拷貝和淺拷貝
但是問題又來了赤拒,我們發(fā)現(xiàn)秫筏,僅僅兩個(gè)簡單成員變量的設(shè)置和獲取操作,就需要寫十幾行的代碼挎挖,如果一個(gè)類里面有十幾個(gè)變量这敬,那么僅僅這一塊代碼就需要上百行,嚴(yán)重影響了開發(fā)效率蕉朵,并且錯(cuò)誤率也會大大的提升崔涂。
屬性定義
蘋果公司在Objective-C 2.0中引入了屬性(property),它組合了新的預(yù)編譯指令和新的屬性訪問器語法始衅。新的屬性功能顯著的減少了冗余代碼的數(shù)量冷蚂。
怎么在上述代碼中添加WZKPerson的name和age的屬性的呢?
首先我們先將WZKPerson類.h和.m文件中:
-setName:汛闸、-setAge:蝙茶、-name:和-age:方法的聲明和實(shí)現(xiàn)注釋掉。
在WZKPerson.h文件添加如下代碼诸老。
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger age;
最后運(yùn)行一下程序隆夯,發(fā)現(xiàn)輸出的結(jié)果和之前是一樣的。
另外也可以通過點(diǎn)方式設(shè)置或者獲取屬性值别伏。
wangwu.name=@"12345";//等價(jià)于[wangwu setName:@"12345"];
wangwu.age=20;//等價(jià)于[wangwu setAge:20];
[wangwu sayMyInfo];
到此為止蹄衷,我們就成功的添加了WZKPerson類的name和age的屬性,是不是很簡單厘肮。
下面稍微解釋屬性聲明的代碼:
- OC中可以通過@property聲明屬性愧口;
- 通過@synthesize自動生成getter、setter方法(目前可以省略@synthesize聲明)轴脐;
屬性聲明后调卑,甚至也可以直接省略掉成員變量的聲明
下面總結(jié)一下屬性的生成規(guī)則:
- 如果只聲明了一個(gè)屬性a,不使用@synthesize實(shí)現(xiàn)大咱,編譯器會使用_a作為屬性的成員變量恬涧。
如果沒有定義成員變量_a,則會自動生成一個(gè)私有的成員變量_a碴巾;
如果已經(jīng)定義了成員變量_a則使用自定義的成員變量_a溯捆;
注意,如果此時(shí)定義的成員變量不是_a而是a,則此時(shí)會自動生成一個(gè)成員變量_a提揍,它和自定義成員變量a沒有任何關(guān)系啤月; - 如果聲明了一個(gè)屬性a,使用@synthesize a進(jìn)行實(shí)現(xiàn)劳跃,但是實(shí)現(xiàn)過程中沒有指定使用的成員變量谎仲,則此時(shí)編譯器會使用a作為屬性的成員變量。
如果定義了成員變量a刨仑,則使用自定義成員變量郑诺;
如果此時(shí)沒有定義,則自動生成一個(gè)私有的成員變量a杉武;
注意辙诞,如果此時(shí)定義的是_a,則它跟生成的a成員變量沒有任何關(guān)系轻抱; - 如果聲明了一個(gè)屬性a飞涂,使用@synthesize a=_a進(jìn)行實(shí)現(xiàn),這個(gè)過程已經(jīng)指定了使用的成員變量祈搜,此時(shí)會使用指定的成員變量作為屬性變量较店;
屬性擴(kuò)展
可能大家會發(fā)現(xiàn),在上面聲明屬性代碼中有一個(gè)()號夭问,并且其中有一些參數(shù)泽西。
@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger age;
()號中的參數(shù),我們叫做屬性限定詞缰趋。
這些限定詞包括:nonatomic捧杉、atomic、copy秘血、retain味抖、strong、weak灰粮、assign仔涩、 readonly、readwrite粘舟;
下面簡單的介紹這些限定詞的含義:
限定詞 | 說明 |
---|---|
atomic | 默認(rèn)值熔脂,表示原子,同一時(shí)間可以由多個(gè)線程訪問屬性 |
nonatomic | 表示非原子柑肴,同一時(shí)間只能有一個(gè)線程進(jìn)行訪問 |
readonly | 只讀屬性霞揉,只生成getter方法 |
readwrite | 默認(rèn)值,生成getter晰骑、setter方法 |
assign | 默認(rèn)值适秩,直接賦值,通常修飾基本數(shù)據(jù)類型,也可以修飾對象數(shù)據(jù)類型秽荞,但此時(shí)會指向同一個(gè)內(nèi)存地址 |
retain | 先release原來的值骤公,再retain新值,會導(dǎo)致引用計(jì)數(shù)+1 |
copy | 先release原來的值扬跋,再copy新值(深拷貝) |
strong | iOS4.0之后出來的關(guān)鍵字阶捆,會導(dǎo)致引用計(jì)數(shù)+1,作用等價(jià)于retain |
weak | iOS4.0之后出來的關(guān)鍵字钦听,用于對象數(shù)據(jù)類型心赶,作用等價(jià)于assign |
@property的參數(shù)最多可以有三個(gè)揭蜒,中間用逗號分割。如果不進(jìn)行設(shè)置沉删,程序會使用默認(rèn)參數(shù):(atomic,readwrite,assign)
一般情況下娱挨,如果在多線程開發(fā)中一個(gè)屬性可能會被多個(gè)線程同時(shí)訪問余指,可以考慮用atomic,否則建議使用nonatomic跷坝,效率更高酵镜;
小技巧:通常情況下,字符串對象使用copy柴钻,非字符串對象使用retain淮韭,基本數(shù)據(jù)對象使用assign;
另外使用copy功能需要一些前提贴届,需要遵守NSCopying協(xié)議靠粪,實(shí)現(xiàn)copyWithZone:方法;