【iOS Sharing】【6】Block相关
本文最后更新于:2021年12月22日 上午
【iOS笔记】系列目录
目录
1. block的循环引用是如何造成的?
2. 使用UIAnimation的block回调时,需不需要使用__weak避免循环引用?为什么?
3. block属性是否可以用strong修饰?
4. 什么场景下才需要对变量使用__block?
5. block 修改捕获变量除了用 __block 还可以怎么做?有哪些局限性?
1. block的循环引用是如何造成的?
1 |
|
我们在上面的代码中创建了一个Light(光)类,并声明两个属性color(颜色)及block。然后我们实例化一个对象loveLight并对其属性赋值,实现并调用block,造成循环引用。
然后我们通过clang代码,了解这段代码内部的部分实现:
1 |
|
通过clang后的源码我们可以知道:
- 对象的创建本身就是强引用(默认strong修饰)。
- 对象对block属性赋值,在ARC下,block作为返回值时或者赋值给一个strong/copy修饰的对象会自动调用copy,loveLight强引用block。
- 对象的block在其内部捕获了对象本身,block在自动调用copy的时候,_Block_object_assign(clang源码最后一行)会根据捕获变量的所有权修饰符,来对变量的引用计数进行操作。此处loveLight本身是strong修饰,则引用计数+1,block强引用loveLight对象。
- 所以双方互相引用,造成了循环引用。
同时面试中面试官还可能会询问你如何检测到内存泄漏,我们可以通过Instruments中的Leaks进行检测,也可以选择facebook发布的FBRetainCycleDetector内存泄漏检测工具。
2. 使用UIView Animation的block回调时,是否需要考虑循环引用的问题?为什么?
首先UIView Animation使用时,不需要考虑循环引用的问题。
UIKit将动画直接集成到UIView的类中,当内部的一些属性发生改变时,UIView将为这些改变提供动画支持,并以类方法的形式提供接口。
而block造成循环引用的主要原因是对象与block的相互持有,UIView Animation的block本身处于类方法中,在使用时并不属于调用控制器。同时控制器也无法强引用一个类,所以不会造成循环引用的问题。
3. block属性是否可以用strong修饰?
block属性可以使用strong属性修饰符修饰,但是不推荐,会有内存泄漏的隐患。
首先,ARC中block用copy属性修饰符修饰是MRC时代延续的产物,提醒开发者可能存在的内存问题。同时copy的确是可以用strong来替代的。
我们都知道block在OC中有三种类型:- _NSConcreateGlobalBlock 全局的静态block,不会访问任何外部变量。- _NSConcreateStackBlock 栈区的block,当函数返回时会被销毁。- _NSConcreateMallocBlock 堆区的block,当引用计数为0时被销毁。
block在MRC下可以存在于全局区、栈区和堆区,而在ARC下,block会自动从栈区拷贝到堆区(除了裸写block实现块),所以只存在于全局区和堆区。
所以对于栈区block,MRC下处于栈区,想在作用域外调用就得copy到堆区;ARC则自动copy堆区。
那么这个时候问题就来了,strong属性修饰符并不能拷贝,就会有野指针错区的可能,造成Crash。这种情况很少见,但是不代表不可能发生,所以最好还是使用copy属性修饰符。
此处感谢@buaacyg的评论反馈,同时也对撰文的不严谨深表歉意。
更正:
针对Block属性修饰符的问题在撰写的时候的确没有考虑周全,我们将在以下予以更正和解答。
首先,在以下情形中block会自动从栈拷贝到堆:
- 1、当 block 调用 copy 方法时,如果 block 在栈上,会被拷贝到堆上;
- 2、当 block 作为函数返回值时,编译器自动将 block 作为 _Block_copy 函数,效果等同于直接调用 copy 方法;
- 3、当 block 被赋值给 __strong id 类型的对象或 block 的成员变量时,编译器自动将 block 作为 _Block_copy 函数,效果等同于直接调用 copy 方法;
- 4、当 block 作为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 时。这些方法会在内部对传递进来的 block 调用 copy 或 _Block_copy 进行拷贝;
那针对上述自动拷贝的情况我们做一个实验:
ARC下strong修饰block,且不引用外部变量,block类型为__NSGlobalBlock
ARC下strong修饰block,引入外部变量,block类型为__NSMallocBlock
所以由此就可以理解为ARC下strong修饰的block并没有处于栈区的可能,也就不存在作用域结束栈区内容销毁野指针的问题了。
但是为了保证修饰符和block特性的一致性,使用copy修饰符仍然是最为合适的。
4. 什么场景下才需要对变量使用__block?
赋值场景下使用__block,使用场景下不需要。
我们来对比下赋值场景和使用场景
1 |
|
5. block 修改捕获变量除了用 __block 还可以怎么做?有哪些局限性?
- 将变量声明为全局的或利用
static
修饰 - 也可以把变量声明为类的成员变量或者属性,访问同样不需要
__block
__block
修饰的局部变量会随着 block 销毁而销毁,内存管理与 block 同步,全局/局部静态/全局静态,存在于程序的整个生命周期,成员变量的生命周期由其类控制
联系方式
邮箱: xiebangyao_1994@163.com
相关账号:
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!