引言
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 修饰的自动变量,可以在 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 中,对捕获的对象的指针指向进行修改,则需要添加 __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如何捕获外部值的详细内容,更多关于objective c block捕获外部值的资料请关注代码网其它相关文章!
发表评论