版权声明:欢迎大家积极分享!交流。关注我~ https://blog.csdn.net/qinqi376990311/article/details/79031617
最近开发中正好遇到了一个问题:
首先这是一个会引起循环引用的 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发送消息是不会有问题的。