Block内部访问实例变量会出现的问题

版权声明:欢迎大家积极分享!交流。关注我~ 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发送消息是不会有问题的。

猜你喜欢

转载自blog.csdn.net/qinqi376990311/article/details/79031617