这里有关于block的5道测试题,建议你阅读本文以前先作一下测试。html
先介绍一下什么是闭包。在wikipedia上,闭包的定义)是:ios
In programming languages, a closure is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.objective-c
翻译过来,闭包是一个函数(或指向函数的指针),再加上该函数执行的外部的上下文变量(有时候也称做自由变量)。编程
block实际上就是Objective-C语言对于闭包的实现。 block配合上dispatch_queue,能够方便地实现简单的多线程编程和异步编程,关于这个,我以前写过一篇文章介绍:《使用GCD》。数据结构
本文主要介绍Objective-C语言的block在编译器中的实现方式。主要包括:多线程
block的数据结构定义以下(图片来自这里):闭包
对应的结构体定义以下:app
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct Block_descriptor { unsigned long int reserved; unsigned long int size; void (*copy)(void *dst, void *src); void (*dispose)(void *); }; struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; /* Imported variables. */ }; |
经过该图,咱们能够知道,一个block实例实际上由6部分构成:异步
该数据结构和后面的clang分析出来的结构实际是同样的,不过仅是结构体的嵌套方式不同。但这一点我一开始没有想明白,因此也给你们解释一下,以下2个结构体SampleA和SampleB在内存上是彻底同样的,缘由是结构体自己并不带有任何额外的附加信息。ide
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct SampleA { int a; int b; int c; }; struct SampleB { int a; struct Part1 { int b; }; struct Part2 { int c; }; }; |
在Objective-C语言中,一共有3种类型的block:
咱们在下面会分别来查看它们各自的实现方式上的差异。
为了研究编译器是如何实现block的,咱们须要使用clang。clang提供一个命令,能够将Objetive-C的源码改写成c语言的,借此能够研究block具体的源码实现方式。该命令是
1
|
clang -rewrite-objc block.c |
咱们先新建一个名为block1.c的源文件:
1 2 3 4 5 6 7 |
#include <stdio.h> int main() { ^{ printf("Hello, World!\n"); } (); return 0; } |
而后在命令行中输入clang -rewrite-objc block1.c
便可在目录中看到clang输出了一个名为block1.cpp的文件。该文件就是block在c语言实现,我将block1.cpp中一些无关的代码去掉,将关键代码引用以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("Hello, World!\n"); } 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() { (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA) (); return 0; } |
下面咱们就具体看一下是如何实现的。__main_block_impl_0就是该block的实现,从中咱们能够看出:
咱们另外新建一个名为block2.c的文件,输入如下内容:
1 2 3 4 5 6 7 8 9 10 11 |
#include <stdio.h> int main() { int a = 100; void (^block2)(void) = ^{ printf("%d\n", a); }; block2(); return 0; } |
用以前提到的clang工具,转换后的关键代码以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int a; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a = __cself->a; // bound by copy printf("%d\n", a); } 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 a = 100; void (*block2)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a); ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2); return 0; } |
在本例中,咱们能够看到:
咱们修改上面的源码,在变量前面增长__block关键字:
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <stdio.h> int main() { __block int i = 1024; void (^block1)(void) = ^{ printf("%d\n", i); i = 1023; }; block1(); return 0; } |
生成的关键代码以下,能够看到,差别至关大:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
struct __Block_byref_i_0 { void *__isa; __Block_byref_i_0 *__forwarding; int __flags; int __size; int i; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_i_0 *i; // by ref __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref printf("%d\n", (i->__forwarding->i)); (i->__forwarding->i) = 1023; } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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() { __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024}; void (*block1)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344); ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1); return 0; } |
从代码中咱们能够看到:
NSConcreteMallocBlock类型的block一般不会在源码中直接出现,由于默认它是当一个block被copy的时候,才会将这个block复制到堆中。如下是一个block被copy时的示例代码(来自这里),能够看到,在第8步,目标的block类型被修改成_NSConcreteMallocBlock。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
static void *_Block_copy_internal(const void *arg, const int flags) { struct Block_layout *aBlock; const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE; // 1 if (!arg) return NULL; // 2 aBlock = (struct Block_layout *)arg; // 3 if (aBlock->flags & BLOCK_NEEDS_FREE) { // latches on high latching_incr_int(&aBlock->flags); return aBlock; } // 4 else if (aBlock->flags & BLOCK_IS_GLOBAL) { return aBlock; } // 5 struct Block_layout *result = malloc(aBlock->descriptor->size); if (!result) return (void *)0; // 6 memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first // 7 result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed result->flags |= BLOCK_NEEDS_FREE | 1; // 8 result->isa = _NSConcreteMallocBlock; // 9 if (result->flags & BLOCK_HAS_COPY_DISPOSE) { (*aBlock->descriptor->copy)(result, aBlock); // do fixup } return result; } |
对于block外的变量引用,block默认是将其复制到其数据结构中来实现访问的,以下图所示(图片来自这里):
对于用__block修饰的外部变量引用,block是复制其引用地址来实现访问的,以下图所示(图片来自这里):
在LLVM开源的关于block的实现源码,其内容也和咱们用clang改写获得的内容类似,印证了咱们对于block内部数据结构的推测。
在ARC开启的状况下,将只会有 NSConcreteGlobalBlock和 NSConcreteMallocBlock类型的block。
本来的NSConcreteStackBlock的block会被NSConcreteMallocBlock类型的block替代。证实方式是如下代码在XCode中,会输出<__NSMallocBlock__: 0x100109960>
。在苹果的官方文档中也提到,当把栈中的block返回时,不须要调用copy方法了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { int i = 1024; void (^block1)(void) = ^{ printf("%d\n", i); }; block1(); NSLog(@"%@", block1); } return 0; } |
我我的认为这么作的缘由是,因为ARC已经能很好地处理对象的生命周期的管理,这样全部对象都放到堆上管理,对于编译器实现来讲,会比较方便。
但愿本文能加深你对于block的理解。我在学习中,查阅了如下文章,一并分享给你们。祝你们玩得开心~