上一篇文章中说过,auto类型的局部变量,能够被block捕获,可是不能修改值。函数
__block能够解决block内部没法修改外部auto变量的问题。测试
__block int age = 10; void (^myblock)(void) = ^{ NSLog(@"%d",age); }; age = 20; myblock();
用法就是这么简单,这样咱们修改age为20的时候,打印也是20。spa
咱们看看编译后的代码。debug
struct __Block_byref_age_0 { void *__isa; __Block_byref_age_0 *__forwarding; int __flags; int __size; int age; }; //Block struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_age_0 *age; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
在block内部多了一个指向__Block_byref_age_0
类型结构体的age指针。上面我也帖上了这个__Block_byref_age_0
结构体的结构。咱们发现int类型的age在这个结构体内部了。指针
那也就是说,__block修饰的变量,编译器会把它包装成一个对象,而后咱们的这个成员变量放到了这个对象的内部。code
咱们观察一下这个__Block_byref_age_0
内部,这些变量可能有疑惑的也就是这个__forwarding
。他是一个指向这个结构体自身的指针。并且咱们还能够看出来在打印age的时候,是也是经过__forwarding调用的age(age->__forwarding->ag
),具体为何要多加这个字段,咱们后面再说。对象
static struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); void (*dispose)(struct __main_block_impl_0*); }
__main_block_desc_0
这个结构体中也多了两个指针,这是与内存管理有关的函数。生命周期
底层分析差很少了,那咱们还没说到为何__block修饰的属性,在block内部能够修改,咱们看下面的代码内存
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10}; void (*myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344)); (age.__forwarding->age) = 20;
咱们建立了__Block_byref_age_0
类型的age对象,同时把外部age的值也就是10,传递了进去。而后初始化了block。rem
关键是下面的修改age的值的时候,直接就是修改的age对象里面的age属性了,而后打印的时候,也是打印的他。
这个地方其实仍是挺抽象的了,也不是很好理解。
怎么前面定义的age变量跟后面修改的就不是一个了?
__block int age = 10; NSLog(@"%p",&age); void (^myblock)(void) = ^{ NSLog(@"%d",age); }; NSLog(@"%p",&age); age = 20; myblock();
这是最简单的方法,打印出两个age的地址,就是不同的。那咱们怎么去判断就是__Block_byref_age_0
里面的age呢,你们能够参考下面的作法。
struct __Block_byref_age_0 { void *__isa; struct __Block_byref_age_0 *__forwarding; int __flags; int __size; int age; }; struct __main_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(void); void (*dispose)(void); }; struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __main_block_impl_0 { struct __block_impl impl;// 8+4+4+8 struct __main_block_desc_0* Desc;//8 struct __Block_byref_age_0 *age;// 8+8+4+4 }; int main(int argc, const char * argv[]) { @autoreleasepool { __block int age = 10; void (^block)(void) = ^{ age = 20; NSLog(@"age is %d", age); }; struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block; NSLog(@"%p", &age); } return 0; }
上面的操做就是咱们把底层的一些结构拿出来,而后把咱们的block类型桥接成__main_block_impl_0
类型。而后咱们经过debug能够拿到这个blockImpl的地址,而后经过内存中的地址偏移计算出来内部__Block_byref_age_0
中的age地址,看看和打印出来的age地址是否一致。
咱们拿到blockImpl的地址是0x1005002d0(我测试时候的值,每次都不一样)。
__main_block_impl_0
内部第一个属性是一个__block_impl
结构体,而后__block_impl
内部两个指针(一个指针8字节)两个int(一个int4字节),共占24字节。
第二个参数是一个指针,8字节。
age在第三个参数指向的结构体中,也就是说__Block_byref_age_0
类型的age内存地址是0x1005002d0偏移32,也就是0x100500300。而后__Block_byref_age_0
内部的age变量前面,有两个指针两个int,24字节,0x100500300再偏移24,也就是0x100500318。跟咱们NSLog(@"%p", &age);
打印的一致。
因此能够得出,咱们修改的这个age,其实就是底层age对象内部的age变量。
上面咱们留下了一个__forwarding
指针的疑问,咱们先不着急解决,先说说block类型。
block有isa指针,开始我想经过写了几个类型的block,用clang编译看cpp代码,但发现一直是这个样子impl.isa = &_NSConcreteStackBlock;
。因此我就用最直接的打印[obj class]的方法。
注意,由于在ARC的环境下,编译器给咱们作了不少内存相关的工做,因此我在研究block类型的过程当中切换到了MRC环境。
我用过的例子就不写了,下面是一个小总结。
一共有三种Block
__NSGlobalBlock__ 内存位于数据区
__NSStackBlock__ 栈区
__NSMallocBlock__ 堆区
具体什么样的block对应哪种类型?
__NSGlobalBlock__
:没有访问auto变量__NSStackBlock__
:访问了auto变量__NSMallocBlock__
: __NSStackBlock__
调用copy
提示
咱们在声明一个block属性的时候,习惯用copy关键字,是为了把栈区的block拷贝到堆区,让咱们来管理他的生命周期。
ARC环境下会根据状况自动将栈上的block拷贝到堆上。ARC环境下也用copy是为了和MRC环境统一,也能够用strong。
当__block变量在栈上时,不会对指向的变量产生强引用。
当__block变量copy到堆上时,会根据这个变量的修饰符是__strong,__weak,__unsafe_unretained作出相应的操做造成强引用(retain)或者弱引用。(ARC会retain,MRC不会)。
最后说一下上面的__forwarding指针问题。
这个图能够很好地诠释这个问题了。
咱们的block在内存中可能位于栈上,可能在堆上。
使用了这个指针以后,让咱们在block位于不一样内存位置的状况下,访问到相应准确位置的变量。