1.ARC的本質(zhì)
ARC是編譯器的特性,它并沒(méi)有改變OC引用計(jì)數(shù)式內(nèi)存管理的本質(zhì),更不是GC(垃圾回收),底層實(shí)現(xiàn)依然依賴引用計(jì)數(shù),只不過(guò)ARC模式下編譯器會(huì)自動(dòng)幫我們管理。
打開(kāi)ARC:-fobjc-arc
關(guān)閉ARC:-fno-objc-arc
2.引用計(jì)數(shù)管理原則
(1)自己生成的對(duì)象自己持有
(2)非自己生成的對(duì)象自己也可以持有
(3)自己持有的對(duì)象不需要時(shí)可以釋放
(4)非自己持有的對(duì)象自己不能釋放
3.四種變量所有權(quán)修飾符
__strong
__weak
__autoreleasing
__unsage_unretaied
以上是變量所有權(quán)修飾符
以下是屬性修飾符
copy 對(duì)應(yīng)的所有權(quán)類型是 __strong
拷貝,復(fù)制一個(gè)對(duì)象并創(chuàng)建strong關(guān)聯(lián),引用計(jì)數(shù)為1,原對(duì)象計(jì)數(shù)不變
retain 對(duì)應(yīng)的所有權(quán)類型是 __strong
持有(MRC),強(qiáng)引用,原對(duì)象引用計(jì)數(shù)加1
strong 對(duì)應(yīng)的所有權(quán)類型是 __strong
持有(ARC),強(qiáng)引用,原對(duì)象引用計(jì)數(shù)加1
weak 對(duì)應(yīng)的所有權(quán)類型是 __weak
賦值,弱引用,不改變引用計(jì)數(shù)。對(duì)象釋放后把指針置為nil,避免了也指針
assing 對(duì)應(yīng)的所有權(quán)類型是 __unsage_unretaied
賦值,弱引用,引用計(jì)數(shù)不變。ARC中對(duì)象不能使用assign,但基本類型(BOOL、int、float)仍可以使用
unsafe_unretained 對(duì)應(yīng)的所有權(quán)類型是 __unsage_unretaied
關(guān)于__strong
強(qiáng)引用,對(duì)于所有對(duì)象,只有當(dāng)沒(méi)有任何一個(gè)強(qiáng)引用指向它時(shí),它才能夠被釋放。如果聲明引用時(shí)不加修飾符,默認(rèn)是強(qiáng)引用。如果要釋放強(qiáng)引用指向的對(duì)象時(shí),要將強(qiáng)引用置為nil。
關(guān)于__weak
弱引用,弱引用不會(huì)影響對(duì)象的釋放,如果一個(gè)對(duì)象釋放了,那么指向它的所有弱引用全部置為nil,防止產(chǎn)生野指針。
最常見(jiàn)的用法是使用__weak來(lái)避免強(qiáng)循環(huán)引用。比如:
(1)設(shè)置delegate屬性為weak。
(2)block中防止強(qiáng)循環(huán)引用。
(3)一般由SB和xib脫線的控件
@property (weak, nonatomic) IBOutlet UIButton *testButton;。
關(guān)于__autoreleasing
表示在autorelease pool中自動(dòng)釋放對(duì)象,與MRC模式下相同。并沒(méi)有對(duì)應(yīng)的屬性修飾符,任何一個(gè)對(duì)象的屬性都不應(yīng)該是autorelease型的。但是autorelease是一直存在于ARC模式下的。
以下兩行代碼的意義是相同的。
NSString *str = [[[NSString alloc] initWithFormat:@"hehe"] autorelease]; // MRC
NSString *__autoreleasing str = [[NSString alloc] initWithFormat:@"hehe"]; // ARC
__autoreleasing常見(jiàn)的用法:
(1)參數(shù)傳遞返回值
示例:
- (NSObject *)object {
NSObject *o = [[NSObject alloc] init];
return o;
}
這里o默認(rèn)是__strong,由于return使得o超出了其作用于,本來(lái)應(yīng)該釋放的,但是因?yàn)樾枰獙⑺鳛榉祷刂?,所?strong>一般情況下要將它注冊(cè)倒Autorelease Pool中(ARC模式下可以通過(guò)運(yùn)行時(shí)優(yōu)化方案來(lái)跳過(guò)autorelease機(jī)制)。
autorelease機(jī)制:
接收方:調(diào)用方法的對(duì)象,使用o的話就需要強(qiáng)引用它,那么retaincount +1,使用后再rataincount -1。
提供方:提供對(duì)象作為返回值,創(chuàng)建了對(duì)象那么retaincount +1,使用后再rataincount -1。
但是你要保證對(duì)象在返回前沒(méi)有被釋放,否則返回的是nil,這個(gè)時(shí)候需要一個(gè)合理的機(jī)制來(lái)延長(zhǎng)對(duì)象的生命周期,又能保證釋放它,這個(gè)機(jī)制就是autorelease機(jī)制。
對(duì)象作為返回值時(shí),會(huì)被放入正在使用的Autorelease Pool中,Autorelease Pool會(huì)強(qiáng)引用這個(gè)對(duì)象,所以對(duì)象不會(huì)被釋放,延長(zhǎng)了生命周期,Autorelease Pool自己銷毀的時(shí)候會(huì)講里面的對(duì)象一并銷毀。
Autorelease Pool是與線程一一映射的,如果Autorelease Pool的drain方法沒(méi)有在接收方和提供方交接過(guò)程中觸發(fā),那么autorelease對(duì)象不會(huì)被釋放(除非嚴(yán)重的線程錯(cuò)亂)。
Autorelease Pool釋放的時(shí)機(jī)
- Run Loop會(huì)在每次loop結(jié)尾時(shí)銷毀它。
- GCD 的 dispatched blocks 會(huì)在一個(gè) Autorelease Pool 的上下文中執(zhí)行,這個(gè) Autorelease Pool 不時(shí)的就被銷毀了(依賴于實(shí)現(xiàn)細(xì)節(jié))。NSOperationQueue 也是類似。
- 其他線程則會(huì)各自對(duì)他們對(duì)應(yīng)的 Autorelease Pool 的生命周期負(fù)責(zé)。
ARC下跳過(guò)autorelease機(jī)制的優(yōu)化方法
為了兼容MRC,以及在MRC-to-ARC,ARC-to-MRC,ARC-to-ARC切換。
return的時(shí)候:ARC調(diào)用objc_autoreleaseReturnValue() 替代autorelease。
調(diào)用方持有對(duì)象的時(shí)候:ARC 會(huì)調(diào)用objc_retainAutoreleasedReturnValue()。
在調(diào)用 objc_autoreleaseReturnValue() 時(shí),ARC會(huì)在棧上查詢r(jià)eturn address來(lái)確定return value是否傳給了objc_retainAutoreleasedReturnValue()。如果沒(méi)傳,那么會(huì)走autorelease過(guò)程。如果傳了(返回值能從提供方傳給交接方),就跳過(guò)autorelease并同時(shí)修改retain address來(lái)跳過(guò)objc_retainAutoreleasedReturnValue(),從而一舉消除了autorelease和retain。
(2)訪問(wèn)__weak修飾的變量
訪問(wèn)__weak修飾的變量,實(shí)際上必定會(huì)訪問(wèn)到Autorelease Pool中注冊(cè)的對(duì)象。
id __weak obj1 = obj0;
NSLog(@"class=%@", [obj1 class]);
// 等同于:
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class=%@", [tmp class]);
因?yàn)閷?duì)象只是弱引用,為了保證訪問(wèn)對(duì)象的時(shí)候,對(duì)象沒(méi)有被廢棄,將對(duì)象注冊(cè)到Autorelease Pool中,這樣就能保證在Autorelease Pool被銷毀前對(duì)象時(shí)存在的。
(3)引用傳遞參數(shù)
NSError *__autoreleasing error;
if (![data writeToFile:filename options:NSDataWritingAtomic error:&error])
{
NSLog(@"Error: %@", error);
}
error參數(shù)的類型是(NSError *__autoreleasing *)。如果你講error定義為strong類型,那么編譯器會(huì)隱式的進(jìn)行轉(zhuǎn)換:
NSError *error;
NSError *__autoreleasing tempError = error; // 編譯器添加
if (![data writeToFile:filename options:NSDataWritingAtomic error:&tempError])
{
error = tempError; // 編譯器添加
NSLog(@"Error: %@", error);
}
所以為了提高效率,在定義error的時(shí)候?qū)⑵渎暶鳛開(kāi)autrorelease類型的。
在ARC中,這種指針的指針類型的函數(shù)參數(shù)(NSError **),如果不加修飾符,編譯器默認(rèn)為_(kāi)autrorelease類型。
(4)某些類的方法會(huì)隱式的使用自己的Autorelease Pool,這時(shí)使用_autorelease類型要小心。
- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
@autoreleasepool // 被隱式創(chuàng)建
{
if (there is some error && error != nil)
{
*error = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
}
}
}];
// *error 在這里已經(jīng)被dict的做枚舉遍歷時(shí)創(chuàng)建的autorelease pool釋放掉了 :(
}
為了能正常使用*error,需要一個(gè)臨時(shí)的強(qiáng)引用,在dict的block中使用它,保證引用的對(duì)象不會(huì)在出了block后被釋放:
- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
__block NSError* tempError; // 加__block保證可以在Block內(nèi)被修改
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
{
if (there is some error)
{
*tempError = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
}
}]
if (error != nil)
{
*error = tempError;
}
}
關(guān)于__unsafe _unretained
為了兼容低版本,相當(dāng)于MRC模式下的assign,僅僅是引用了對(duì)象,沒(méi)有其他任何操作,當(dāng)對(duì)象被釋放后依然指向?qū)ο螅ㄋ诘膬?nèi)存地址),會(huì)形成野指針,非常的不安全。
現(xiàn)版本可以忽略掉這個(gè)修飾符,因?yàn)槭茿RC的時(shí)代了。
*的正確使用方式:
NSString * __weak str = @"hehe";
聲明在棧中的指針默認(rèn)為nil,如:
- (void)myMethod
{
NSString *name;
NSLog(@"name: %@", name);
}
會(huì)輸出null而不是crash。
ARC與Block
MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler = ^(NSInteger result) {
MyViewController *strongMyController = weakMyController;
if (strongMyController) {
// ...
[strongMyController dismissViewControllerAnimated:YES completion:nil];
// ...
}
else {
// Probably nothing...
}
};
這里myController強(qiáng)引用了completionHandler
completionHandler調(diào)用了dismiss方法,也強(qiáng)引用了completionHandler
這樣的話就形成了循環(huán)引用。
ARC模式下__ block只代表能在block中修改這個(gè)變量,沒(méi)有其他作用。
要想避免循環(huán)引用,需要使用一個(gè)weakMyController對(duì)象弱引用myController,這樣block中對(duì)myController持有弱引用的話,就不會(huì)產(chǎn)生循環(huán)引用。
但是由于block對(duì)myController是持有弱引用,那么就有可能在block引用myController之前,myController已經(jīng)被釋放,block因此不能正常使用(單線程中問(wèn)題不大,一般出現(xiàn)在多線程中)。
所以我們?cè)赽lock中定義一個(gè)強(qiáng)引用strongMyController,用它來(lái)指向weakMyController指向的對(duì)象(我是這樣想的,引用其實(shí)是指針,這里是將weakMyController指向的地址賦值給了strongMyController,這樣strongMyController也指向了相同的地址,也就是同一個(gè)對(duì)象),這樣在block使用之前,myController就不會(huì)被提前釋放了。
block捕獲的變量和在block中定義的變量是有區(qū)別的,存在空間和生命周期都是不同的
并不是說(shuō)所有的block都存在循環(huán)引用,比如下面這個(gè):
TestObject *aObject = [[TestObject alloc] init];
aObject.name = @"hehe";
self.aBlock = ^(){
NSLog(@"aObject's name = %@",aObject.name);
};
堆和棧的區(qū)別
棧:系統(tǒng)分配,自動(dòng)管理,存放參數(shù),變量,指針等?;緮?shù)據(jù)類型存放在棧中,所以內(nèi)存管理不包括這些。
堆:程序員自己分配,自己釋放,存放對(duì)象類型,是內(nèi)存管理的主要內(nèi)容。
block是分配在棧上面的,為了不被系統(tǒng)回收,聲明時(shí)會(huì)使用copy屬性,copy到堆中。
結(jié)構(gòu)體也一樣,由于是分配到棧中,所以如果想要傳遞結(jié)構(gòu)體類型的參數(shù)的話,應(yīng)該將結(jié)構(gòu)體作為一個(gè)對(duì)象的屬性,然后將對(duì)象放到數(shù)組或字典中(只能存放對(duì)象類型)。