黑幕背后的__block修饰符
来源:互联网 发布:javaweb注册登陆源码 编辑:程序博客网 时间:2024/05/17 00:08
我们知道在Block使用中,Block内部能够读取外部局部变量的值。但我们需要改变这个变量的值时,我们需要给它附加上__block修饰符。
__block另外一个比较多的使用场景是,为了避免某些情况下Block循环引用的问题,我们也可以给相应对象加上__block 修饰符。
为什么不使用__block就不能在Block内部修改外部的局部变量?
我们注意到Block实质被转换成了一个__main_block_impl_0的结构体实例,其中__main_block_impl_0结构体的成员包括局部变量val。在__main_block_impl_0结构体的构造方法中,val作为第三个参数传递进入。
但执行我们的Block时,通过block找到Block对应的方法执行部分__main_block_func_0,并把当前block作为参数传递到__main_block_func_0方法中。
__main_block_func_0的第一个参数声明如下:
这个时候我们就可以通过__cself->val对该变量进行访问。
那么,为什么这个时候不能给val进行赋值呢?
因为main函数中的局部变量val和函数__main_block_func_0不在同一个作用域中,调用过程中只是进行了值传递。当然,在上面代码中,我们可以通过指针来实现局部变量的修改。不过这是由于在调用__main_block_func_0时,main函数栈还没展开完成,变量val还在栈中。但是在很多情况下,block是作为参数传递以供后续回调执行的。通常在这些情况下,block被执行时,定义时所在的函数栈已经被展开,局部变量已经不在栈中了(block此时在哪里?),再用指针访问就……
所以,对于auto类型的局部变量,不允许block进行修改是合理的。
__block 到底是怎么工作的?
我们把以下代码通过 clang -rewrite-objc 源代码文件名重写:
可得到如下代码:
我们从上述被转化的代码中可以看出 Block 本身也一样被转换成了 __main_block_impl_0 结构体实例,该实例持有__Block_byref_val_0结构体实例的指针。
我们再看一下赋值和执行部分代码被转化后的结果:
由上文描述可知,我们可以使用下述代码解除Block循环引用的问题:
通过执行block方法,nil被赋值到_block变量tmp中。这个时候_block变量对 self 的强引用失效,从而避免循环引用的问题。使用__block变量的优点是:
* 通过__block变量可以控制对象的生命周期
* 在不能使用__weak修饰符的环境中,我们可以避免使用__unsafe_unretained修饰符
* 在执行Block时可动态地决定是否将nil或者其它对象赋值给__block变量
但是这种方法有一个明显的缺点就是,我们必须去执行Block才能够解除循环引用问题,否则就会出现问题。
__block另外一个比较多的使用场景是,为了避免某些情况下Block循环引用的问题,我们也可以给相应对象加上__block 修饰符。
为什么不使用__block就不能在Block内部修改外部的局部变量?
我们把以下代码通过 clang -rewrite-objc 源代码文件名重写:
<pre name="code" class="objc">int main(int argc, const char * argv[]) { @autoreleasepool { int val = 10; void (^block)(void) = ^{ NSLog(@"%d", val); }; block(); } return 0;}得到如下代码:
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int val; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int val = __cself->val; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__val_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_41daf1_mi_0, val);}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(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; int val = 10; void (*block)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0;}
我们注意到Block实质被转换成了一个__main_block_impl_0的结构体实例,其中__main_block_impl_0结构体的成员包括局部变量val。在__main_block_impl_0结构体的构造方法中,val作为第三个参数传递进入。
但执行我们的Block时,通过block找到Block对应的方法执行部分__main_block_func_0,并把当前block作为参数传递到__main_block_func_0方法中。
__main_block_func_0的第一个参数声明如下:
struct __main_block_impl_0 *__cself它和Objective-C的self相同,不过它是指向 __main_block_impl_0 结构体的指针。
这个时候我们就可以通过__cself->val对该变量进行访问。
那么,为什么这个时候不能给val进行赋值呢?
因为main函数中的局部变量val和函数__main_block_func_0不在同一个作用域中,调用过程中只是进行了值传递。当然,在上面代码中,我们可以通过指针来实现局部变量的修改。不过这是由于在调用__main_block_func_0时,main函数栈还没展开完成,变量val还在栈中。但是在很多情况下,block是作为参数传递以供后续回调执行的。通常在这些情况下,block被执行时,定义时所在的函数栈已经被展开,局部变量已经不在栈中了(block此时在哪里?),再用指针访问就……
所以,对于auto类型的局部变量,不允许block进行修改是合理的。
__block 到底是怎么工作的?
我们把以下代码通过 clang -rewrite-objc 源代码文件名重写:
int main(int argc, const char * argv[]) { @autoreleasepool { __block NSInteger val = 0; void (^block)(void) = ^{ val = 1; }; block(); NSLog(@"val = %ld", val); } return 0;}
可得到如下代码:
struct __Block_byref_val_0 { void *__isa; __Block_byref_val_0 *__forwarding; int __flags; int __size; NSInteger val;};struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_val_0 *val; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __main_block_func_0 (struct __main_block_impl_0 *__cself) { __Block_byref_val_0 *val = __cself->val; // bound by ref (val->__forwarding->val) = 1;}static void __main_block_copy_0 (struct __main_block_impl_0*dst, struct __main_block_impl_0*src) { _Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0 (struct __main_block_impl_0*src) { _Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}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_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};int main(int argc, const char * argv[]) { { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 0}; void (*block)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); NSLog((NSString *)&__NSConstantStringImpl__val_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_d7fc4b_mi_0, (val.__forwarding->val)); } return 0;}我们发现由__block修饰的变量变成了一个__Block_byref_val_0结构体类型的实例。该结构体的声明如下:
struct __Block_byref_val_0 { void *__isa; __Block_byref_val_0 *__forwarding; int __flags; int __size; NSInteger val;};注意到这个结构体中包含了该实例本身的引用 __forwarding。
我们从上述被转化的代码中可以看出 Block 本身也一样被转换成了 __main_block_impl_0 结构体实例,该实例持有__Block_byref_val_0结构体实例的指针。
我们再看一下赋值和执行部分代码被转化后的结果:
static void __main_block_func_0 (struct __main_block_impl_0 *__cself) { __Block_byref_val_0 *val = __cself->val; // bound by ref (val->__forwarding->val) = 1;}((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
我们从__cself找到__Block_byref_val_0结构体实例,然后通过该实例的__forwarding访问成员变量val。成员变量val是该实例自身持有的变量,指向的是原来的局部变量。如图所示:
void (^block)(void) = ^{NSLog(@"This is a Global Block");};int main(int argc, const char * argv[]) { @autoreleasepool { block(); } return 0;}该代码转换后的代码中,Block结构体的成员变量isa的初始化如下:
impl.isa = &_NSConcreteGlobalBlock;
impl.isa = &_NSConcreteMallocBlock;
val.__forwarding->val如下面:
static void __main_block_copy_0 (struct __main_block_impl_0*dst, struct __main_block_impl_0*src) { _Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0 (struct __main_block_impl_0*src) { _Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}当Block从栈复制到堆时,会使用_Block_object_assign函数持有该变量(相当于retain)。当堆上的Block被废弃时,会使用_Block_object_dispose函数释放该变量(相当于release)。
由上文描述可知,我们可以使用下述代码解除Block循环引用的问题:
__block id tmp = self;void(^block)(void) = ^{ tmp = nil;};block();
通过执行block方法,nil被赋值到_block变量tmp中。这个时候_block变量对 self 的强引用失效,从而避免循环引用的问题。使用__block变量的优点是:
* 通过__block变量可以控制对象的生命周期
* 在不能使用__weak修饰符的环境中,我们可以避免使用__unsafe_unretained修饰符
* 在执行Block时可动态地决定是否将nil或者其它对象赋值给__block变量
但是这种方法有一个明显的缺点就是,我们必须去执行Block才能够解除循环引用问题,否则就会出现问题。
1 0
- 黑幕背后的__block修饰符
- 黑幕背后的__block修饰符
- 黑幕背后的__block修饰符
- 黑幕背后的__block修饰符
- 黑幕背后的__block修饰符
- 黑幕背后的__block修饰符
- 黑幕背后的__block修饰符
- 黑幕背后的__block修饰符
- 黑幕背后的__block修饰符
- 黑幕背后的__block修饰符
- 黑幕背后的Autorelease
- 黑幕背后的Autorelease
- 黑幕背后的Autorelease
- 黑幕背后的Autorelease
- 黑幕背后的Autorelease
- 黑幕背后的Autorelease
- 黑幕背后的Autorelease
- 黑幕背后的Autorelease
- C++内存管理
- 【LeetCode】Search in Rotated Sorted Array II
- Android——调用百度天气API实现天气预报
- 小米2S稳定版 教你如何一键ROOT
- 织梦仿站系列教程第十一讲——幻灯片的制作
- 黑幕背后的__block修饰符
- playframework 一步一步来 之 日志 (三)
- 浅谈write(byte[] b)和write(byte[] b,int off,int len)的区别
- 如何用VBA做数据透视表
- Hibernate查询方式——QBC查询方式
- 跳过微信屏蔽APP扫描以及识别不同系统的手机
- MFC两个对话框同时显示和GetParent()函数
- 使用POI导出excel
- Kafka与FlumeNG整合实践