详解Objective C 中Block如何捕获外部值
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>引言</li><li>自动变量</li><li>静态变量、静态全局变量与全局变量</li><li>带 __block 的自动变量</li><li>捕获对象</li><li>__block 对象类型的捕获</li></ul></div><p class="maodian"></p><h2>引言</h2><p><code>Block</code> 本质上也是一个 <code>Objective-C</code> 对象,它内部也有个 <code>isa</code>指针。<code>Block</code> 是封装了函数调用以及函数调用环境的 <code>Objective-C</code> 对象。<code>Block</code> 的底层结构如下图所示:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202209/2022090508393908.png" /></p>
<p><code>Block</code> 对于不同类型的值会有不同的捕获方式,本文将通过代码展示其对于各种场景下的外部值是如何进行捕获的。</p>
<p class="maodian"></p><h2>自动变量</h2>
<p>首先展示源代码:</p>
<div class="jb51code"><pre class="brush:cpp;">int main(int argc, const char * argv[]) {
@autoreleasepool {
NSInteger value = 0;
void(^block)(void) = ^{
NSLog(@"%zd", value);
};
block();
}
return 0;
}
</pre></div>
<p>经过 <code>clang -rewrite-objc</code> 之后,得到的代码如下,可以看到,对于自动变量的捕获,是会在 <code>Block</code> 结构体中生成一个对应类型的成员变量来实现捕获的能力,这也解释了为什么在 <code>Block</code> 中修改捕获的值的内容,无法对 <code>Block</code> 外的值产生影响。</p>
<div class="jb51code"><pre class="brush:cpp;">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;
}
</pre></div>
<p class="maodian"></p><h2>静态变量、静态全局变量与全局变量</h2>
<p>对于静态变量、静态全局变量与全局变量的捕获,会稍有不同,其中:</p>
<ul><li>全局变量与静态全局变量:直接使用,因为地址一直是可以直接获取的。</li><li>静态变量:捕获地址使用,因为 <code>block</code> 有可能会传递出创建时的作用域。</li></ul>
<div class="jb51code"><pre class="brush:cpp;">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;
}
</pre></div>
<div class="jb51code"><pre class="brush:cpp;">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;
}
</pre></div>
<p class="maodian"></p><h2>带 __block 的自动变量</h2>
<p>被 <code>__block</code> 修饰的自动变量,可以在 <code>Block</code> 内部对其外部的值进行修改:</p>
<div class="jb51code"><pre class="brush:cpp;">int main(int argc, const char * argv[]) {
@autoreleasepool {
__block NSInteger value = 0;
void(^block)(void) = ^{
value = 10;
};
block();
NSLog(@"%zd", value);
}
return 0;
}
</pre></div>
<p>这次生成的代码复杂了一些,不过只关注 <code>value</code> 部分的话可以发现,<code>Block</code> 为了捕获 <code>__block</code> 类型的自动变量,会生成 <code>__Block_byref_value_0</code> 结构体,并通过该结构体来实现对外部 <code>__block</code> 自动变量的捕获。</p>
<div class="jb51code"><pre class="brush:cpp;">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;
}
</pre></div>
<p><code>__block</code> 可以用于解决 <code>block</code> 内部无法修改 <code>auto</code> 变量值的问题,<code>__block</code> 不能修饰全局变量、静态变量(<code>static</code>),编译器会将 <code>__block</code> 变量包装成一个对象。</p>
<p>当 <code>block</code> 在栈上时,并不会对 <code>__block</code> 变量产生强引用。</p>
<p>当 <code>block</code> 被 <code>copy</code> 到堆时,会调用 <code>block</code> 内部的 <code>copy</code> 函数,<code>copy</code> 函数内部会调用 <code>_Block_object_assign</code> 函数,<code>_Block_object_assign</code> 函数会对 <code>__block</code> 变量形成强引用(<code>retain</code>)。</p>
<p>当 <code>block</code> 从堆中移除时,会调用 <code>block</code> 内部的 <code>dispose</code> 函数,<code>dispose</code> 函数内部会调用 <code>_Block_object_dispose</code> 函数,<code>_Block_object_dispose</code> 函数会自动释放引用的 <code>__block</code> 变量(<code>release</code>)。</p>
<p class="maodian"></p><h2>捕获对象</h2>
<p>在探究完对标量类型的捕获之后,让我们看一下对对象类型的捕获:</p>
<div class="jb51code"><pre class="brush:cpp;">int main(int argc, const char * argv[]) {
@autoreleasepool {
NSArray *array = ;
void(^block)(void) = ^{
NSLog(@"%@", array);
};
block();
}
return 0;
}
</pre></div>
<p>通过转译的代码可以看出,因为对象类型本身已经是存储在堆上的值了,所以直接获取其地址即可,同时其新增了两个函数 <code>__main_block_copy_0</code> 和 <code>__main_block_dispose_0</code>,这两个函数是用来将对象拷贝到堆上和被从堆上移除时调用的,其内部又分别调用了 <code>_Block_object_assign</code> 和 <code>_Block_object_dispose</code> 用来对捕获的对象进行引用计数的增加和减少。</p>
<div class="jb51code"><pre class="brush:cpp;">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;
}
</pre></div>
<p><code>Block</code> 对象本身分为三种类型:</p>
<ul><li><strong>NSGlobalBlock</strong>:没有访问 <code>auto</code> 变量,调用 <code>copy</code> 方法之后不会发生变化。</li><li><strong>NSStackBlock</strong>:访问了 <code>auto</code> 变量,调用 <code>copy</code> 方法之后存储位置从栈变为堆。</li><li><strong>NSMallocBlock</strong>:<code>__NSStackBlock__</code> 调用了 <code>copy</code> 方法之后,引用计数增加。</li></ul>
<p>在 <code>ARC</code> 环境下,编译器会根据情况自动将栈上的 <code>block</code> 复制到堆上,比如以下情况:</p>
<ul><li><code>Block</code> 作为函数返回值时</li><li>将 <code>Block</code> 赋值给 <code>__strong</code> 指针时</li><li><code>Block</code> 作为 <code>Cocoa API</code> 中方法名含有 <code>usingBlock</code> 的方法参数时</li><li><code>Block</code> 作为 <code>GCD API</code> 的方法参数时</li></ul>
<p>所以,当 <code>Block</code> 内部访问了对象类型的 <code>auto</code> 变量时。如果 <code>Block</code> 是在栈上,将不会对 <code>auto</code> 变量产生强引用。</p>
<p>如果 <code>Block</code> 被拷贝到堆上,会调用 <code>Block</code> 内部的 <code>copy</code> 函数,<code>copy</code> 函数内部会调用 <code>_Block_object_assign</code> 函数,<code>_Block_object_assign</code> 函数会根据 <code>auto</code> 变量的修饰符(<code>__strong</code>、<code>__weak</code>、<code>__unsafe_unretained</code>)做出相应的操作,形成强引用或者弱引用。</p>
<p>如果 <code>Block</code> 从堆上移除,会调用 <code>Block</code> 内部的 <code>dispose</code> 函数,<code>dispose</code> 函数内部会调用 <code>_Block_object_dispose</code> 函数。<code>_Block_object_dispose</code> 函数会自动释放引用的 <code>auto</code> 变量(<code>release</code>)。</p>
<p class="maodian"></p><h2>__block 对象类型的捕获</h2>
<p>如果想在 <code>Block</code> 中,对捕获的对象的指针指向进行修改,则需要添加 <code>__block</code> 关键字:</p>
<div class="jb51code"><pre class="brush:cpp;">int main(int argc, const char * argv[]) {
@autoreleasepool {
__block NSArray *array = ;
void(^block)(void) = ^{
array = ;
NSLog(@"%@", array);
};
block();
}
return 0;
}
</pre></div>
<p>通过转译我们可以看出,跟 <code>__block</code> 修饰的标量类型相似,同样会生成 <code>__Block_byref_array_0</code> 结构体来捕获对象类型。同时其内部生成了 <code>__Block_byref_id_object_copy</code> 和 <code>__Block_byref_id_object_dispose</code> 两个函数指针,用于对被结构体包装的对象进行内存管理。</p>
<div class="jb51code"><pre class="brush:cpp;">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;
}
</pre></div>
<p>当 <code>block</code> 在栈上时,对它们都不会产生强引用。</p>
<p>当 <code>block</code> 拷贝到堆上时,都会通过 <code>copy</code> 函数来处理它们,<code>__block</code> 变量(假设变量名叫做 <code>a</code>):</p>
<div class="jb51code"><pre class="brush:cpp;">_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
</pre></div>
<p>对象类型的 <code>auto</code> 变量(假设变量名叫做 <code>p</code>):</p>
<div class="jb51code"><pre class="brush:cpp;">_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
</pre></div>
<p>当 <code>block</code> 从堆上移除时,都会通过 <code>dispose</code> 函数来释放它们,<code>__block</code> 变量(假设变量名叫做 <code>a</code>):</p>
<div class="jb51code"><pre class="brush:cpp;">_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
</pre></div>
<p>对象类型的 <code>auto</code> 变量(假设变量名叫做 <code>p</code>):</p>
<div class="jb51code"><pre class="brush:cpp;">_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);</pre></div>
<p>以上就是详解Objective C 中Block如何捕获外部值的详细内容,更多关于Objective C Block捕获外部值的资料请关注琼殿技术社区其它相关文章!</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>Objective-C中block循环引用问题详解</li><li>Objective-C中的block与Swift中的尾随闭包使用教程</li><li>全面解析Objective-C中的block代码块的使用</li><li>iOS block的值捕获与指针捕获详解</li><li>iOS中block变量捕获原理详析</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]