深入研究Block实现原理
kublia
8年前
<p>摘要</p> <p>Blocks是C语言的扩充功能, iOS 4中引入了这个新功能“Blocks”,那么block到底是什么东西呢。其实它就是一个闭包,一个带有自动变量(局部变量)的匿名函数。很多语言也实现自己的闭包,比如C#的lamda表达式。这篇文章将从分析源码的角度来分析下block到底是什么鬼。</p> <p>研究工具:clang</p> <p>为了研究编译器的实现原理,我们使用clang(LLVM编译器,和GCC类似),通过命令 clang -rewrite-objc main.m ,解析main.m,这样我们就会得到对应的cpp文件main.cpp,就能看到block内部实现代码(后面有源码),借此可以研究 block 中各个特性的源码实现方式。</p> <h2>一、 block捕获外部变量</h2> <p style="text-align:center"><img src="https://simg.open-open.com/show/c021ab05234559a1e57fb147bfe87fce.png"></p> <p>说到block怎么捕获外部变量,我们要知道c语言中的5种变量:</p> <ul> <li>自动变量</li> <li>函数参数</li> <li>静态变量</li> <li>静态全局变量</li> <li>全局变量</li> </ul> <p>今天主要对除函数参数变量之外的四种变量的捕获情况进行研究</p> <p>根据这四种变量写出测试代码如下:</p> <p><img src="https://simg.open-open.com/show/1ddaf66291c3eede0f753e39914c1f9d.png"></p> <p>测试代码出错,原因是变量d没有加 __block 修饰,由于 __block 稍微复杂,我们后边再讲解,现在我们先对静态变量、静态全局变量、全局变量进行分析,代码:</p> <pre> <code class="language-cpp">#import <Foundation/Foundation.h> int global_a = 1; static int static_global_b = 2; int main(int argc, const char * argv[]) { static int static_c = 3; int d = 4; void(^TestBlock)() = ^{ NSLog(@"block内部:global_a = %d, static_global_b = %d, static_c = %d, d = %d", global_a, static_global_b, static_c, d); }; global_a ++; static_global_b ++; static_c ++; d ++; NSLog(@"block外部:global_a = %d, static_global_b = %d, static_c = %d, d = %d", global_a, static_global_b, static_c, d); TestBlock(); return 0; }</code></pre> <p>运行结果:</p> <pre> <code class="language-cpp">block外部:global_a = 2, static_global_b = 3, static_c = 4, d = 5 block内部:global_a = 2, static_global_b = 3, static_c = 4, d = 4</code></pre> <p>在这里有两个问题:</p> <p>1、为何不加__block就不能修改自动变量的值?</p> <p>2、为何自动变量的值没有增加,其他变量的值增加?自动变量什么情况下才能在block中增加修改?</p> <p>为了弄清楚以上两个疑问,我们用clang转换一下源码分析:</p> <pre> <code class="language-cpp">int global_a = 1; static int static_global_b = 2; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int *static_c; int d; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_c, int _d, int flags=0) : static_c(_static_c), d(_d) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int *static_c = __cself->static_c; // bound by copy int d = __cself->d; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_4acb3a_i_0, global_a, static_global_b, (*static_c), d); } 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[]) { static int static_c = 3; int d = 4; void(*TestBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_c, d)); global_a ++; static_global_b ++; static_c ++; d ++; NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_4acb3a_i_1, global_a, static_global_b, static_c, d); ((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock); return 0; }</code></pre> <p>我们先简单解释下三个概念: __main_block_impl_0 、 __main_block_func_0 、 __main_block_desc_0</p> <ul> <li> <p>__main_block_func_0</p> <p>block内部的实现函数,其中__block_impl中的FuncPtr指向这个函数</p> </li> <li> <p>__main_block_desc_0</p> <p>1、 reserved :保留字段默认为0</p> <p>2、 Block_size :为 sizeof(struct __main_block_impl_0) ,用来表示block所占内存大小。因为没有持有变量,block大小为impl的大小加上Desc指针大小</p> <p>3、 __main_block_desc_0_DATA : __main_block_desc_0 的一个结构体实例</p> <p>这个结构体,用来描述block的大小等信息。如果持有可修改的捕获变量时(即加__block),会增加两个函数(copy和dispose),我们后面会分析</p> </li> <li> <p>main_block_impl_0</p> <p>我们可以看到</p> main_block_impl_0结构体中包含 __block_impl 、 __main_block_desc_0 两个类型的结构体变量,其中 __block_impl 的内部实现源码如下: <pre> <code class="language-cpp">struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; };</code></pre> 1、 isa指针 ,如果我们对runtime了解的话,就明白isa指向Class的指针。<br> 2、 Flags ,当block被copy时,应该执行的操作<br> 3、 Reserved 为保留字段<br> 4、 FuncPtr指针 ,指向block内的函数实现<br> __block_impl 保存block的类型isa(如&_NSConcreteStackBlock),标识(当block发生copy时,会用到),block的方法。<br> 接下来我们看下在main函数中block实现代码: <pre> <code class="language-cpp">void(*TestBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_c, d));</code></pre> 去掉一些类型转换代码: <pre> <code class="language-cpp">void(*TestBlock)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &static_c, d));</code></pre> 调用block时的代码: <pre> <code class="language-cpp">((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);</code></pre> 去掉类型转换之后代码: <pre> <code class="language-cpp">TestBlock->FuncPtr(TestBlock);</code></pre> 可以看到以上代码是调用 __main_block_impl_0 结构体中的构造函数,将变量传入结构体内部保存,之后将这个结构体作为参数传给FuncPtr指向的函数即 __main_block_func_0 , 其中静态变量static_c传入block内部的是地址,自动变量传入的是值,而且在block外部执行 d++ 之前已经将d的值捕获进入block内部, 这也就能说明为何block内部不能改变静态变量的值的原因<br> 最终在block内部实现结果: <pre> <code class="language-cpp">impl.isa = &_NSConcreteStackBlock; impl.Flags = 0; impl.FuncPtr = __main_block_func_0; Desc = &__main_block_desc_0_DATA; static_c = 3; d = 4;</code></pre> </li> </ul> <p>到此,__main_block_impl_0结构体就是这样把自动变量捕获进来的。也就是说,在执行Block语法的时候,Block语法表达式所使用的自动变量的值是被保存进了Block的结构体实例中,也就是Block自身中。</p> <p>这里值得说明的一点是,如果Block外面还有很多自动变量,静态变量,等等,这些变量在Block里面并不会被使用到。那么这些变量并不会被Block捕获进来,也就是说并不会在构造函数里面传入它们的值。</p> <p>Block捕获外部变量仅仅只捕获Block闭包里面会用到的值,其他用不到的值,它并不会去捕获。</p> <p>我们再来看一下__main_block_func_0函数</p> <pre> <code class="language-cpp">static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int *static_c = __cself->static_c; // bound by copy int d = __cself->d; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_4acb3a_i_0, global_a, static_global_b, (*static_c), d); }</code></pre> <p>可以看到,在函数内部通过 __cself->static_c 、 __cself->d 来获取 static_c 和 d 的值,由于结构体中捕获了变量的值,因此 __main_block_impl_0 类型 __cself 能够获取到内部保存的变量值,但是在函数内部只能修改捕获地址值的 static_c 变量,不能修改传入值变量的 d 的值。</p> <p>到此为止,上面提出的二个问题就解开答案了。首先全局变量global_a和静态全局变量static_global_b的值增加,以及它们被Block捕获进去,这一点很好理解,因为是全局的,作用域很广,所以Block捕获了它们进去之后,在Block里面进行++操作,就像局部函数一样,可以成功修改全局变量的值,Block结束之后,它们的值依旧可以得以保存下来。自动变量是以值传递方式传递到Block的构造函数里面去的。Block只捕获Block中会用到的变量。由于只捕获了自动变量的值,并非内存地址,所以Block内部不能改变自动变量的值。Block捕获的外部变量可以改变值的是静态变量,静态全局变量,全局变量。</p> <p>总结一下:在Block中改变变量值有2种方式,一是传递内存地址指针到Block中,二是改变存储区方式(__block)。</p> <p>我们先试一下第一种方式:传递内存地址,代码:</p> <pre> <code class="language-cpp">#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { NSMutableString *str = [NSMutableString stringWithString:@"123"]; void(^TestBlock)() = ^{ [str appendString:@" 456"]; NSLog(@"block中 %@", str); }; NSLog(@"block前 %@", str); TestBlock(); return 0; }</code></pre> <p>控制台输出:</p> <pre> <code class="language-cpp">block前 123 block中 123 456</code></pre> <p>内部实现源码:</p> <pre> <code class="language-cpp">struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; NSMutableString *str; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableString *_str, int flags=0) : str(_str) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSMutableString *str = __cself->str; // bound by copy ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)str, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_1); NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_2, str); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 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[]) { NSMutableString *str = ((NSMutableString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("stringWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_0); void(*TestBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str, 570425344)); NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_3, str); ((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock); return 0; }</code></pre> <p>__main_block_impl_0构造函数</p> <pre> <code class="language-cpp">__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableString *_str, int flags=0) : str</code></pre> <p>可以看出传递的是NSMutableString *类型,即传递的地址,进而可以改变str的值。</p> <p>上边代码中我们可以看到 __main_block_copy_0 和_ _main_block_dispose_0 概念,接下来对这两个概念进行初步讲解.</p> <h2>二、Block的copy和dispose</h2> <p style="text-align:center"><img src="https://simg.open-open.com/show/bf0e1452830d007079ea97e81e381aa1.png"></p> <p>OC中,一般Block就分为以下3种,_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock</p> <p>先来说明一下3者的区别。</p> <p>1.从捕获外部变量的角度上来看</p> <ul> <li> <p>_NSConcreteStackBlock:</p> <p>只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。</p> <p>StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了。</p> </li> <li> <p>_NSConcreteMallocBlock:</p> <p>有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制</p> </li> <li> <p>_NSConcreteGlobalBlock:</p> <p>没有用到外界变量或只用到全局变量、静态变量的block为_NSConcreteGlobalBlock,生命周期从创建到应用程序结束。</p> </li> </ul> <p>没有用到外部变量肯定是_NSConcreteGlobalBlock,这点很好理解。不过只用到全局变量、静态变量的block也是_NSConcreteGlobalBlock。举例如下:</p> <pre> <code class="language-cpp">#import <Foundation/Foundation.h> int global_a = 1; static int static_global_b = 2; int main(int argc, const char * argv[]) { static int static_c = 3; void (^myBlock)(void) = ^{ NSLog(@"Block中 变量 = %d %d %d",static_global_b ,static_c, global_a); }; NSLog(@"%@",myBlock); myBlock(); return 0; }</code></pre> <p>控制台结果:</p> <pre> <code class="language-cpp"><__NSMallocBlock__: 0x100203980> Block中 变量 = 2 3 1</code></pre> <p>可见,只用到全局变量、静态变量的block也可以是_NSConcreteGlobalBlock。</p> <p>所以在ARC环境下,3种类型都可以捕获外部变量。</p> <p>2. 从持有对象的角度上来看:</p> <ul> <li> <p>_NSConcreteStackBlock是不持有对象的</p> <pre> <code class="language-cpp">#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { NSObject *obj = [[NSObject alloc]init]; void (^myBlock)(void) = ^{ NSLog(@"block中 %ld",obj.retainCount); }; NSLog(@"block外 %ld",obj.retainCount); myBlock(); return 0; }</code></pre> <p>输出结果:</p> <pre> <code class="language-cpp">block外 1 block中 1</code></pre> </li> <li> <p>_NSConcreteMallocBlock是持有对象的</p> <pre> <code class="language-cpp">//以下是在MRC下执行的 #import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { NSObject *obj = [[NSObject alloc]init]; NSLog(@"block-1 %ld",obj.retainCount); void (^myBlock)(void) = [^{ NSLog(@"block-3 %ld",obj.retainCount); } copy]; NSLog(@"block-2 %ld",obj.retainCount); myBlock(); return 0; }</code></pre> <p>输出结果:</p> <pre> <code class="language-cpp">block-1 1 block-1 2 block-1 2</code></pre> </li> <li> <p>_NSConcreteGlobalBlock也不持有对象</p> <pre> <code class="language-cpp">#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { void (^myBlock)(void) = ^{ NSObject *obj = [[NSObject alloc]init]; NSLog(@"block %ld",obj.retainCount); }; myBlock(); return 0; }</code></pre> <p>输出结果:</p> <pre> <code class="language-cpp">block 1</code></pre> <p>由于_NSConcreteStackBlock所属的变量域一旦结束,那么该Block就会被销毁。在ARC环境下,编译器会自动的判断,把Block自动的从栈copy到堆。比如当Block作为函数返回值的时候,肯定会copy到堆上。</p> </li> <li>手动调用copy</li> <li>Block是函数的返回值</li> <li>Block被强引用,Block被赋值给__strong或者id类型</li> <li>调用系统API入参中含有usingBlcok的方法</li> </ul> <p>以上4种情况,系统都会默认调用copy方法把Block赋复制</p> <p>但是当Block为函数参数的时候,就需要我们手动的copy一份到堆上了。这里除去系统的API我们不需要管,比如GCD等方法中本身带usingBlock的方法,其他我们自定义的方法传递Block为参数的时候都需要手动copy一份到堆上。</p> <p>copy函数把Block从栈上拷贝到堆上,dispose函数是把堆上的函数在废弃的时候销毁掉。</p> <pre> <code class="language-cpp">#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__))) #define Block_release(...) _Block_release((const void *)(__VA_ARGS__)) // Create a heap based copy of a Block or simply add a reference to an existing one. // This must be paired with Block_release to recover memory, even when running // under Objective-C Garbage Collection. BLOCK_EXPORT void *_Block_copy(const void *aBlock) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2); // Lose the reference, and if heap based and last reference, recover the memory BLOCK_EXPORT void _Block_release(const void *aBlock) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2); // Used by the compiler. Do not call this function yourself. BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2); // Used by the compiler. Do not call this function yourself. BLOCK_EXPORT void _Block_object_dispose(const void *, const int) __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);</code></pre> <p>上面是源码中2个常用的宏定义和4个常用的方法,一会我们就会看到这4个方法。</p> <pre> <code class="language-cpp">static void *_Block_copy_internal(const void *arg, const int flags) { struct Block_layout *aBlock; const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE; if (!arg) return NULL; aBlock = (struct Block_layout *)arg; if (aBlock->flags & BLOCK_NEEDS_FREE) { latching_incr_int(&aBlock->flags); return aBlock; } else if (aBlock->flags & BLOCK_IS_GLOBAL) { return aBlock; } struct Block_layout *result = malloc(aBlock->descriptor->size); if (!result) return (void *)0; memmove(result, aBlock, aBlock->descriptor->size); result->flags &= ~(BLOCK_REFCOUNT_MASK); result->flags |= BLOCK_NEEDS_FREE | 1; result->isa = _NSConcreteMallocBlock; if (result->flags & BLOCK_HAS_COPY_DISPOSE) { (*aBlock->descriptor->copy)(result, aBlock); } return result; }</code></pre> <p>上面这一段是Block_copy的一个实现,实现了从_NSConcreteStackBlock复制到_NSConcreteMallocBlock的过程.</p> <pre> <code class="language-cpp">void _Block_release(void *arg) { struct Block_layout *aBlock = (struct Block_layout *)arg; if (!aBlock) return; int32_t newCount; newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK; if (newCount > 0) return; if (aBlock->flags & BLOCK_NEEDS_FREE) { if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock); _Block_deallocator(aBlock); } else if (aBlock->flags & BLOCK_IS_GLOBAL) { ; } else { printf("Block_release called upon a stack Block: %p, ignored\\\\n", (void *)aBlock); } }</code></pre> <p>上面这一段是Block_release的一个实现,实现了怎么释放一个Block。</p> <p>因为在C语言的结构体中,编译器没法很好的进行初始化和销毁操作。这样对内存管理来说是很不方便的。所以就在 __main_block_desc_0 结构体中间增加成员变量 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*) 和 void (*dispose)(struct __main_block_impl_0*) ,利用OC的Runtime进行内存管理。</p> <p>相应的增加了2个方法。</p> <pre> <code class="language-cpp">static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}</code></pre> <p>这里的 _Block_object_assign 和 _Block_object_dispose 就对应着retain和release方法。</p> <h2>三.Block中__block实现原理</h2> <p style="text-align:center"><img src="https://simg.open-open.com/show/b1bcdefb58c73bafb3d7a7fc4efdfe65.png"></p> <p>1.普通非对象的变量</p> <p>先来看看普通变量的情况</p> <pre> <code class="language-cpp">#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { __block int a = 0; void (^myBlock)(void) = ^{ a ++; }; myBlock(); return 0; }</code></pre> <p>转化后的代码:</p> <pre> <code class="language-cpp">struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_a_0 *a; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref (a->__forwarding->a) ++; } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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[]) { __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0}; void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)); ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock); return 0; }</code></pre> <p>从以上代码看出</p> <ul> <li>被__block修饰的变量,内部实现多了一个 __Block_byref_a_0 类型的结构体,这个结构体有5个成员变量。第一个是 isa指针 ,第二个是指向自身类型的 __forwarding 指针,第三个是一个标记 flag ,第四个是它的大小,第五个是变量值。</li> <li>被__block修饰的变量a转化成 __Block_byref_a_0 类型的变量a</li> <li>将 __Block_byref_a_0 类型的变量a的地址传入 __main_block_impl_0 内部的 __Block_byref_a_0 修饰的变量a</li> <li>在 __main_block_func_0 函数中通过 __cself->a 取到 __Block_byref_a_0 结构体变量,在通过 (a->__forwarding->a) ++ 实现被__block修饰的变量a的值加一</li> </ul> <p>ARC环境下,一旦Block赋值就会触发copy, <strong>block就会copy到堆上,Block也是</strong> NSMallocBlock。ARC环境下也是存在 <strong>NSStackBlock的时候,这种情况下,</strong> block就在栈上。</p> <p>MRC环境下,只有copy, <strong>block才会被复制到堆上,否则,</strong> block一直都在栈上,block也只是 <strong>NSStackBlock,这个时候</strong> forwarding指针就只指向自己了。</p> <p>2.对象的变量</p> <pre> <code class="language-cpp">//ARC环境下 #import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { __block NSObject *block_obj = [[NSObject alloc] init]; NSObject *obj = [[NSObject alloc] init]; NSLog(@"block外 block_obj:%p; obj:%p", █_obj, &obj); void (^myBlock)(void) = ^{ NSLog(@"block中 block_obj:%p; obj:%p", █_obj, &obj); }; myBlock(); NSLog(@"%@", myBlock); return 0; }</code></pre> <p>结果输出:</p> <pre> <code class="language-cpp">block外 block_obj:0x7fff5fbff758; obj:0x7fff5fbff728 block中 block_obj:0x100300578; obj:0x1003001e0 <__NSMallocBlock__: 0x1002004d0></code></pre> <p>以上代码转换后:</p> <pre> <code class="language-cpp">struct __Block_byref_block_obj_0 { void *__isa; __Block_byref_block_obj_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); NSObject *block_obj; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; NSObject *obj; __Block_byref_block_obj_0 *block_obj; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, __Block_byref_block_obj_0 *_block_obj, int flags=0) : obj(_obj), block_obj(_block_obj->__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_block_obj_0 *block_obj = __cself->block_obj; // bound by ref NSObject *obj = __cself->obj; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_0486e2_mi_1, &(block_obj->__forwarding->block_obj), &obj); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_obj, (void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj, 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[]) { __attribute__((__blocks__(byref))) __Block_byref_block_obj_0 block_obj = {(void*)0,(__Block_byref_block_obj_0 *)█_obj, 33554432, sizeof(__Block_byref_block_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))}; NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")); NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_0486e2_mi_0, &(block_obj.__forwarding->block_obj), &obj); void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, (__Block_byref_block_obj_0 *)█_obj, 570425344)); ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock); return 0; }</code></pre> <p>首先需要说明的一点是对象在OC中,默认声明自带__strong所有权修饰符的,所以main开头我们声明的</p> <pre> <code class="language-cpp">__block NSObject *block_obj = [[NSObject alloc] init]; NSObject *obj = [[NSObject alloc] init];</code></pre> <p>等价于:</p> <pre> <code class="language-cpp">__block __strong NSObject *block_obj = [[NSObject alloc] init]; __strong NSObject *obj = [[NSObject alloc] init];</code></pre> <p>从以上转化代码看出:</p> <ul> <li>被__block 修饰的变量block_obj转化成 __Block_byref_block_obj_0 的变量,名称相同</li> <li>__main_block_impl_0 构造方法中将 __Block_byref_block_obj_0 类型的 block_obj 变量的地址传入给结构体的 __Block_byref_block_obj_0 类型 block_obj`变量,将变量obj的地址传入给结构体的变量obj</li> <li>在 __main_block_func_0 函数中通过 __cself->block_obj 获取 __Block_byref_block_obj_0 类型变量 block_obj ,再通过 block_obj->__forwarding->block_obj 获取外部捕获的变量;通过 __cself->obj 获取obj变量</li> <li>Block捕获了 <strong>block,并且强引用了,因为在</strong> Block_byref_block_obj_0结构体中,有一个变量是id block_obj,这个默认也是带__strong所有权修饰符的</li> <li>ARC环境下,Block捕获外部对象变量,是都会copy一份的,地址都不同。只不过带有__block修饰符的变量会被捕获到Block内部持有</li> </ul> <p>我们再来看看MRC环境下的情况,还是将上述代码的例子运行在MRC中:</p> <pre> <code class="language-cpp">block外 block_obj:0x7fff5fbff758; obj:0x7fff5fbff728 block中 block_obj:0x7fff5fbff758; obj:0x7fff5fbff700 <__NSStackBlock__: 0x7fff5fbff6e0></code></pre> <p>这个时候block在栈上, <strong>NSStackBlock</strong> ,可以打印出来retainCount值都是1。当把这个block copy一下,就变成 <strong>NSMallocBlock</strong> ,对象的retainCount值就会变成2了。</p> <h3>总结:</h3> <p>在MRC环境下,</p> <p>block根本不会对指针所指向的对象执行copy操作,而只是把指针进行的复制。</p> <p>而在ARC环境下,对于声明为</p> <p>block的外部对象,在block内部会进行retain,以至于在block环境内能安全的引用外部对象,所以才会产生循环引用的问题!</p> <ul> <li>对于非对象的变量<br> 自动变量的值,被copy进了Block,不带 <strong>block的自动变量只能在里面被访问,并不能改变值;带</strong> block的自动变量 和 静态变量 就是直接地址访问。所以在Block里面可以直接改变变量的值</li> <li>静态全局变量,全局变量,函数参数<br> 可以在直接在Block中改变变量值的,但是他们并没有变成Block结构体 __main_block_impl_0 的成员变量,因为他们的作用域大,所以可以直接更改他们的值<br> 值得注意的是,静态全局变量,全局变量,函数参数他们并不会被Block持有,也就是说不会增加retainCount值</li> <li>对于对象<br> 在MRC环境下, <p>block根本不会对指针所指向的对象执行copy操作,而只是把指针进行的复制。</p> <p>而在ARC环境下,对于声明为</p> block的外部对象,在block内部会进行retain,以至于在block环境内能安全的引用外部对象。</li> </ul> <h3>最后</h3> <p>在ARC环境下,Block也是存在 __NSStackBlock 的时候的,平时见到最多的是 _NSConcreteMallocBlock ,是因为我们会对Block有赋值操作,所以ARC下,block 类型通过=进行传递时,会导致调用 objc_retainBlock->_Block_copy->_Block_copy_internal 方法链。并导致 __NSStackBlock__ 类型的 block 转换为 __NSMallocBlock__ 类型。</p> <pre> <code class="language-cpp">#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { __block int temp = 10; NSLog(@"%@",^{NSLog(@"*******%d %p",temp ++,&temp);}); return 0; }</code></pre> <pre> <code class="language-cpp"><__NSStackBlock__: 0x7fff5fbff768></code></pre> <h3>参考:</h3> <p><a href="/misc/goto?guid=4959730853746028998" rel="nofollow,noindex">http://www.jianshu.com/p/ca6ac0ae93ad</a></p> <p><a href="/misc/goto?guid=4959730853850214545" rel="nofollow,noindex">http://www.jianshu.com/p/ee9756f3d5f6</a></p> <p> </p> <p>来自:http://www.jianshu.com/p/710026d5bcfb</p> <p> </p>