NSTimer和Block的弱引用问题

903 阅读2分钟

考虑一个问题,为什么在使用timer的时候会碰到这样的代码

- (void)viewDidLoad{
    [super viewDidLoad];
    self.timer = [NSTimer timerWithTimeInterval:1 target:self
    selector:@selector(timerFire) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop]addTimer:self.timer
    forMode:NSDefaultRunLoopMode];
}

- (void)timerFire {
    NSLog(@"%s",__func__);
}

很明显,这样的代码,如果不处理好会导致内存泄露,比如要这么处理

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
    [self.timer invalidate];
}

如果仅仅是在dealloc 中写[self.timer invalidate]; 是不行,因为控制器会因为循环引用而导致无法释放,从而不会走dealloc

解决方法一:

尝试按照block的方式,使用__weak来添加弱引用

 __weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf
selector:@selector(timerFire) userInfo:nil repeats:YES];

这样的方式还是会导致循环引用.

看timer的文档,我们会得到这样的注解:

the timer maintains a strong reference to this object until it is invalidated;

这句话很明显的告诉使用者,timer 会对target 进行一次 retain操作,从而 timer 和target互相用strong指着,导致无法释放,直到我们主动调用 invalidate.

那么我们如果按照常见的循环引用block来测试的时候,典型的循环引用,self被block捕获而获得一个strong的引用

self.block = ^{
    NSLog(@"%@",self);
};
self.block();

破解循环引用

__weak typeof(self) weakSelf = self;
self.block = ^{
    NSLog(@"%@",weakSelf);
};
self.block();

就可以打破这种循环引用.

其实破解循环引用的原因不在weakself上,而是block的变量捕获机制上,

简单的测试下一段代码转成c++后的代码

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *objc = [NSObject new];
        __weak NSObject *objc_weak = objc;
        void (^block)(void) = ^(){
            NSLog(@"%@",objc_weak);
        };
        block();
        
        NSLog(@"Hello, World!");
    }
    return 0;
}

//相应的block类型 __weak
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *__weak objc_weak;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__weak _objc_weak, int flags=0) : objc_weak(_objc_weak) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//strong的代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *objc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_objc, int flags=0) : objc(_objc) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

可以看到在捕获objc时,对应捕获的变量类型,会做出默认strong, 和weak时 使用__weak来保存捕获的变量.而对timer来说,最终会对weakself msgsend retain 其实还是对self 进行retain操作,最终会导致循环引用.

所以在有些代码的时候,我们会这样使用block

    __weak typeof(self) weakSelf = self;
    self.block = ^{
        if (weakSelf) {
            __strong typeof(self) strongSelf = weakSelf;
            NSLog(@"%@",strongSelf);
        }
    };
    self.block();

首先判断 self是否在block回调的期间被释放,导致self为nil,接着使用strongself 来给weak做一个强指针引用,保证在block的作用域上,self不会被突然释放.