block的原理是怎样的?本质是什么?
一个含有自动变量的匿名函数
Block 的本质是一个封装了函数及其调用环境的 Objective-C 对象
一个 block 实际是一个对象,它主要由一个 isa 和 一个 impl 和 一个 descriptor 组成。
是封装了函数代码以及其执行上下文(捕获的变量和常量)的对象
一个 block 实例由 6 部分构成:
- isa 指针,所有对象都有该指针,用于实现对象相关的功能。
- flags,用于按 bit 位表示一些 block 的附加信息,本文后面介绍 block copy 的实现代码可以看到对该变量的使用。
- reserved,保留变量。
- invoke,函数指针,指向具体的 block 实现的函数调用地址。
- descriptor, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。
- variables,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。
__block
的作用是什么?有什么使用注意点?
__block
可以用于解决 block 内部无法修改auto变量值,__block
不能也没必要修饰全局变量、静态变量
__block
是一个在 Block 内部修饰变量的关键字。使用 __block
修饰的变量可以在 Block 内部被修改,并且这些修改会在 Block 执行后在外部保留。在 Block 内部修改通过 __block
修饰的变量不会创建一个新的拷贝,而是直接修改原始变量。
- 使用
__block
修饰的变量会增加其在栈上的生命周期,当 Block 在堆上被拷贝时,__block
修饰的变量也会被拷贝到堆上,确保 Block 内部对该变量的修改在 Block 外部也能得到保留。 - 小心循环引用:如果一个对象持有一个拥有
__block
修饰的 Block 的强引用,而该 Block 又捕获了该对象,可能导致循环引用。此时需要在 Block 内部使用__weak
来修饰对象,或者使用weakSelf
和strongSelf
来避免循环引用。 - 在block执行的代码块中,将捕获的变量重置为 nil,缺点是必须执行完 block 块才会解决循环引用
- 捕获变量作为参数传入
block的属性修饰词为什么是copy?使用block有哪些使用注意?
Block 在定义时通常存储在栈上,但如果你将 Block 赋值给一个引用类型(如将 Block 赋值给对象属性),为了避免在 Block 在栈上销毁后导致访问野指针的问题,需要将其拷贝到堆上。使用 copy
修饰符会在将 Block 赋值给引用类型时执行一次拷贝操作,确保 Block 在堆上得到正确的管理,以便在其生命周期内保持有效。
MRC 下 block 如果没有 copy 到堆上,值捕获不会对外部变量引用。 虽然 ARC 环境 strong 也可以修饰 Block,那是因为编译器会对 strong 修饰的 block 也会进行一次 copy 操作。为什么用 copy 修饰算是历史习惯问题,推荐不管 ARC、MRC 使用 copy 修饰 。使用注意:循环引用问题
block一旦没有进行copy操作,就不会在堆上
ARC 环境下下列操作会自动 block 进行 copy 操作:
- block 作为方法的返回值
- 将 block 赋值给 __strong 指针时
- block 作为Cocoa API中方法名含有usingBlock的方法参数时
- block 作为GCD API的方法参数时
- block捕获变量使用的时候
block在修改NSMutableArray,需不需要添加__block?
在 Block 内部修改 NSMutableArray(或其他可变集合类)的内容时,并不需要添加 __block
修饰符。可变集合类是引用类型,所以 Block 内部对它的修改会直接影响到原始对象,无需使用 __block
修饰符。
ARC 对 block 类型的影响
在 ARC 开启的情况下,将只会有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的 block。
原本的 NSConcreteStackBlock 的 block 会被 NSConcreteMallocBlock 类型的 block 替代。证明方式是以下代码在 XCode 中,会输出 <__NSMallocBlock__: 0x100109960>
。在苹果的 官方文档 中也提到,当把栈中的 block 返回时,不需要调用 copy 方法了。
我个人认为这么做的原因是,由于 ARC 已经能很好地处理对象的生命周期的管理,这样所有对象都放到堆上管理,对于编译器实现来说,会比较方便。