天天看點

iOS開發筆記之五十七——__weak與__strong是如何解決循環引用的

當我們寫一個block時,如果你的block涉及被self持有以及需要通路self的成員時,循環引用問題由此産生。解決的辦法也很簡單,其中利用__weak與__strong是常見的手段,類似代碼如下:

__weak typeof(self) weakSelf = self;
self.block = ^{
      __strong typeof(weakSelf) strongSelf = weakSelf;
      [strongSelf print];
};
           

如果要把這個問題說清楚明白,不是件容易的事,看了很多文章,都沒有深入全面地講明白這個問題。為了深入剖析其中的原理,建構了一個MyTestBlock類,類的結構代碼如下:

//
//  MyTestBlock.h
//  myBlock
//
//  Created by lizitao on 16/10/2.
//  Copyright © 2016年 lizitao. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface MyTestBlock : NSObject

@property (nonatomic, copy) void(^block)();

@end

           
//
//  MyTestBlock.m
//  myBlock
//
//  Created by lizitao on 16/10/2.
//  Copyright © 2016年 lizitao. All rights reserved.
//

#import "MyTestBlock.h"

@implementation MyTestBlock

- (void)dealloc
{
    NSLog(@"------>dealloc");
}

- (instancetype)init
{
    self = [super init];
    if (self)
    {
        __weak typeof(self) weakSelf = self;
        self.block = ^{
            __strong typeof(weakSelf) strongSelf = weakSelf;
            [strongSelf print];
        };
    }
    return self;

}

- (void)print
{
    NSLog(@"---->print");
}

@end
           

然後在main方法裡測試它:

//
//  main.m
//  myBlock
//
//  Created by lizitao on 16/10/2.
//  Copyright © 2016年 lizitao. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "MyTestBlock.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        MyTestBlock *testBlock = [[MyTestBlock alloc] init];
        testBlock.block();
    }
    while (1) {;} //讓主程式一直運作不退出
    return 0;
}

           

代碼中,block屬性為copy,定義為copy屬性是一個存放在堆的block,堆block從棧複制過程中會複制它所使用的變量的引用(非__block屬性),循環引用也是是以産生。畫了個簡單的示意圖,如下:

iOS開發筆記之五十七——__weak與__strong是如何解決循環引用的
iOS開發筆記之五十七——__weak與__strong是如何解決循環引用的

對于代碼中的這一步,是如何打破這個循環的呢?我們在block外的weakSelf構造了一個指向self的弱引用,如下:

__weak typeof(self) weakSelf = self;
           

如下圖所示:

iOS開發筆記之五十七——__weak與__strong是如何解決循環引用的

如果你的block之行不是異步之行,在顯示調用testBlock.block()時,就已經執行,此時的strongSelf變量是可以省略的,如下:

__weak typeof(self) weakSelf = self;
self.block = ^{
       [weakSelf print];
};
           

列印結果如下:

2017-02-03 12:13:35.881 myBlock[6131:4562526] ---->print
2017-02-03 12:13:35.883 myBlock[6131:4562526] ------>dealloc

           

雖然循環引用就打破了,但是新的問題又來了。那就是會有self提前于block執行之前釋放的場景,testBlock實體釋放了,self就指向nil了,weakSelf也會被置為nil,等block回來時,其實在向一個nil發消息。此時就用到了strongSelf,它的作用主要是應對異步執行的block,添加異步邏輯如下:

__weak typeof(self) weakSelf = self;
self.block = ^{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:5];
        [weakSelf print];
    });
};
           

此時的執行結果如下:

2017-02-03 12:19:14.229 myBlock[6181:4566726] ------>dealloc

block中的語句沒有執行,添加strongSelf邏輯之後:

__weak typeof(self) weakSelf = self;
self.block = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       [NSThread sleepForTimeInterval:5];
        [strongSelf print];
                
   });
};
           

5s之後的列印結果如下:

2017-02-03 13:30:23.129 myBlock[6327:4593186] ---->print
2017-02-03 13:30:23.131 myBlock[6327:4593186] ------>dealloc
           

針對上面的過程,補充流程圖如下:

iOS開發筆記之五十七——__weak與__strong是如何解決循環引用的
iOS開發筆記之五十七——__weak與__strong是如何解決循環引用的

有人看到這裡,肯定會有疑惑,這裡strongSelf的引用不又像前面的self一樣導緻了循環引用了嗎?這裡需要好好解釋一下:

self是一個指向執行個體對象的指針,它的生命周期至少是伴随着目前的執行個體對象的,是以一旦它和對象之間有循環引用是無法被自動打破的;strongSelf是block内部的一個局部變量,變量的作用域僅限于局部代碼,而程式一旦跳出作用域,strongSelf就會被釋放,這個臨時産生的“循環引用”就會被自動打破,代碼的執行事實上也是這樣子的。

我們可以簡單地驗證一下,首先我們用到一個CoreFoundation中的一個方法CFGetRetainCount,它可以在ARC下擷取retainCount,修改main函數中如下:

@autoreleasepool
{
    MyTestBlock *testBlock = [[MyTestBlock alloc] init];
    NSLog(@"block執行前:%lu",CFGetRetainCount((__bridge CFTypeRef)testBlock));
    testBlock.block();
    NSLog(@"block執行後:%lu",CFGetRetainCount((__bridge CFTypeRef)testBlock));
}
           

執行結果如下:

2017-02-03 13:49:34.465 myBlock[6380:4604974] block執行前:1
2017-02-03 13:49:34.466 myBlock[6380:4604974] block執行後:2
2017-02-03 13:49:39.469 myBlock[6380:4605000] ---->print
2017-02-03 13:49:39.469 myBlock[6380:4605000] ------>dealloc
           

我們發現block執行後,testBlock對象的retainCount加了1,說明block執行時,對testBlock對象臨時加了強引用, 但是最終對象順利dealloc。

繼續閱讀