最近開發中正好遇到了一個問題: 首先這是一個會引起迴圈引用的 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傳送訊息是不會有問題的。