內(nèi)存管理(ARC)

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ì)象類型)。

引用文章鏈接:iOS開(kāi)發(fā)ARC內(nèi)存管理技術(shù)要點(diǎn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容