本篇内容介绍了“Objective C中Block怎么捕获外部值”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
Block
本质上也是一个 Objective-C
对象,它内部也有个 isa
指针。Block
是封装了函数调用以及函数调用环境的 Objective-C
对象。Block
的底层结构如下图所示:
Block
对于不同类型的值会有不同的捕获方式,本文将通过代码展示其对于各种场景下的外部值是如何进行捕获的。
首先展示源代码:
int main(int argc, const char * argv[]) { @autoreleasepool { NSInteger value = 0; void(^block)(void) = ^{ NSLog(@"%zd", value); }; block(); } return 0; }
经过 clang -rewrite-objc
之后,得到的代码如下,可以看到,对于自动变量的捕获,是会在 Block
结构体中生成一个对应类型的成员变量来实现捕获的能力,这也解释了为什么在 Block
中修改捕获的值的内容,无法对 Block
外的值产生影响。
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; NSInteger value; // 捕获的 NSInteger value __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSInteger _value, int flags=0) : value(_value) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSInteger value = __cself->value; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_zyxvpxvq6csfxvn_n0000000000000_T_main_e3ca95_mi_0, value); } 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; NSInteger value = 0; void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, value)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
对于静态变量、静态全局变量与全局变量的捕获,会稍有不同,其中:
全局变量与静态全局变量:直接使用,因为地址一直是可以直接获取的。
静态变量:捕获地址使用,因为 block
有可能会传递出创建时的作用域。
NSInteger globalValue = 1; static NSInteger staticGlobalValue = 2; int main(int argc, const char * argv[]) { @autoreleasepool { static NSInteger staticValue = 3; void(^block)(void) = ^{ globalValue += 1; staticGlobalValue += 2; staticValue += 3; }; block(); } return 0; }
NSInteger globalValue = 1; static NSInteger staticGlobalValue = 2; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; NSInteger *staticValue; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSInteger *_staticValue, int flags=0) : staticValue(_staticValue) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSInteger *staticValue = __cself->staticValue; // bound by copy globalValue += 1; staticGlobalValue += 2; (*staticValue) += 3; } 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; static NSInteger staticValue = 3; void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &staticValue)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
被 __block
修饰的自动变量,可以在 Block
内部对其外部的值进行修改:
int main(int argc, const char * argv[]) { @autoreleasepool { __block NSInteger value = 0; void(^block)(void) = ^{ value = 10; }; block(); NSLog(@"%zd", value); } return 0; }
这次生成的代码复杂了一些,不过只关注 value
部分的话可以发现,Block
为了捕获 __block
类型的自动变量,会生成 __Block_byref_value_0
结构体,并通过该结构体来实现对外部 __block
自动变量的捕获。
struct __Block_byref_value_0 { // 为捕获 __block 的自动变量,生成的结构体。为的是方便多个 Block 同时捕获一个自动变量时使用。 void *__isa; // isa 指针 __Block_byref_value_0 *__forwarding; // 在 Block 单纯在栈上是,指向的是自己,拷贝到堆上后,指向的是在堆上的 Block。之所以需要这样的指针是因为当 Block 拷贝到堆上时,调用方式是统一的。 int __flags; int __size; NSInteger value; // 具体的值 }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_value_0 *value; // 通过引用的方式捕获 value,其中变量类型为 __Block_byref_value_0 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_value_0 *_value, int flags=0) : value(_value->__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_value_0 *value = __cself->value; // bound by ref (value->__forwarding->value) = 10; // 赋值代码 } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->value, (void*)src->value, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->value, 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[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_value_0 value = {(void*)0,(__Block_byref_value_0 *)&value, 0, sizeof(__Block_byref_value_0), 0}; void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_value_0 *)&value, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_zyxvpxvq6csfxvn_n0000000000000_T_main_6bf1c6_mi_0, (value.__forwarding->value)); } return 0; }
__block
可以用于解决 block
内部无法修改 auto
变量值的问题,__block
不能修饰全局变量、静态变量(static
),编译器会将 __block
变量包装成一个对象。
当 block
在栈上时,并不会对 __block
变量产生强引用。
当 block
被 copy
到堆时,会调用 block
内部的 copy
函数,copy
函数内部会调用 _Block_object_assign
函数,_Block_object_assign
函数会对 __block
变量形成强引用(retain
)。
当 block
从堆中移除时,会调用 block
内部的 dispose
函数,dispose
函数内部会调用 _Block_object_dispose
函数,_Block_object_dispose
函数会自动释放引用的 __block
变量(release
)。
在探究完对标量类型的捕获之后,让我们看一下对对象类型的捕获:
int main(int argc, const char * argv[]) { @autoreleasepool { NSArray *array = [NSArray array]; void(^block)(void) = ^{ NSLog(@"%@", array); }; block(); } return 0; }
通过转译的代码可以看出,因为对象类型本身已经是存储在堆上的值了,所以直接获取其地址即可,同时其新增了两个函数 __main_block_copy_0
和 __main_block_dispose_0
,这两个函数是用来将对象拷贝到堆上和被从堆上移除时调用的,其内部又分别调用了 _Block_object_assign
和 _Block_object_dispose
用来对捕获的对象进行引用计数的增加和减少。
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; NSArray *array; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSArray *_array, int flags=0) : array(_array) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSArray *array = __cself->array; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_zyxvpxvq6csfxvn_n0000000000000_T_main_8ba4f7_mi_0, array); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);} 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[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; NSArray *array = ((NSArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array")); void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
Block
对象本身分为三种类型:
NSGlobalBlock:没有访问 auto
变量,调用 copy
方法之后不会发生变化。
NSStackBlock:访问了 auto
变量,调用 copy
方法之后存储位置从栈变为堆。
NSMallocBlock:__NSStackBlock__
调用了 copy
方法之后,引用计数增加。
在 ARC
环境下,编译器会根据情况自动将栈上的 block
复制到堆上,比如以下情况:
Block
作为函数返回值时
将 Block
赋值给 __strong
指针时
Block
作为 Cocoa API
中方法名含有 usingBlock
的方法参数时
Block
作为 GCD API
的方法参数时
所以,当 Block
内部访问了对象类型的 auto
变量时。如果 Block
是在栈上,将不会对 auto
变量产生强引用。
如果 Block
被拷贝到堆上,会调用 Block
内部的 copy
函数,copy
函数内部会调用 _Block_object_assign
函数,_Block_object_assign
函数会根据 auto
变量的修饰符(__strong
、__weak
、__unsafe_unretained
)做出相应的操作,形成强引用或者弱引用。
如果 Block
从堆上移除,会调用 Block
内部的 dispose
函数,dispose
函数内部会调用 _Block_object_dispose
函数。_Block_object_dispose
函数会自动释放引用的 auto
变量(release
)。
如果想在 Block
中,对捕获的对象的指针指向进行修改,则需要添加 __block
关键字:
int main(int argc, const char * argv[]) { @autoreleasepool { __block NSArray *array = [NSArray array]; void(^block)(void) = ^{ array = [NSArray array]; NSLog(@"%@", array); }; block(); } return 0; }
通过转译我们可以看出,跟 __block
修饰的标量类型相似,同样会生成 __Block_byref_array_0
结构体来捕获对象类型。同时其内部生成了 __Block_byref_id_object_copy
和 __Block_byref_id_object_dispose
两个函数指针,用于对被结构体包装的对象进行内存管理。
static void __Block_byref_id_object_copy_131(void *dst, void *src) { _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131); } static void __Block_byref_id_object_dispose_131(void *src) { _Block_object_dispose(*(void * *) ((char*)src + 40), 131); } struct __Block_byref_array_0 { void *__isa; __Block_byref_array_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); NSArray *array; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_array_0 *array; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_array_0 *_array, int flags=0) : array(_array->__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_array_0 *array = __cself->array; // bound by ref (array->__forwarding->array) = ((NSArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array")); NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_zyxvpxvq6csfxvn_n0000000000000_T_main_3593f0_mi_0, (array->__forwarding->array)); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 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[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; __attribute__((__blocks__(byref))) __Block_byref_array_0 array = {(void*)0,(__Block_byref_array_0 *)&array, 33554432, sizeof(__Block_byref_array_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array"))}; void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_array_0 *)&array, 570425344)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } return 0; }
当 block
在栈上时,对它们都不会产生强引用。
当 block
拷贝到堆上时,都会通过 copy
函数来处理它们,__block
变量(假设变量名叫做 a
):
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
对象类型的 auto
变量(假设变量名叫做 p
):
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
当 block
从堆上移除时,都会通过 dispose
函数来释放它们,__block
变量(假设变量名叫做 a
):
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
对象类型的 auto
变量(假设变量名叫做 p
):
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
“Objective C中Block怎么捕获外部值”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注亿速云网站,小编将为大家输出更多高质量的实用文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。