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...
}
};
到此,一個完善的解決方案就完成了,好了,就到這裡吧,後序大家一起來研究學習。。。