1. 程式人生 > >iOS開發中ARC的那點事

iOS開發中ARC的那點事

       

在MRC時代,Block會隱式地對進入其作用域內的物件(或者說被Block捕獲的指標指向的物件)加retain,來確保Block使用到該物件時,能夠正確的訪問。

這件事情在下面程式碼展示的情況中要更加額外小心。

MyViewController *myController = [[MyViewController alloc] init…];

// 隱式地呼叫[myController retain];造成迴圈引用
myController.completionHandler =  ^(NSInteger result) {
   [myController dismissViewControllerAnimated:YES completion:nil];
};

[self presentViewController:myController animated:YES completion:^{
   [myController release]; // 注意,這裡呼叫[myController release];是在MRC中的一個常規寫法,並不能解決上面迴圈引用的問題
}];

在這段程式碼中,myController的completionHandler呼叫了myController的方法[dismissViewController...],這時completionHandler會對myController做retain操作。而我們知道,myController對completionHandler也至少有一個retain(一般準確講是copy),這時就出現了在記憶體管理中最糟糕的情況:迴圈引用!簡單點說就是:myController retain了completionHandler,而completionHandler也retain了myController。迴圈引用導致了myController和completionHandler最終都不能被釋放。我們在delegate關係中,對delegate指標用weak就是為了避免這種問題。

不過好在,編譯器會及時地給我們一個警告,提醒我們可能會發生這型別的問題:


對這種情況,我們一般用如下方法解決:給要進入Block的指標加一個__block修飾符。

這個__block在MRC時代有兩個作用:

  • 說明變數可改
  • 說明指標指向的物件不做這個隱式的retain操作

一個變數如果不加__block,是不能在Block裡面修改的,不過這裡有一個例外:static的變數和全域性變數不需要加__block就可以在Block中修改。

使用這種方法,我們對程式碼做出修改,解決了迴圈引用的問題:

MyViewController * __block myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];
};
//之後正常的release或者retain

在ARC引入後,沒有了retain和release等操作,情況也發生了改變:在任何情況下,__block修飾符的作用只有上面的第一條:說明變數可改。即使加上了__block修飾符,一個被block捕獲的強引用也依然是一個強引用。這樣在ARC下,如果我們還按照MRC下的寫法,completionHandler對myController有一個強引用,而myController對completionHandler有一個強引用,這依然是迴圈引用,沒有解決問題:(

於是我們還需要對原始碼做修改。簡單的情況我們可以這樣寫:

__block MyViewController * myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];
    myController = nil;  // 注意這裡,保證了block結束myController強引用的解除
};

在completionHandler之後將myController指標置nil,保證了completionHandler對myController強引用的解除,不過也同時解除了myController對myController物件的強引用。這種方法過於簡單粗暴了,在大多數情況下,我們有更好的方法。

這個更好的方法就是使用weak。(或者為了考慮iOS4的相容性用unsafe_unretained,具體用法和weak相同,考慮到現在iOS4裝置可能已經絕跡了,這裡就不講這個方法了)(關於這個方法的本質我們後面會談到)

為了保證completionHandler這個Block對myController沒有強引用,我們可以定義一個臨時的弱引用weakMyViewController來指向原myController的物件,並把這個弱引用傳入到Block內,這樣就保證了Block對myController持有的是一個弱引用,而不是一個強引用。如此,我們繼續修改程式碼:

MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyViewController = myController;
myController.completionHandler =  ^(NSInteger result) {
    [weakMyViewController dismissViewControllerAnimated:YES completion:nil];
};

這樣迴圈引用的問題就解決了,但是卻不幸地引入了一個新的問題:由於傳入completionHandler的是一個弱引用,那麼當myController指向的物件在completionHandler被呼叫前釋放,那麼completionHandler就不能正常的運作了。在一般的單執行緒環境中,這種問題出現的可能性不大,但是到了多執行緒環境,就很不好說了,所以我們需要繼續完善這個方法。

為了保證在Block內能夠訪問到正確的myController,我們在block內新定義一個強引用strongMyController來指向weakMyController指向的物件,這樣多了一個強引用,就能保證這個myController物件不會在completionHandler被呼叫前釋放掉了。於是,我們對程式碼再次做出修改:

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...
    }
};

到此,一個完善的解決方案就完成了,好了,就到這裡吧,後序大家一起來研究學習。。。