Block內部訪問例項變數會出現的問題

Se7en丶秦發表於2017-12-21

最近開發中正好遇到了一個問題: 首先這是一個會引起迴圈引用的 Block 屬性, 然後需要在 Block 中訪問例項變數。

ViewController

#import "ViewController.h"
#import "TestView.h"

@interface ViewController ()
{
    int _a;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"self指向:%p - self本身:%p", self, &self);
    __weak __typeof(self) weakSelf = self;
    NSLog(@"weakself指向:%p - weakSelf本身:%p", weakSelf, &weakSelf);
    TestView *test = [[TestView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
    test.backgroundColor = [UIColor cyanColor];
    test.handler = ^(NSString *str) {
        __strong __typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"Block內部,weakSelf指向:%p - weakSelf本身:%p", weakSelf, &weakSelf);
        NSLog(@"strongSelf指向:%p - strongSelf本身:%p", strongSelf, &strongSelf);
        NSLog(@"%d", strongSelf->_a);
    };
    [self.view addSubview:test];

    //想辦法讓ViewController被釋放掉
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [UIApplication sharedApplication].keyWindow.rootViewController = [[UIViewController alloc] init];
    });
}

- (void)dealloc {
    NSLog(@"釋放啦~");
}
複製程式碼

TestView

@interface TestView : UIView

@property (nonatomic, copy) void(^handler)(NSString *str);

@end

@implementation TestView

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(7.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            if (self.handler) {
                self.handler(@"hahaha");
            }
        });
    }
    return self;
}

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

複製程式碼

這麼做的流程就是,5秒之後,切換 keyWindow 的 RootVC ,此時 ViewController 被釋放。然後再過兩秒,呼叫 Block 。由於有 GCD ,所以 testView 會被延遲釋放的。Block 執行完畢之後,testView 會被釋放。當然,此時訪問了 _a ,造成野指標訪問,直接 Crash .

輸出結果:

self指向:0x7fc8bb509f10 - self本身:0x7fff5a684bd8
weakself指向:0x7fc8bb509f10 - weakSelf本身:0x7fff5a684bb8
釋放啦~
Block內部,weakSelf指向:0x0 - weakSelf本身:0x60800005a840
strongSelf指向:0x0 - strongSelf本身:0x7fff5a686070
(lldb) //到這裡已經Crash
複製程式碼

崩潰資訊

可以看到,它們指向的地址是一樣的,也就是當前 ViewController 。 另一方面,我們也驗證了一個結論:

Block 會對內部訪問的變數進行 copy 操作。在外面的 weakSelf 和

Block 內部的 weakSelf 不是同一個。看地址,外面的 weakSelf 是在棧區,而 Block 內部的 weakSelf 被拷貝到了堆區。

重點在這裡:

此時,假若 ViewController 被釋放了,然後這個 Block 執行了,此時 strongSelf 是空指標,指向 0x0 ,那麼這個時候,你再用 “->” 運算子去取 _a ,肯定是取不到的,就會造成野指標錯誤。

PS:

“->” 叫做指向結構體成員運算子,用處是使用一個指向結構體或物件的指標訪問其內成員。 我認為:self->_a 的含義就是,從 self 的首地址,偏移到 _a 。從而可以訪問到 _a ,這與魔法數有些類似。 (理解有誤的話歡迎指正~)

綜上所述,在會引起 Retain cycle 的 Block 內部需要訪問例項變數的時候,建議改寫為屬性。因為屬性的 getter 方法是訊息機制,向nil傳送訊息是不會有問題的。

相關文章