什么是block?
01-Block是将函数及其执行上下文封装起来的对象,在底层是一个Block_layout。
02-Block调用即是函数调用
Block捕获外界变量的时候会生成一个同名的中间变量,取获取到的时候的值
Block使用外界变量的时候会生成一个__Block_byref_xxx_0的结构体
Block的签名是@?
Block通过__block访问外界变量的时候会有三层拷贝
1. 首先是block从栈拷贝到堆
2. 将修饰的对象转化为一个结构体,将其拷贝到堆内存
3. 将修饰的对象的内存地址也进行拷贝
Block的释放相当于拷贝的反向,拷贝的东西都需要释放的
block的底层就是一个struct _block_impl_0类型的结构体,这个结构体中包含一个isa指针,所以从更深的层次来说,block就是一个OC对象.
block结构体中又包含impl和Desc结构体,impl结构体中有一个非常重要的成员FuncPtr,FuncPtr是一个指针,指向了封装了blcok代码块的函数,
我们看到调用block的代码:myBlock()的底层实际上是这样子:myBlock->FuncPtr(myBlock);,可以看出调用block其实就是调用了FuncPtr()这个函数
Desc结构体存放了block的大小
如果没有加__block,那结果就是12,加了__block变量就包装成了对象。栈block会拷贝到堆中,并对此对象产生一个强引用。
__block原理
__block不管是修饰基础数据类型还是修饰对象数据类型,底层都是将它包装成一个对象(我这里取个名字叫__blockObj),然后block结构体中有个指针指向__blockObj,而__ Block_byref_xx_x *本质上也是一个对象,因此肯定需要处理它的内存管理问题。
当block在栈上时,block内部并不会对__blockObj产生强引用。
当block调用copy函数从栈拷贝到堆中时,它同时会将__blockObj也拷贝到堆上,并对__blockObj产生强引用。
当block从堆中移除时,会调用block内部的dispose函数,dispose函数内部又会调用_Block_object_dispose函数来释放__blockObj。
__block int a = 10;
当我们在 block 内部想要修改 变量 a 的值时,会报错 error: variable is not assignable (missing __block type specifier)。如前所述,因为在实现上不能改写被截获自动变量的值,所以当编译器在编译过程中检出给被截获自动变量賦值的操作时,便产生编译错误。
他把外部变量a捕获到一个结构体中,因为结构体是在堆区的,这样a就不会出了作用域就销毁。同时捕获了结构体a的指针地址,然后在block里面操作的也是就a的指针地址,所以就达到了修改的目的
———————————————————————————————
变量捕获
* 全局变量,静态全局变量--不会捕获,是使用外部的变量访问。
* 静态局部变量--是捕获变量地址。外面修改会影响block里捕获的地址对应的值
* 普通局部变量--是捕获变量的值,所以在block后面修改变量值不会影响block已捕获的值。
__block引起的循环引用问题:
block是否产生循环引用取决于是否发生了block捕获了某一对象,并且该对象持有这个block
上面的弊端是如果这个block得不到调用,这个循环引用就得不到解除,循环引用是编译的时候就存在的,就算不调用block,一样会存在。
怎么判断一个block是栈block还是堆block还是全局block?
如果一个block里面没有访问普通局部变量(也就是说block里面没有访问任何外部变量或者访问的是静态局部变量或者访问的是全局变量),在内存中是存在数据区的。
如果一个block里面访问了普通的局部变量,那它就是一个__NSStackBlock__,它在内存中存储在栈区,栈区的特点就是其释放不受开发者控制,都是由系统管理释放操作的,所以在调用__NSStackBlock__类型block时要注意,一定要确保它还没被释放。如果对一个__NSStackBlock__类型block做copy操作,那会将这个block从栈复制到堆上。
一个__NSStackBlock__类型block做调用copy,那会将这个block从栈复制到堆上
当你新创建了一个block且没有对其进行copy操作,此时block就是栈block,当你将block进行copy操作(赋值给一个使用copy关键字修饰的属性)后,该属性指向的block就是堆上的block,原来的栈block会在作用域结束后进行内存的回收
当创建的block赋值给成员变量型block的时候会进行copy到堆上的操作?
成员变量型的block会被copy是因为所有权修饰符我们一般声明为copy
block为什么要用copy?
block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈上的,而不是在堆上。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。因为栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区.
使用retain也可以,但是block的retain行为默认是用copy的行为实现的,
因为block变量默认是声明为栈变量的,为了能够在block的声明域外使用,所以要把block拷贝(copy)到堆,所以说为了block属性声明和实际的操作一致,最好声明为copy。