Block内部结构
block的数据结构定义
代码如下:
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
};
通过上面的结构, 可以看出一个 block 实例的构成实际上有6个部分:
isa指针: 所有对象都有该指针,用于实现对象相关的功能。flags: 附加标识位, 在copy和dispose等情况下可以用到。reserved:保留变量。invoke: 函数指针,指向block的实现代码, 也可以说是函数调用地址。descriptor: 表示该block的附加描述信息,主要是size,以及copy和dispose函数的指针。这两个辅助函数在拷贝及丢弃块对象时运行, 其中会执行一些操作, 比方说, 前者要保留捕获的对象,而后者则将之释放。variables: 捕获的变量,block能够访问它外部的局部变量,就是因为将这些变量复制到了结构体中。
面试题
2.block 是类吗?有哪些类型?
block 算是类,因为 block 有 isa 指针
有三种类型
_NSConcreteGlobalBlock全局的静态block,不会访问任何外部变量。_NSConcreteStackBlock保存在栈中的block,当函数返回时会被销毁。_NSConcreteMallocBlock保存在堆中的block,当引用计数为 0 时会被销毁。
3.一个 int 变量被 __block 修饰与否的区别?
先看代码
__block int a = 20;
int b = 20;
void (^printBlock)(void) = ^{
a -= 20;
NSLog(@"block内部 a = %d",a);
NSLog(@"block内部 b = %d",b);
};
printBlock();
a += 20;
b += 20;
NSLog(@"block外部 a = %d",a);
NSLog(@"block外部 b = %d",b);
printBlock();
block内部 a = 0
block内部 b = 20
block外部 a = 20
block外部 b = 40
block内部 a = 0
block内部 b = 20
通过 __block 修饰 int a,block 结构体中对这个变量的引用是指针拷贝,它会作为 block 结构体构造参数传入到结构体中且复制这个变量的指针饮用,从而达到可以修改变量的作用。
int b 没有被 __block 修饰,block 内部对 b 是值拷贝,所以在 block 外部修改 b 不影响内部 b 的变化。
4.block 的变量截获?
先看代码
int main() {
id arr = [NSMutableArray new];
void (^subBlock)(id) = ^(id obj){
[arr addObject:obj];
NSLog(@"arr count = %ld",[arr count]);
};
subBlock([NSObject new]);
subBlock([NSObject new]);
subBlock([NSObject new]);
}
arr count = 1
arr count = 2
arr count = 3
把上面代码翻译成C(在命令行中输入clang -rewrite-objc block1.c即可在目录中看到 clang 输出了一个名为 block1.cpp 的文件。该文件就是 block 在 c 语言实现,将 block1.cpp 中一些无关的代码去掉,将关键代码引用如下:)
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;// 成员变量
struct __main_block_desc_0* Desc;// desc结构体声明
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) };
int main()
{
(void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA) ();
return 0;
}
在OC中,C结构体里不能含有被__strong修饰的变量,因为编译器不知道应该合适初始化和废弃C结构体,但是OC运行时库能够准确把握block从栈复制到堆,以及堆上的block被废弃的时机,在实现上是通过 __TestClass__testMethods_block_copy_0 函数和 __TestClass__testMethods_block_dispose_0 函数进行的
static void __TestClass__testMethods_block_copy_0 (struct __TestClass__testMethods_block_imp_0 *dst,struct __TestClass__testMethods_block_imp_0 *src) {
_Block_object_assign((void *)&dst->array,(void *)src->array,3)
}
static void __TestClass__testMethods_block_dispose_0 (struct __TestClass__testMethods_block_imp_0 *src) {
_Block_object_dispose((void *)src->array,3);
}
_Block_object_assign相当于retain操作,将对象赋值在对象类型的结构体成员变量中_Block_object_dispose相当于release操作
什么时候栈上的 block 会被复制到堆?
- 调用
block的copy函数时 block作为函数返回值返回时- 将
block赋值给附有__strong修饰符id类型的类或者block类型成员变量时 - 方法中含有
usingBlock的Cocoa框架方法时 GCD的API中传递block时
什么时候 block 被废弃呢?
堆上的 block 被释放后,谁都不再持有 block 时调用 dispose 函数
以上就是变量被 block 捕获的内容
5.block 在修改 NSMutableArray 时,需不需要添加 __block ?
修改数组的存储内容不需要添加 __block 修饰
修改数组对象的本身则需要添加 __block 修饰
6.block 怎么进行内存管理?
-
全局
block:_NSConcreteGlobalBlock的结构体实例设置在程序的数据存储区,所以可以在程序的任意位置通过指针访问,产生条件:- 记述全局变量的地方有
block语法时 block不截获的自动变量 两个条件只要满足一个就可以产生全局block
- 记述全局变量的地方有
-
栈
block:_NSConcreteStackBlock在生成block以后,如果这个block不是全局block,那它就是栈block,生命周期在其所属的变量作用域内;如果block变量和__block变量复制到了堆上以后,则不再受到变量作用域结束的影响了,因为它变成了堆block -
堆
block:_NSConcreteMallocBlock将栈block复制到堆以后,block结构体的isa成员变量变成_NSConcreteMallocBlock
7.block 可以用 strong 修饰吗?
ARC 中可以,因为在ARC环境中的 block 只能在堆内存或全局内存中,因此不涉及到从栈拷贝到堆的操作
MRC 中不行,因为要有拷贝的过程,如果执行 copy 用 strong 的话会 crash, strong 是 ARC 中引入的关键字,如果使用 retain 相当于忽视了 block 的 copy 过程。
8.解决循环引用为什么要用 __stong,__weak 修饰?
首先因为 block 捕获变量时,结构体构造时传入了 self,造成默认的引用关系
__weak 修饰的变量,不会出现引用计数+1,也就不会造成 block 强持有外部变量,这样也就不会出现循环引用的问题了。
但是,我们的 block 内部执行的代码中,有可能是一个异步操作,或者延迟操作,此时引用的外部变量可能会变成 nil,导致意想不到的问题,而我们在 block 内部通过 __strong 修饰这个变量时,block 会在执行过程中强持有这个变量,此时这个变量也就不会出现 nil 的情况,当 block 执行完成后,这个变量也就会随之释放了。
9.block 访问对象类型的 auto 变量时,在 ARC 和 MRC 下有什么区别?
ARC 下会对这个对象强引用,MRC 下不会