关于block的循环引用,你看我就够了!

2,600 阅读6分钟

开篇:

如果你之前看过我block的其他的文章,你会更容易今天这篇博客的一些更深的解释,每篇只有一个知识点,基本上把这些都看了,block基本不会有什么问题的,基本都是掌握了.

block的源码解读之变量捕获机制(capture)

从源码、ARC、MRC带你理解block的三大类型

深入理解block的底层之copy

深入理解block的底层之对象类型的auto变量

深入理解__block修饰符的底层实现原理

循环引用:

在平时开发过程中,block是我们经常使用的功能,如果不多加注意就会造成一些问题,最明显的就是循环引用的问题,也是面试官经常问到的问题,这篇博客会着重介绍循环引用的解决方案,并从源码角度解读为什么这些方案可以解决循环引用.

请看下图代码

循环引用代码实例

以上造成循环引用的原因是myBlock持有了person对象,而person内部也持有了myBlock,导致循环引用

person -> myBlock ->person

用图来解释就是:

self和block相互持有,类似上面一摸一样

解释:当person要释放的时候,因为里面持有myBlock,所以无法释放自己;同时myBlock也会等着person释放自己,所以形成了相互持有都无法释放

我在GDPerson里面调用dealloc方法,并在里面打印打印 NSLog(@"%s",__func__);我们看下会不会调用很明显程序执行完毕也不会调用这个dealloc方法,接下来我们看看解决方案

解决循环引用的问题-ARC

解决block的循环引用,我们一共可以用3种方案来解决,首先我们来看ARC模式下有哪些方案

1.用_ _weak解决

首先我们知道现在是person ->block 和 block -> person,我们只要其中一个强引用变没有,就解决了这个问题了吧,一般我们更希望block -> person这个条线断开,因为person是一个对象里面存着block,我们希望保留那条关系,如下请看如下代码:

ARC环境下运行

这个是不是就解决了这个问题,好我们clang一下,把main.m转成c++看一下(命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main.cpp)

ARC环境下运行

如果你看过我之前的博客,你应该非常清楚这段源码:栈block进行copy操作成堆block的时候,调用对象的时候会自动生成如上的copy和dispose函数,然后copy函数又会调用内部__main_block_copy_0函数,然后再调用_Block_object_assign函数,会根据你上面传入的是weak还是strong对这个是这个对象是否持有的操作.明显传入的weak,所以不会持有(细节可以参考之前的博客,这知识说一下大致内容)

2.用_ _unsafe_unretained解决

首先我们来说下,__unsafe_unretained和__weak的区别:

__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址不变

__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil

__unsafe_unretained直面翻译也是不安全的,主要是对象销毁了,释放了,再去访问它,就可能变成野指针,所以不安全,而__weak再访问它,顶多是nil,不会报错!__unsafe_unretained这个非常少用,如果是面试的时候还是可以答一下,它确实能解决循环引用

好,我们看一下下面的代码结果:

ARC环境下

一看,确实调用了dealloc方法,也确实确定解决了循环引用

3.用_ _block解决

首先我们知道_ _block能用解决block内部想修改外部的auto的问题,它其实也能解决循环引用的问题,如果面试官问你_ _block有什么用,我想你肯定能答对这2个作用,好吧,话不多说,我们继续,先看结果,再解释原因

ARC环境下

这里看看,是不是也是调用了dealloc方法,说明是成功的释放了,解决了循环引用,但是有个弊端就是,你必须要调用上面的我圈出来的红色的代码,才能解决循环引用 MyPerson =nil; person.myBlock();这两句.

那我们就来看一下是什么原因,首先我们先看这个为什么会有循环引用,我们先来回顾一下之前的知识,加了__block会生成什么,请看下图

ARC环境下

所以__block就会持有person,这个之前的博客都是介绍的很清楚,所以,代码中的block就有三个

这张图很清晰解释关系

person->block; block->__block; __block->person

正好是三角循环,所以更是造成了循环引用.所以我们只要把其中强引用去掉就行了,所以我把对象置为nil就是把__block->指向对象的那条线,那个强引用删除了啊,对象不存在,自然关系也不存在,也是解决了循环引用的问题.

这个唯一的弊端就是你要写那2句代码,如果你只写了block内部置为nil的代码,而不去调用它,那也会造成内存泄漏!

解决循环引用的问题-MRC

这个知识点作为了解,因为现在都是ARC,万一面试官问你ARC的情况,你也要了解,

因为MRC没有__weak的理念,我们就不讨论这个了,不支持弱指针

1.用_ _unsafe_unretained-解决

大家先看下面的代码结果。 (注意改成MRC测试哦!) 而且MRC要调用[super dealloc];

MRC环境运行

所以很明显,这个是可以解决MRC环境下的循环引用的问题.

2.用_ _block-解决

首先我们还是先看结果

MRC环境运行

这个就是一个区别,一个不一样的地方,在MRC情况下,不管你myPerson是强指针还是弱指针,__block都不会对person进行强引用,这就是区别.

总结:

这里我们就能总结之前的block的面试题了,请看下(几篇博客的一起总结结果)

下面只是大致写了一下,供大家参考,很多我之前都总结了,我就没有每个都搬运过来.

1.block的原理是怎么样的?本质是什么?

block本质是一个oc对象,它内部也有个isa指针,block内部封了函数调用及函数调用的oc对象.

2._block的作用是什么?有什么使用注意点?

可以在block内部修改auto变量,还可以解决循环引用,注意内存地址的处理,而且在MRC环境下__block不会对对象产生强引用

3.block的修饰词为什么是copy?使用block有哪些使用注意?

复制到堆上,存活时间更长久,更好的使用它,注意循环引用等.

4.block在修改NSMutableArray,需不需要添加_block?

不需要,因为是使用它,不是修改它,能不用_block就不要用_block,因为会生成更多东西.

接下来博客我会介绍Runtime的知识点,来探讨Runtime

如果觉得我写得对您有所帮助,请关注我,我会持续更新😄