1. 程式人生 > >【block程式設計第五篇】block中使用 weak–strong dance 技術避免迴圈引用

【block程式設計第五篇】block中使用 weak–strong dance 技術避免迴圈引用

----------------------------------------歡迎檢視block連載部落格【點選】-----------------------------------------------
【block程式設計第一篇】block語法                 【block程式設計第二篇】block捕獲變數和物件;
【block程式設計第三篇】block的記憶體管理     【block程式設計第四篇】block內部實現;
【block程式設計第五篇】block中如何避免迴圈引用(當前)
----------------------------------------------------------------------------------------------------------------------------------


使用 weak–strong dance 技術

block 可以直接引用 self,但是要非常小心地在 block 中引用 self。因為在 block 引用 self,可能會導致迴圈引用。如下例所示:
@interface KSViewController ()
{
    id _observer;
}

@end

@implementation KSViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    KSTester * tester = [[KSTester alloc] init];
    [tester run];
    
    _observer = [[NSNotificationCenter defaultCenter]
                 addObserverForName:@"TestNotificationKey"
                 object:nil queue:nil usingBlock:^(NSNotification *n) {
                     NSLog(@"%@", self);
                 }];
}

- (void)dealloc
{
    if (_observer) {
        [[NSNotificationCenter defaultCenter] removeObserver:_observer];
    }
}

在上面程式碼中,我們新增向通知中心註冊了一個觀察者,然後在 dealloc 時解除該註冊,一切看起來正常。但這裡有兩個問題:
    1.) 在訊息通知 block 中引用到了 self,在這裡 self 物件被 block retain,而 _observer 又 retain 該 block的一份拷貝,通知中心又持有 _observer。因此只要 _observer 物件還沒有被解除註冊,block 就會一直被通知中心持有,從而 self 就不會被釋放,其 dealloc 就不會被呼叫。而我們卻又期望在 dealloc 中通過 removeObserver 來解除註冊以消除通知中心對 _observer/block 的 retain。
    2.) 同時,_observer 是在 self 所在類中定義賦值,因此是被 self retain 的,這樣就形成了迴圈引用。
上面的過程 1) 值得深入分析一下:
蘋果官方文件中對 addObserverForName:object:queue:usingBlock: 中的 block 變數說明如下:

The block is copied by the notification center and (the copy) held until the observer registration is removed.
因此,通知中心會拷貝 block 並持有該拷貝直到解除 _observer 的註冊。在 ARC 中,在被拷貝的 block 中無論是直接引用 self 還是通過引用 self 的成員變數間接引用 self,該 block 都會 retain self。
這兩個問題,可以用 weak–strong dance 技術來解決。該技術在 WWDC 中介紹過:2011 WWDC Session #322 (Objective-C Advancements in Depth)

__weak KSViewController * wself = self;
    _observer = [[NSNotificationCenter defaultCenter]
                 addObserverForName:@"TestNotificationKey"
                 object:nil queue:nil usingBlock:^(NSNotification *n) {
                     KSViewController * sself = wself;
                     if (sself) {
                         NSLog(@"%@", sself);
                     }
                     else {
                         NSLog(@"<self> dealloc before we could run this code.");
                     }
                 }];
下面來分析為什麼該手法能夠起作用。
    首先,在 block 之前定義對 self 的一個弱引用 wself,因為是弱引用,所以當 self 被釋放時 wself 會變為 nil;然後在 block 中引用該弱應用,考慮到多執行緒情況,通過使用強引用 self 來引用該弱引用,這時如果 self 不為 nil 就會 retain self,以防止在後面的使用過程中 self 被釋放;然後在之後的 block 塊中使用該強引用 self,注意在使用前要對 self 進行了 nil 檢測,因為多執行緒環境下在用弱引用 wself 對強引用 sself 賦值時,弱引用 wself 可能已經為 nil 了。
    通過這種手法,block 就不會持有 self 的引用,從而打破了迴圈引用。