最近开发中正好遇到了一个问题: 首先这是一个会引起循环引用的 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本身:0x7fff5a684bd8weakself指向:0x7fc8bb509f10 - weakSelf本身:0x7fff5a684bb8释放啦~Block内部,weakSelf指向:0x0 - weakSelf本身:0x60800005a840strongSelf指向: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发送消息是不会有问题的。