1. 程式人生 > >iOS 中block的迴圈引用問題

iOS 中block的迴圈引用問題

開發中經常使用weakSelf和strongSelf來解決block的迴圈引用問題,但是是不是所有的block都會導致迴圈引用呢?顯然不是的,那麼怎麼判斷呼叫一個帶有block方法時是否會造成迴圈引用呢,我們來分析一下。
首先我們來寫一個含有block的類,並呼叫自己,然後在外部實現這個block,來測試什麼情況會出現迴圈引用。

@interface ALDTestBlockModel ()

@property (nonatomic, copy) dispatch_block_t testBlock;
@property (nonatomic, copy) NSString * testString;

@end

@implementation ALDTestBlockModel
- (void)initWithBlock1:(dispatch_block_t)block{
    //物件不持有該block
    if (block) {
        block();
    }
}

- (void)initWithBlock2:(dispatch_block_t)block{
    //物件持有該block
    self.testBlock = block;
    if (self.testBlock) {
        self.testBlock();
    }
}
@end

外部測試程式碼:

@interface ALDTestBlockController ()

@property (nonatomic, strong) ALDTestBlockModel *model;

@end

@implementation ALDTestBlockController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.model = [ALDTestBlockModel new];
    [self noSelfTest];
    //[self haveSelfTest];
}

- (void)noSelfTest{
    [self.model initWithBlock1:^{
        NSLog(@"1111self=[]");
    }];
    [self.model initWithBlock2:^{
        NSLog(@"2222self=[]");
    }];
}

- (void)haveSelfTest{
    [self.model initWithBlock1:^{
        NSLog(@"1111self====== %@",self.testString);
    }];
    [self.model initWithBlock2:^{
        NSLog(@"2222self====== %@",self.testString);
    }];
}

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

@end

我們首先來測試在實現block中沒有出現self及成員變數之類的,我們斷點在block類中,可以看一下block和testBlock的isa,我們會發現block和testBlock的isa都是一個NSGlobalBlock型別,然後在controller點選返回,斷點在dealloc,會發現會走斷點。

然後來測一下block中出現self或者成員變數時,先測

[self.model initWithBlock1:^{
        NSLog(@"1111self====== %@",self.testString);
    }];

這個方法裡面,未持有block,點選返回,dealloc的斷點依然會走。
再來測一下

[self.model initWithBlock2:^{
        NSLog(@"2222self====== %@",self.testString);
    }];

這個方法裡面,model持有了block,點選返回不會走dealloc,說明出現了迴圈引用。這個時候就可以用weakSelf和strongSelf來解決迴圈引用的問題了。
總結一下,當呼叫帶有block的方法時,如果該方法內部沒有持有該block,那麼這個方法在被呼叫時,就不需要考慮迴圈引用的問題。如果該方法內部持有了該block,那麼這個方法在被呼叫時,一定要注意block實現時是否會用到self或者self相關的,如果要用到,就需要用weakSelf和strongSelf來避免迴圈引用。
最後補充一點,block的isa有三種:

  • NSGlobalBlock:儲存在程式的資料區域,在 block 內部沒有引用任何外部變數。
  • NSStackBlock:儲存在棧上,在 block 內部引用外部變數。在 MRC 下,棧塊在當函式退出的時候,該空間會被回收,因此如果再呼叫該 block 會導致 crash,通過拷貝該棧塊,可以解決該問題。在 ARC 模式下,生成的 block 也是 棧塊,只是當賦值給 strong 物件時,系統會主動對其進行 copy。
  • NSMallocBlock:儲存在堆上的 block。
    我們都知道棧區的記憶體是系統自動管理的,所以出現NSStackBlock時我們可以不用考慮記憶體問題,但是出現NSMallocBlock時一定要注意了,很容易出現記憶體洩漏。