这是我参与8月更文挑战的第29天,活动详情查看: 8月更文挑战
在之前的文章中,我们分析了block
的内存管理以及堆栈情况;今天我们来分析一下block
究竟是一个什么样的结构呢;
clang分析block底层结构
没有__block修饰变量
说到分析结构,那么我们第一反应就是使用clang
或者xcrun
命令来分析cpp
文件;
为了避免其他函数的干扰,我们创建一个block.c
的文件,代码如下:
进入文件目录,使用xcrun
命令生成cpp
文件:
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc block.c -o block.cpp
接下来,打开cpp
文件,查看main
函数,其实现如下:
我们将代码中强转
和类型
相关的代码去掉,对cpp
文件代码进行优化,如下:
那么__main_block_impl_0
是什么呢?其实现也可以在cpp
文件中找到:
可以看到__main_block_impl_0
是一个结构体;根据代码我们可以分析出是将block
指向了__main_block_impl_0
结构体的构造函数;
a(_a)
是一个c++
的语法,我们传入的参数_a
会对成员变量a
进行赋值,将_a
传给a
;
这时我们发现,这个结构体中有一个a
,这个a
是否是我们外部的参数a
呢?我们将main
函数中的代码修改如下:
然后再次生成cpp
文件,比较前后两个cpp
文件中__main_block_impl_0
有什么区别:
这也就说明,我们传进来的参数
a
,被block
捕获之后,会在block
的底层生成一个成员变量
;对象类型同样适用,也会生成成员变量;
再分析__main_block_impl_0
我们发现这样一个细节impl.isa = &_NSConcreteStackBlock;
,impl
的isa
指向了一个_NSConcreteStackBlock
;
按照我们之前讲的,如果一个block
捕获到外界变量,并且没有被弱引用,那么他应该是一个堆block
;而此时编译器生成的却是一个栈block
;
那么是不是编译时
生成的是栈block
,到运行时才会变为堆block
呢?
我们来看一下__main_block_impl_0
的构造函数,它的第一个参数fp
,那么fp
是什么时候被调用的呢?
首先我们要确定fp
是什么?我们可以从cpp
文件的main
函数实现中,可以分析出fp
指的就是__main_block_func_0
,那么__main_block_func_0
又是什么呢?
__main_block_func_0
也是一个函数,也就是fp
传递的是一个函数;而impl.FuncPtr = fp;
中fp
又被赋值给了FuncPtr
;而FuncPtr
是在main
函数中被调用的;
block
在底层是一个fp
的函数式保存;所以一个block
被定义之后,如果没有被调用,那么功能逻辑代码永远不会被执行;
我们在函数__main_block_func_0
中发现a
来自于__cself->a
(值拷贝),那么传进来的__cself
又是是什么呢?
根据上下文调用关系我们可以分析出__cself
就是block
自身;__main_block_impl_0
中的a
与__main_block_func_0
中的a
值相同,地址不同;
__block修饰变量
我们在开发过程中,经常会有这样的场景,我们需要在block
中修改a
的值,那么我们就需要使用__block
来修饰a
,这样我们才能在block
内部修改a
,那么__block
做了什么事情导致我们可以对外部变量进行修改了呢?
接下来,我们将main
函数修改如下:
然后生成其cpp
文件;我们先来看一下__main_block_impl_0
结构体有什么变化:
可以看到我们之前的int a
,因为使用了__block
之后,变成了__Block_byref_a_0 *a
;而在main
函数中多了一行代码:__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 5};
,很明显,这是一个结构体的初始化操作;其代码优化之后如下:
接下来我们来看一下__Block_byref_a_0
:
__forwarding
就是a
;
然后在__main_block_impl_0
的构造函数中将结构体__Block_byref_a_0
进入进去
然后a(_a->__forwarding)
将__Block_byref_a_0
结构体中的__forwarding
传给了__main_block_impl_0
的成员变量__Block_byref_a_0 *a
;
那么此时函数__main_block_func_0
中的a
和我们定义的a = 5
的a
是相同的,他们都指向了同一片内存空间(传递的是地址,指针赋值);
正因为都指向了同一片内存空间,所以在block
内部才能修改外部变量;
__block
生成了一个结构体__Block_byref_a_0
,并且传给block
其指针地址,从而达到能够修改同一片内存空间的目的;
分析到这里,我们基本上了解了block
在底层的数据结构,但是我们中间产生的疑问:是不是编译时
生成的是栈block
,到运行时才会变为堆block
呢?依然没有解决;请听下回讲解......