ObjC block简析(一)

探究block的本质

Block

在main.m的main函数中声明一个block并执行block()经过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp命令将main.m转为main.cpp。xcode

cpp文件中的main函数

在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

__block_impl结构体
咱们能够看到这里并非结构体指针,而是结构体自己,因此 __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中处理一下外部变量的逻辑,那么block是怎么处理这些变量的呢?cdn

auto局部变量的捕获

访问auto变量

出现上述状况的缘由是什么呢?依然将main.m转成main.cpp。

main.cpp

上面图片中咱们能够发现block的结构体中出现了一个age变量,而且其构造函数代表将_age赋值给age成员。而在FuncPtr存储的函数__main_block_func_0的地址中,咱们发现调用此函数是从block的结构体中取出age成员的值进行打印的。因此当定义block的时候age的值已经被block用一个一样名字的成员捕获了。并且捕获的仅仅是age的值。

static局部变量的捕获

main.cpp

经过上图咱们能够看出block中存储的是a的地址,因此当FuncPtr指向的函数调用的时候会经过取a地址中存储的值,所就出现下面这种状况了。

static类型的变量捕获

全局变量的捕获

全局变量

咱们能够看出全局变量并无被block所捕获,由于全局变量存放在全局区,随时均可以访问,因此当FuncPtr指向的函数调用的时候就会直接取a和b的值用。而局部变量超过做用域就会自动回收因此block须要在自身存放一份,以保证其能准确访问。

总结

局部变量在block中使用的时候会被block捕获,auto变量是值捕获,而static变量是地址捕获。所有变量不会被捕获。

block类型

既然上面说block是一个oc对象,那么他也应该是有类型的。那么block的类型有什么有趣的事呢?

因为在ARC环境下编译器默默对block作了一些咱们看不见的工做,因此咱们将xcode的arc模式关掉,以便于窥探到本质。

关闭arc

类型

结果

经过上述结果咱们能够看出当block访问了auto变量的时候会变成__NSStackBlock__类型。而其余状况下是__NSGlobalBlock__类型。

__NSGlobalBlock__类型存在于数据区,__NSStackBlock__存在于栈区。

在ARC环境下打印的结果:

ARC下的block类型

而在ARC环境下本来__NSGlobalBlock__的block依然是__NSGlobalBlock__类型,而本来是__NSStackBlock__却变成了__NSMallocBlock__存放在堆区。这是由于当咱们定义block的时候ARC默认为咱们作了一次copy操做。

下面是block的类型以及在内存中存放的区域:

block的类型

咱们尝试在MRC下对__NSGlobalBlock____NSMallocBlock__类型的block进行copy操做并打印结果:

copy操做

block的类型(MRC环境)

访问了auto变量的block是__NSStackBlock__类型,没有访问auto变量的block是__NSGlobalBlock__类型。而对__NSStackBlock__类型进行copy操做就会变为__NSMallocBlock__类型。

对三种类型的block分别进行copy操做结果以下:

对三种类型的block进行copy操做


ObjC block简析(二)

相关文章
相关标签/搜索