在main.m的main函数中声明一个block并执行block()
经过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
命令将main.m转为main.cpp。xcode
在main.cpp中能够发现上图中的代码。其中在main函数中的block和block(),被转化为bash
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
复制代码
其中不乏强制转换类型的代码,将强制类型转换的代码去掉咱们能够明确的看出:iphone
void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
block->FuncPtr(block);
复制代码
block存储了__main_block_impl_0
函数的地址,而此函数中传递的第一个值是一个函数指针,函数的做用就是block中存储的代码的做用,即打印hello world
。第二个参数是一个结构体指针,其中包含有对block的描述。函数
而__main_block_impl_0
是一个结构体,__main_block_impl_0
函数是结构体__main_block_impl_0
的构造函数,在此结构中就能够看到这个与结构体名字相同的没有返回值的函数。post
该函数将类型地址赋值给isa,将存放block中代码的函数的指针赋值给funcptr,将block的描述赋值给desc。spa
__main_block_impl_0
结构体中第一个成员就是: 3d
__main_block_impl_0
的第一个成员实际上是isa.
而咱们的block()
转换成了block->FuncPtr(block);
就是经过block查找到FuncPtr而后调用,执行相应的代码。至于block明明是__main_block_impl_0
类型的为何却能直接查找到FuncPtr成员,上面已经说明这里存放的并非结构体指针,也不是地址,而是结构体impl自己,因此block是能够通过类型强制转换转换成__block_impl
类型进而经过FuncPtr指针调用相应的函数的。指针
咱们都知道,ObjC中的对象都有一个isa指针,而block中一样存在isa指针。因此能够说block是一个ObjC指针。它封装了block函数的调用。code
像上述例子对咱们来讲一般没有什么实际做用,而咱们一般要在block中处理一下外部变量的逻辑,那么block是怎么处理这些变量的呢?cdn
出现上述状况的缘由是什么呢?依然将main.m转成main.cpp。
上面图片中咱们能够发现block的结构体中出现了一个age变量,而且其构造函数代表将_age赋值给age成员。而在FuncPtr存储的函数__main_block_func_0
的地址中,咱们发现调用此函数是从block的结构体中取出age成员的值进行打印的。因此当定义block的时候age的值已经被block用一个一样名字的成员捕获了。并且捕获的仅仅是age的值。
经过上图咱们能够看出block中存储的是a的地址,因此当FuncPtr指向的函数调用的时候会经过取a地址中存储的值,所就出现下面这种状况了。
咱们能够看出全局变量并无被block所捕获,由于全局变量存放在全局区,随时均可以访问,因此当FuncPtr指向的函数调用的时候就会直接取a和b的值用。而局部变量超过做用域就会自动回收因此block须要在自身存放一份,以保证其能准确访问。
局部变量在block中使用的时候会被block捕获,auto变量是值捕获,而static变量是地址捕获。所有变量不会被捕获。
既然上面说block是一个oc对象,那么他也应该是有类型的。那么block的类型有什么有趣的事呢?
因为在ARC环境下编译器默默对block作了一些咱们看不见的工做,因此咱们将xcode的arc模式关掉,以便于窥探到本质。
经过上述结果咱们能够看出当block访问了auto变量的时候会变成__NSStackBlock__
类型。而其余状况下是__NSGlobalBlock__
类型。
__NSGlobalBlock__
类型存在于数据区,__NSStackBlock__
存在于栈区。
在ARC环境下打印的结果:
而在ARC环境下本来__NSGlobalBlock__
的block依然是__NSGlobalBlock__
类型,而本来是__NSStackBlock__
却变成了__NSMallocBlock__
存放在堆区。这是由于当咱们定义block的时候ARC默认为咱们作了一次copy操做。
下面是block的类型以及在内存中存放的区域:
咱们尝试在MRC下对__NSGlobalBlock__
和__NSMallocBlock__
类型的block进行copy操做并打印结果:
访问了auto变量的block是__NSStackBlock__
类型,没有访问auto变量的block是__NSGlobalBlock__
类型。而对__NSStackBlock__
类型进行copy操做就会变为__NSMallocBlock__
类型。
对三种类型的block分别进行copy操做结果以下: