前言
很多剛接觸iOS的朋友,對property的可選參數(shù)如何使用他匪,什么情況下使用哪種選項(xiàng)不了解,也問了我很多這方面的知識夸研,雖然知道怎么用邦蜜,但是有些說不出其區(qū)別。在這里亥至,再次深入學(xué)習(xí)一遍悼沈,對copy/strong/weak/__weak/__strong/assign
的使用場景總結(jié)總結(jié)贱迟。如果有說得不對的地方,請指出絮供。如果有疑問衣吠,請私聊我,或者直接回復(fù)我壤靶。
自動引用計(jì)數(shù)
原文檔關(guān)于自動引用說明:
Automatic Reference Counting (ARC) is a compiler feature that provides automatic memory management of Objective-C objects. Rather than having to think about retain and release operations, ARC allows you to concentrate on the interesting code, the object graphs, and the relationships between objects in your application.
翻譯過來就是:Automatic Reference Counting (ARC)
是一個編譯器的特性缚俏,提供了對iOS
對象的自動內(nèi)存管理,ARC
在編譯期間自動在適當(dāng)?shù)牡胤教砑?code>ObjC對象的retain
和release
操作代碼,而不需要我們關(guān)心贮乳。
ARC
在編譯期間忧换,根據(jù)Objective-C
對象的存活周期,在適當(dāng)?shù)奈恢锰砑?code>retain和release
代碼向拆。從概念上講包雀,ARC
與手動引用計(jì)數(shù)內(nèi)存管理遵循同樣的內(nèi)存管理規(guī)則,但是ARC也無法防止循環(huán)強(qiáng)引用亲铡。
ARC
還引入了新的修飾符來修飾變量和聲明屬性:
- 聲明變量的修飾符:__strong, __weak, __unsafe_unretained, __autoreleasing;
- 聲明屬性的修飾符:strong, weak, unsafe_unretained才写。
- 對象和Core Foundation-style對象直接的轉(zhuǎn)換修飾符號:__bridge,__bridge_retained或CFBridgingRetain, __bridge_transfer或CFBridgingRelease奖蔓。
- 對于線程的安全赞草,有nonatomic,這樣效率就更高了吆鹤,但是不是線程的厨疙。如果要線程安全,可以使用atomic疑务,這樣在訪問是就會有線程鎖沾凄。
- 記住內(nèi)存管理法則:誰使對象的引用計(jì)數(shù)+1,不再引用時知允,誰就負(fù)責(zé)將該對象的引用計(jì)數(shù)-1撒蟀。
實(shí)戰(zhàn)性學(xué)習(xí)
下面我們來聲明一個Person類來學(xué)習(xí):
@interface Person : NSObject
// 注意:蘋果有命名規(guī)范的,命名屬性時温鸽,不能以copy開頭保屯。
// 如果下面的屬性聲明為copyString,會編譯不通過涤垫。
@property (nonatomic, copy) NSString *copiedString;
// 默認(rèn)會是什么呢姑尺?
@property (nonatomic) NSString *name;
// 默認(rèn)是strong類型
@property (nonatomic) NSArray *array;
@end
如果屬性沒有指定類型,默認(rèn)是什么呢蝠猬?其實(shí)是strong
切蟋。如果證明呢?驗(yàn)證方法:分別將array
屬性的類型分別設(shè)置為weak
, assign
,strong
,不設(shè)置榆芦,這四種情況的結(jié)果分別是:第一種打印為空柄粹,第二種直接直接崩潰喘鸟,第三種和最后一種是可以正常使用。如下面的驗(yàn)證代碼:
Person *lili = [[Person alloc] init];
lili.name = @"LiLi";
lili.copiedString = @"LiLi\' father is LLL";
lili.array = @[@"謝謝", @"感謝"];
NSArray *otherArray = lili.array;
lili = nil;
NSLog(@"%@", otherArray);
再繼續(xù)添加下面的代碼镰惦。默認(rèn)聲明變量的類型為__strong
類型,因此上面的NSArray *otherArray = lili.array;
與__strong NSArray *otherArray = lili.array;
是一樣的犬绒。如果我們要使用弱引用旺入,特別是在解決循環(huán)強(qiáng)引用時就特別重要了。我們可以使用__weak聲明變量為弱引用凯力,這樣就不會增加引用計(jì)數(shù)值茵瘾。
__strong NSArray *strongArray = otherArray;
otherArray = nil;
// 打印出來正常的結(jié)果。
NSLog(@"strongArray = %@", strongArray);
__weak NSArray * weakArray = strongArray;
strongArray = nil;
// 打印出來:null
NSLog(@"weakArray: %@", weakArray);
xib/storybard連接的對象為什么可以使用weak
@property (nonatomic, weak) IBOutlet UIButton *button;
像上面這行代碼一樣咐鹤,在連接時自動生成為weak拗秘。因?yàn)檫@個button已經(jīng)放到view上了,因此只要這個View不被釋放祈惶,這個button的引用計(jì)數(shù)都不會為0雕旨,因此這里可以使用weak引用。
如果我們不使用xib/storyboard捧请,而是使用純代碼創(chuàng)建呢凡涩?
@property (nonatomic, weak) UIButton *button;
補(bǔ)充:有朋友反映這里說得不夠詳細(xì),感謝這位朋友的反饋疹蛉。
使用weak時活箕,由于button在創(chuàng)建時,沒有任何強(qiáng)引用可款,因此就有可能提前釋放育韩。Xcode編譯器會告訴我們,這里不能使用weak闺鲸。因此我們需要記住筋讨,只要我們在創(chuàng)建以后需要使用它,我們必須保證至少有一個強(qiáng)引用摸恍,否則引用計(jì)數(shù)為0版仔,就會被釋放掉。對于上面的代碼误墓,就是由于在創(chuàng)建時使用了weak引用蛮粮,因此button的引用計(jì)數(shù)仍然為0,也就是會被釋放谜慌,編譯器在編譯時會檢測出來的然想。
這樣寫,在創(chuàng)建時通過self.button = ...
就是出現(xiàn)錯誤欣范,因?yàn)檫@是弱引用变泄。所以我們需要聲明為強(qiáng)引用令哟,也就是這樣:
@property (nonatomic, strong) UIButton *button;
block聲明使用copy
在使用block時,盡量使用typedef來起一個別名妨蛹,這樣更容易閱讀屏富。使block作為屬性時,盡量使用copy蛙卤。蘋果官方有說明狠半,因?yàn)橛猩舷挛淖兞坎东@,應(yīng)該使用copy颤难。但是很多朋友說使用strong也一樣神年,沒有區(qū)別。目前行嗤,沒有辦法說明這使用strong與copy導(dǎo)致有沒有區(qū)別已日。不過,筆者一直都會使用copy來聲明Block栅屏,而不是strong:
typedef void (^TestBlock)(NSString *name);
@property (nonatomic, copy) TestBlock testBlock;
字符串使用copy
對于字符串飘千,通常都是使用copy的方式。雖然使用strong也沒有沒有問題栈雳,但是事實(shí)上在開發(fā)中都會使用copy占婉。為什么這么做?因?yàn)閷τ谧址Χ鳎覀兿M且淮蝺?nèi)容的拷貝逆济,外部修改也不會影響我們的原來的值,而且NSString類遵守了NSCopying, NSMutableCopying, NSSecureCoding協(xié)議磺箕。
下面時使用copy的方式奖慌,驗(yàn)證如下:
NSString *hahaString = @"哈哈";
NSString *heheString = [hahaString copy];
// 哈哈, 哈哈
NSLog(@"%@, %@", hahaString, heheString);
heheString = @"呵呵";
// 哈哈, 呵呵
NSLog(@"%@, %@", hahaString, heheString);
我們修改了heheString,并不會影響到原來的hahaString松靡。copy一個對象變成新的對象(新內(nèi)存地址) 引用計(jì)數(shù)為1 原來對象計(jì)數(shù)不變简僧。
屬性聲明修飾符
屬性聲明修飾符有:strong, weak, unsafe_unretained, readWrite,默認(rèn)strong, readWrite的雕欺。
- strong:strong和retain相似,只要有一個strong指針指向?qū)ο蟮郝恚搶ο缶筒粫讳N毀
- weak:聲明為weak的指針,weak指針指向的對象一旦被釋放屠列,weak的指針都將被賦值為nil啦逆;
- unsafe_unretained:用unsafe_unretained聲明的指針,指針指向的對象一旦被釋放笛洛,這些指針將成為野指針夏志。
@property (nonatomic, copy) NSString *name;
// 一旦所指向的對象被釋放,就會成為野指針
@property (nonatomic, unsafe_unretained) NSString *unsafeName;
lili.name = @"Lili";
lili.unsafeName = lili.name;
lili.name = nil;
// unsafeName就變成了野指針苛让。這里不會崩潰沟蔑,因?yàn)闉閚il.
NSLog(@"%@", lili.unsafeName);
深拷貝與淺拷貝
關(guān)于淺拷貝湿诊,簡單來說,就像是人與人的影子一樣瘦材。而深拷貝就像是夢幻西游中的龍宮有很多個長得一樣的龍宮厅须,但是他們都是不同的精靈,因此他們各自都是獨(dú)立的食棕。
我相信還有不少朋友有這樣一種誤解:淺拷貝就是用copy朗和,深拷貝就是用mutableCopy。如果有這樣的誤解宣蠕,一定要更正過來例隆。copy只是不可變拷貝甥捺,而mutableCopy是可變拷貝抢蚀。比如,
// 那么arr是不可變的
NSArray *arr = [modelsArray copy];
// 那么ma是可變的镰禾。
NSMutableArray *ma = [modelsArray mutableCopy];
舉個例子:
lili.array = [@[@"謝謝", @"感謝"] mutableCopy];
NSMutableArray *otherArray = [lili.array copy];
lili.array[0] = @"修改了謝謝";
// 打用笄: 謝謝 修改了謝謝
// 說明數(shù)組里面是字符串時,直接使用copy是相當(dāng)于深拷貝的吴侦。
NSLog(@"%@ %@", otherArray[0], lili.array[0]);
這里就是淺拷貝屋休,但是由于數(shù)組中的元素都是字符串,因此不會影響原來的值备韧。
數(shù)組中是對象時:
NSMutableArray *personArray = [[NSMutableArray alloc] init];
Person *person1 = [[Person alloc] init];
person1.name = @"lili";
[personArray addObject:person1];
Person *person2 = [[Person alloc] init];
person2.name = @"lisa";
[personArray addObject:person2];
// 淺拷貝
NSArray *newArray = [personArray copy];
Person *p = newArray[0];
p.name = @"lili的名字被修改了";
// 打印結(jié)果:lili的名字被修改了
// 說明這邊修改了劫樟,原來的數(shù)組對象的值也被修改了。雖然newArray和personArray不是同一個數(shù)組织堂,不是同一塊內(nèi)存叠艳,
// 但是實(shí)際上兩個數(shù)組的元素都是指向同一塊內(nèi)存。
NSLog(@"%@", ((Person *)(personArray[0])).name);
深拷貝易阳,其實(shí)就是對數(shù)組中的所有對象都創(chuàng)建一個新的對象:
NSMutableArray *personArray = [[NSMutableArray alloc] init];
Person *person1 = [[Person alloc] init];
person1.name = @"lili";
[personArray addObject:person1];
Person *person2 = [[Person alloc] init];
person2.name = @"lisa";
[personArray addObject:person2];
// 深拷貝
NSMutableArray *newArray = [[NSMutableArray alloc] init];
for (Person *p in personArray) {
Person *newPerson = [[Person alloc] init];
newPerson.name = p.name;
[newArray addObject:newPerson];
}
Person *p = newArray[0];
p.name = @"lili的名字被修改了";
// 打印結(jié)果:lili
NSLog(@"%@", ((Person *)(personArray[0])).name);
閱讀更多關(guān)于深拷貝與淺拷貝
Getter/Setter
在ARC下附较,getter/setter的寫法與MRC的不同了。我面試過一些朋友潦俺,筆試這關(guān)就寫得很糟(不包括算法)拒课。通常在筆試時都會讓重寫一個屬性的Getter/Setter方法。
@property (nonatomic, strong) NSMutableArray *array;
- (void)setArray:(NSMutableArray *)array {
if (_array != array) {
_array = nil;
_array = array;
}
}
如果是要重寫getter就去呢事示?就得增加一個變量了早像,如果同時重寫getter/setter方法,就不會自動生成_array變量肖爵,因此我們可以聲明一個變量為_array:
- (void)setArray:(NSMutableArray *)array {
if (_array != array) {
_array = nil;
_array = array;
}
}
- (NSMutableArray *)array {
return _array;
}
總結(jié)
關(guān)于屬性的這些選項(xiàng)的學(xué)習(xí)扎酷,做一下總結(jié):
- 所有的屬性,都盡可能使用nonatomic遏匆,以提高效率法挨,除非真的有必要考慮線程安全谁榜。
- NSString:通常都使用copy,以得到新的內(nèi)存分配凡纳,而不只是原來的引用窃植。
- strong:對于繼承于NSObject類型的對象,若要聲明為強(qiáng)使用荐糜,使用strong巷怜,若要使用弱引用,使用__weak來引用暴氏,用于解決循環(huán)強(qiáng)引用的問題延塑。
- weak:對于xib上的控件引用,可以使用weak答渔,也可以使用strong关带。
- __weak:對于變量的聲明,如果要使用弱引用沼撕,可以使用weak宋雏,如:weak typeof(Model) weakModel = model;就可以直接使用weakModel了。
- __strong:對于變量的聲明务豺,如果要使用強(qiáng)引用磨总,可以使用strong,默認(rèn)就是strong笼沥,因此不寫與寫__strong聲明都是一樣的蚪燕。
- __unsafe_unretained:在所引用的對象被釋放后,該指針就成了野指針奔浅,不好控制馆纳。對于C語言的指針,我們會使用這個來聲明乘凸。
- __autoreleasing:如果要在循環(huán)過程中就釋放厕诡,可以手動使用__autoreleasing來聲明將之放到自動釋放池。
參考資料
官方文檔關(guān)于自動引用計(jì)數(shù)內(nèi)存管理介紹:官方文檔說明