面试专题(重难点)iOSiOS基础知识

iOS底层探索之Block(二)——如何解决Block循环引用问

2021-08-30  本文已影响0人  俊而不逊

Block你知道几种?Block的循环引用你有几种解决办法呢?

Block循环引用问题

iOS底层探索之Block(一)——初识Block(你知道几种Block呢?)

在上一篇博客已经介绍了block的类型,那么本篇博客废话不多说直接进入主题,手把手带你如何解决Block循环引用的问题!

1. 为什么会出现循环引用分析

1.1 正常情况

在正常情况下,A持有了BB引用计数+1,当 A释放的时候给 B发送信号,B 接受到 release信号后,引用计数-1

正常情况示意图

正常使用,正常释放是不会出现循环引用问题的,但是如果出现你中有我,我中有你的情况,就会出现循环引用问题了。更形象的比喻,就是鹤蚌相争故事了。

1.2 循环引用原因

互相持有都释放不了,如下图所示:

循环引用示意图
这里出现循环引用问题是,AB互相持有了,两方都释放不了,A无法调用 dealloc就给 B发送不了 release消息,B 收不到 release消息引用计数不会减少,也就不能调用 dealloc方法进行释放。

那么出现循环引用该如何解决呢?

1.3 循环引用解决思路

出现循环引用的解决办法就是,打破互相持有的局面,打破任意一方的持有都可以。

2. 解决方案

先来看看一个循环引用的例子,如下:


循环引用举例

这里出现循环引用的原因是:

self持有了 blockblock 里面又持有了 self,形成了一个闭环,互相持有,无法释放。

那么再来看看,下面👇这个代码是否出现循环引用呢?

[UIView animateWithDuration:0.25 animations:^{
        self.name = @"reno";
    } completion:nil];

上面👆代码是不会出现循环引用问题的,当前 UIView持有了 blockself并没有持有block,所以这里不会出现循环引用问题的。

那么言归正传,出现循环引用改如何解决呢?请接着往下看!👇

2.1 __weak解决循环引用

第一种方法就是使用__weak,这个相信大家都很熟悉了,如下:

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

这样确实没有问题,但是如果block内部使用了延时函数VC 销毁的时候还是会出现问题,如下:

2.2 strong-weak-dance循环引用

__weak typeof(self)weakSelf  = self;
    self.block = ^(void){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakSelf.name);
        });
    };
    self.block();

很多人会问:这里的问题出在哪里呢?这里是一个延时函数是异步的,当VC销毁的时候block也跟着释放了,延时函数内的打印name就来不及打印,就出现如下情况:

打印结果
从打印来看,dealloc方法调用了,VC 已经销毁了,block 也就释放了,内部的延时函数里面的打印还来不及打印,所以 namenull。那么该如何解决呢?解决如下:
测试结果

因为__weak会自动置为nil,所以这里使用__strong(strong-weak-dance)暂时延长 self的生命周期,使得可以正常打印。

那么这里为什么不会出现循环引用问题啊?因为这里strongSelf是一个临时的变量,出了作用域也跟着释放了,所以不会出现循环引用,这就是强弱共舞方法你学会了吗?

厉害了

2.3 手动档方式解决循环引用

有的人喜欢手动挡的汽车,喜欢那种操控感,特别带劲,那么接下来就介绍一种手动方式解决循环引用问题。


__block ViewController *vc = self;
    self.block = ^(void){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
            vc = nil;
        });
    };
    self.block();

这里使用一个临时的VC变量之后,持有关系为: self --> block --> VC--> selfVCblock使用完成后就被置为nilblock不构成对self的持有关系了,因此这里就不构成循环引用问题,这是不是很爽,这一波操作很溜啊!😁

这里使用__block是因为需要对外部变量,进行赋值操作,关注我,后续篇章会对block进行底下的源码分析,敬请期待!

2.4 参数形式解决循环引用

上面👆手动操作方法你 get到了吗?还有一个更哇塞的方法,如下:


typedef void(^JP_Block)(ViewController*);

@property (nonatomic, strong) JP_Block JPBlock;

- (void)parameterMethod{

    self.JPBlock = ^(ViewController *vc){

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
             NSLog(@"%@",vc.name);
         });
    };
    self.JPBlock(self);
}

这里使用参数的方式,并不会出现循环引用的问题,是不是哇塞!好简单啊!

那么解决block循环引用问题的方法就介绍到这里了,你学会了吗?你是否还有更好的方式,欢迎评论区留言交流!

更多内容持续更新

🌹 喜欢就点个赞吧👍🌹

🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹

🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

上一篇下一篇

猜你喜欢

热点阅读