前言
在IOS
开发中大家对block
用的非常多,一般情况下仅仅停留在会用的层面,具体的block
的底层是如何实现的是一概不知。也许很多人就问block
会用不就行了,知道底层原理干什么。比如block
是如何捕获变量的,block
底层是什么类型。如果你了解它的底层原理就不会有这些疑问。作为一名程序开发者,必须要弄清楚其底层原理。
准备工作
1. block底层分析
1.1 block捕获普通变量(非__block修饰)
1.1.1 block
捕获对象类型
创建一个NSObject
对象在block
内部使用。代码如下
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
NSObject * obj = [NSObject alloc];
void (^ block)(void) = ^{
NSLog(@"----%@",obj);
};
block();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
通过xcrun
把main.m
文件编译成main.cpp
文件,提取block
相关的关键底层代码如下:
block
底层是一个结构体
,在__main_block_impl_0
结构提中又嵌套了两个结构体__block_impl
和__main_block_desc_0
。
-
__block_impl
数据结构
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
__block_impl
结构体中的FuncPtr
用来保存任务功能函数即__main_block_func_0
函数的地址。
-
__main_block_desc_0
数据结构
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};
__main_block_desc_0
结构中的变量copy
和dispose
是非常重要的函数保存着__main_block_copy_0
和__main_block_dispose_0
函数地址。在block
进行拷贝
和释放
时调用。
-
__main_block_impl_0
构造函数
__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;
}
构造函数__main_block_impl_0
对block
结构体中相关属性进行设置。构造函数__main_block_impl_0
的第一个参数为__main_block_func_0
方法实现地址,在声明定义block
时,将block
的任务函数封装到FuncPtr
属性中。
调用
block
执行时,实际调用的是block->FuncPtr
,并将block
结构体作为参数传入到方法实现中,底层代码如下:
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
1.1.2 block
捕获非对象类型
用int
类型的变量进行实例探究。代码如下:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
int a = 10;
void (^ block)(void) = ^{
NSLog(@"----%d",a);
};
block();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
通过xcrun
把main.m
文件编译成main.cpp
文件,提取block
相关的关键底层代码如下:
捕获
非对象类型
比如基本数据类型,__main_block_desc_0
结构中是没有变量copy
和dispose
,这就意味着没有copy
和dispose
功能。这一点很重要,会影响block的三层拷贝
。
1.1.3 总结:main
函数中和block
操作相关的有两个步骤
-
对
block
的结构体进行初始化赋值
,参数有__main_block_func_0
任务功能函数,&__main_block_desc_0_DATA
描述信息的函数地址以及外界的变量obj
。-
block
底层会被编译成一个结构体类型
-
block
结构体此时自动生成了一个NSObject *__strong obj
的变量,对结构体进行赋值时obj(_obj)
通过C++
方式给block
结构体的obj
赋值 - 将
__main_block_func_0
赋值给block
结构体中的变量__block_impl
中的FuncPtr
保存 - 将
&__main_block_desc_0_DATA
赋值给block
结构体中的变量__main_block_desc_0
中的Desc
保存
-
-
调用
blcok
结构体中__block_impl
变量中的FuncPtr
变量执行任务功能函数-
FuncPtr
执行时会把blcok
作为参数,因为要获取blcok
中的objc
变量 -
__main_block_func_0
方法中使用的变量其实就是获取的blcok
结构体中新生成的obj
。结构体中的obj
和外界变量的obj
指向的同一片内存空间
-
-
blcok
的赋值和执行都是通过blcok
内部的数据或者函数去执行,都是用blcok
中保存的数据
1.1.4 问题补充
为什么普通的变量(不用__block
修饰)在blcok
内部不能进行修改?
因为blcok
内部的变量obj
和外界变量obj
指向的是同一块内存,如果此时blcok
内部的变量obj
重新指向一块内存即obj
中地址发生改变
,而此时外界的变量obj
还是指向它开始指向的内存空间没有改变,此时编译器不知道该用哪个obj
造成代码有歧义,所以不能进行修改仅仅可读
。这也就大家常说的值拷贝
。
1.2 block捕获__block修饰的变量
1.2.1 block
捕获__block
修饰的对象类型
给变量obj
添加__block
修饰,并且在block
内存进行修改。代码如下:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
__block NSObject * obj = [NSObject alloc];
void (^ block)(void) = ^{
obj = [NSObject alloc];
NSLog(@"----%@",obj);
};
block();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
通过xcrun
把main.m
文件编译成main.cpp
文件,提取block
相关的关键底层代码如下:
main.cpp
中可以看出blcok
捕获和调用流程和捕获非__block
修饰的变量的流程基本一致
,区别就是捕获变量的数据结构类型发生了改变
编译器会把__block
修饰的变量底层编译成一个结构体__Block_byref_obj_0
,结构如下:
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;//__Block_byref_obj_0的地址默认赋值时指向自己
int __flags;//标识
int __size;//大小
void (*__Block_byref_id_object_copy)(void*, void*);//copy方法
void (*__Block_byref_id_object_dispose)(void*);//dispose方法
NSObject *__strong obj;//对象的地址
};
编译器把__block
修饰的变量底层编译成结构体时,会进行初始化赋值,具体如下:
-
(__Block_byref_obj_0 *)&obj
赋值给__forwarding
,其实就是__forwarding
指向__Block_byref_obj_0
地址。简单的说__Block_byref_obj_0
中的__forwarding
指向__Block_byref_obj_0
-
__Block_byref_id_object_copy_131
赋值给__Block_byref_id_object_copy
-
__Block_byref_id_object_dispose_131
赋值给__Block_byref_id_object_dispose
- 对象的地址赋值给变量
NSObject *__strong obj
blcok
的结构体初始化赋值时,把变量的结构体__Block_byref_obj_0 obj
的地址
作为参数
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_obj_0 *obj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,
__Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
此时把_obj->__forwarding
赋值 __main_block_impl_0
结构体中自动生成的变量__Block_byref_obj_0 *obj
,也就意味着obj
也是指向__Block_byref_obj_0
结构体
(block)->FuncPtr(block)
调用任务功能函数代码如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_obj_0 *obj = __cself->obj; // bound by ref
(obj->__forwarding->obj) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)
((id)objc_getClass("NSObject"), sel_registerName("alloc"));
NSLog((NSString *)&__NSConstantStringImpl,(obj->__forwarding->obj));
}
-
__Block_byref_obj_0 *obj = __cself- >obj
把blcok
结构体中的obj
赋值给obj
,也就是把__Block_byref_obj_0
的地址赋值给了__Block_byref_obj_0 *obj
-
obj->__forwarding
指向的是__Block_byref_obj_0
结构体的地址
-
obj->__forwarding->obj
就是__Block_byref_obj_0
结构体中的NSObject *__strong obj
变量 -
obj->__forwarding->obj
修改obj
时,block
内部和外部变量的指向没有改变,只是改变了外部和内部变量里面的obj
,实际上访问到的obj
就是同一个,不管这个obj
有没有被修改。
1.2.2 block
捕获__block
修饰的非对象类型
用__block
修饰的int
类型变量进行实例探究。代码如下:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
__block int a = 10;
void (^ block)(void) = ^{
NSLog(@"----%d",a);
};
block();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
通过xcrun
把main.m
文件编译成main.cpp
文件,提取block
相关的关键底层代码如下:
block
捕获__block
修饰的对象类型,在__Block_byref_obj_0
结构体中没有copy
和dispose
。这一点很重要会影响block的三层拷贝
1.2.3 总结__Block
修饰的对象在block
内部做了什么
-
__block
修饰的变量底层会生成一个__Block_byref_obj_0
结构体 -
__Block_byref_obj_0
结构体中保存着对象的地址
以及__Block_byref_obj_0
地址 -
blcok内部
和外部的变量
都是指向的__Block_byref_obj_0
地址。而__Block_byref_obj_0
中的变量obj
发生改变时blcok
内部和外部的变量仍然指向__Block_byref_obj_0
地址,然后获取obj
1.3 __blcok修饰变量指示图
根据.cpp
文件中变量的赋值和修改流程所画的图,更好的理解为什么__block
修饰的变量可以修改。如下:
1.4 block未捕获变量(全局或静态)
1.4.1 block
未捕获变量
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
void (^ block)(void) = ^{
};
block();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
通过xcrun
把main.m
文件编译成main.cpp
文件,提取block
相关的关键底层代码如下:
如果
block
未捕获变量,那么在block
内部不会自动生成相应的变量,__main_block_desc_0
结构体中没有copy
和disponse
函数。
1.4.2 block
捕获全局或静态变量
static int a = 100;
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
void (^ block)(void) = ^{
NSLog(@"----%d",a);
};
block();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
通过xcrun
把main.m
文件编译成main.cpp
文件,提取block
相关的关键底层代码如下:
block
捕获全局和静态变量和未捕获变量是一样的,此时只是使用了全局或静态变量直接传入。
2. block底层探究
通过对main.cpp
文件分析,大致理清楚了blcok
的变量赋值以及block
的调用。但是栈区blcok
变成堆区blcok
过程还不了解。下面通过汇编跟踪流程的方式进行探究,请继续往下走咯!
2.1 全局block的底层探究
运行一个案例给blcok
的设置断点,如下图所示:
block
虽然没有值但是block
中的变量和底层main.cpp
文件中的block
结构很相似。下面进行汇编调试:汇编断点
汇编中显示跳转到
objc_retainBlock
,直接给objc_retainBlock
下符号断点。继续调试,如下:objc_retainBlock
-
objc_retainBlock
汇编中下一步会跳转到_Block_copy
-
objc_retainBlock
方法是在libobjc.A.dylib
源码库中 -
真机
情况下x0
表示方法的第一个参数即消息的接收者
。通过lldb
调试发现此时的消息接收者是block
。而且此时的block
是一个全局block
给_Block_copy
添加符号断点,继续调试,如下:
-
_Block_copy
方法在libsystem_blocks.dylib
源码库中 - 通过
lldb
调试发现_Block_copy
的消息接收者是block
。此时的block
仍然是一个全局block
_Block_copy
汇编结束的位置会return
一个返回值,在真机情况下,返回值会保存到x0
寄存器中。那么查看返回值如下:
全局
block
经过_Block_copy
以后什么也没做
,直接返回全局block
2.2 堆区block的底层探究
创建一个堆block
,给blcok
的设置断点,如下图所示:
打开汇编断点,如下:
打开汇编断点
同理添加
_Block_copy
的符号断点,如下:_Block_copy
在
_Block_copy
方法中进行了lldb
调试,发现此时的block
是栈block
,不是堆block
,这是为什么呢?继续往下走,查看return
的类型,如下:查看return的类型
经过上面的流程已经流程对应的
lldb
调试得出以下几点:
- 栈区
block
经过_Block_copy
以后变成堆区block
- 栈区
block
和堆区block
的地址不一样说明是在堆区新开辟的内存
- 堆区
block
里面的变量invoke
、cpoy
、disponse
地址是一样的,说明栈区block
在运行时会cpoy
一份到堆区,形成一个新的堆区block
然后返回
2.3 栈区block的底层探究
创建一个栈区的block
案例,给栈区blcok
设置断点,如下图所示:
同样打开汇编断点,查看里面汇编的情况,如下:
打开汇编断点
根据汇编流程,发现栈区
block
并不会调用objc_retainBlock
方法,也就不会调用_Block_copy
方法。
2.4 总结以上三种情况
- 全局
block
在运行时调用_Block_copy
方法后,仍然是全局block
- 堆区
block
是由编译时的栈区block
在运行时调用_Block_copy
方法,生成新的堆区block
- 栈区
blcok
不会进行_Block_copy
的操作
结论:如果block
赋值给强引用
或者copy修饰的变量
,那么block
会进行_Block_copy
操作,如果是赋值给__weak
修饰的变量则不会进行_Block_copy
的操作
2.5 block的类型Block_layout结构体
通过.cpp
文件和汇编对block
的类型有个初步了解,但是还是不全面而且不够深入。同类型的blcok
里面的变量也是有区别的, 只有通过底层源码去探究block
类型。objc_retainBlock
方法是在libobjc.A.dylib
源码库中,在objc4-818.2
源码中全局搜索objc_retainBlock
,如下:
objc_retainBlock
方法中调用了_Block_copy
方法和汇编流程是相吻合的。在汇编流程中得知_Block_copy
方法在libsystem_blocks.dylib
源码库中,但是该源码库并没有开源。经过开发者的不断探索在libclosure-79
源码库中找到了_Block_copy
方法实现,如下:_Block_copy
_Block_copy
方法中发现block的底层是一个Block_layout
结构体。那么查看Block_layout
结构体如下:
struct Block_layout {
//block 的类型
void * __ptrauth_objc_isa_pointer isa; // 8 字节
//用来标识`blcok`的信息是按位存储的,类似于对象中isa的`bits`
volatile int32_t flags;// 4字节
//保留字段
int32_t reserved;// 4字节
//函数指针,保存任务函数的实现地址 就是.cpp文件中的FuncPtr
BlockInvokeFunction invoke; // 8字节
//描述信息
struct Block_descriptor_1 *descriptor;// 8字节
};
Block_layout
结构体变量的含义:
-
isa
:表示block
的类型(栈、堆 、全局
) -
flags
:标识符类似于对象中isa
的bits
-
reserved
:保留字段 -
invoke
:函数指针,保存任务函数的实现地址 就是.cpp
文件中的FuncPtr
函数 -
descriptor
:描述信息
flag
标识
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
BLOCK_SMALL_DESCRIPTOR = (1 << 22), // compiler
#endif
BLOCK_IS_NOESCAPE = (1 << 23), // compiler
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
- 第
1
位BLOCK_DEALLOCATING
: 释放标记,-般常用BLOCK_NEEDS_FREE
做位与操作,一同传入Flags
,告知该block
可释放 - 低
16
位BLOCK_REFCOUNT_MASK
:存储引用计数的值,是一个可选用参数
- 第
22
位BLOCK_SMALL_DESCRIPTOR
:包含了Block_descriptor_1
、Block_descriptor_2
和Block_descriptor_3
- 第
24
位BLOCK_NEEDS_FREE
:低16
是否有效的标志,程序根据它来决定是否增加或是减少引用计数位的值 - 第
25
位BLOCK_HAS_COPY_DISPOSE
:是否拥有拷贝辅助函数
(a copy helper function
) - 第
26
位BLOCK_HAS_CTOR
:是否拥有block
析构函数 - 第
27
位BLOCK_IS_GC
:标志是否有垃圾回收;//OS X - 第
28
位BLOCK_IS_GLOBAL
:标志是否是全局block
- 第
30
位BLOCK_HAS_SIGNATURE
:与BLOCK_USE_STRET
相对,判断当前block
是否拥有一个签名。用于runtime
时动态调用
在这些标记位中BLOCK_HAS_COPY_DISPOSE
和BLOCK_HAS_SIGNATURE
这两个标记位特别重要
-
BLOCK_HAS_COPY_DISPOSE
表示是否有Block_descriptor_2
-
BLOCK_HAS_SIGNATURE
表示是否有Block_descriptor_3
- 因为
Block_descriptor_1
是必须要有的,所以没有其标记位 - 通过标记位可以判断
Block_descriptor_1
是必须有的,Block_descriptor_2
和BLOCK_DESCRIPTOR_3
是可选的
descriptor
描述信息
Block_descriptor_1
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved; //8字节
uintptr_t size; //8字节
};
Block_descriptor_2
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy; //8字节
BlockDisposeFunction dispose; //8字节
};
Block_descriptor_3
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature; //8字节
const char *layout; //8字节 // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
- 对
descriptor
三种描述信息的总结-
Block_descriptor_1
是结构体类型,其中reserved
表示保留信息,size
表示block
大小 -
Block_descriptor_2
是结构体类型,其中copy
存的是copy
函数地址,dispose
存的是dispose
函数地址 -
Block_descriptor_3
是结构体类型,其中signature
表示签名信息,layout
表示block
的扩展布局
-
descriptor
的构造函数探究
下面探究下descriptor
的构造函数,究竟是如何获取descriptor
的。
Block_descriptor_1
struct Block_descriptor_1 *desc1 = layout->descriptor;
Block_descriptor_2
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
desc += sizeof(struct Block_descriptor_1); //地址偏移获取到descriptor_2
return (struct Block_descriptor_2 *)desc;
}
Block_descriptor_3
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);//descriptor_1的地址
desc += sizeof(struct Block_descriptor_1);//地址偏移descriptor_1的大小
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {//如果descriptor_2存在
desc += sizeof(struct Block_descriptor_2);//继续地址偏移descriptor_2大小
}
return (struct Block_descriptor_3 *)desc; //得到descriptor_3的地址
}
Block_descriptor_1
是直接赋值,而Block_descriptor_2
和Block_descriptor_3
是通过地址偏移
获取的。在结合上面的标志位再次验证Block_descriptor_1
是必有的默认的,Block_descriptor_2
和Block_descriptor_3
是可选的,根据标记位
判断。
2.6 lldb验证Block_layout中变量
2.6.1 堆区block
调试验证
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
NSObject * obj = [NSObject alloc];
void (^ block)(void) = ^{
NSLog(@"----%@",obj);
};
block();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
在_Block_copy
汇编开始的位置
或者ret位置
进行调试,如下:
-
flags
的值是0x00000000c3000002
,descriptor
的值是0x0000000100390078
- 打印
descriptor
中存储的信息得到size
的值0x0000000000000028
换算成10
进制等于40
是Block_layout
结构体的大小。大家可能有疑问Block_layout
结构体的大小不是32
,因为捕获变量会在block
内部生成一个新的变量,现在捕获的是一个指针类型(8
字节)所以Block_layout
大小总共40
个字节 -
BLOCK_HAS_COPY_DISPOSE=(1 << 25)
,flags & BLOCK_HAS_COPY_DISPOSE
用来判断是否有Block_descriptor_2
-
BLOCK_HAS_SIGNATURE=(1 << 30)
,flags & BLOCK_HAS_SIGNATURE
用来判断是否有Block_descriptor_3
-
signature
的值是0x000000010038ffa6
,打印出的值是v8@?0
签名信息的补充
v8@?0
:v
表示返回值为空(没返回值),8
表示参数的总大小,@?
表示block
,0
表示从0
号字节开始
2.6.2 全局block
调试验证
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
void (^ block)(void) = ^{
};
block();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
在_Block_copy
汇编开始的位置
或者ret位置
进行调试,如下:
-
Block_layout
结构体大小是0x0000000000000020
转换成10
进制32
,因为没有捕获变量所以大小只有32
字节 - 全局
block
没有Block_descriptor_2
,有Block_descriptor_3
- 因为
内存是连续的Block_descriptor_2
没有,那么0x0000000104a03f9b
就是Block_descriptor_3
的起始位置
2.7 blcok的三层拷贝
2.7.1 _Block_copy
源码探究
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
//block是否需要释放
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
//如果是全局block就直接返回
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {// 栈 - 堆 (编译期)
//编译时期不能生成堆block 只能是栈block 只能进行通过_Block_copy进行开辟堆block
// Its a stack block. Make a copy.
size_t size = Block_size(aBlock);
struct Block_layout *result = (struct Block_layout *)malloc(size);
if (!result) return NULL;
//将aBlock拷贝到result中
memmove(result, aBlock, size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#if __has_feature(ptrauth_signed_block_descriptors)
//BLOCK_SMALL_DESCRIPTOR 是包含了Block_descriptor_1、Block_descriptor_2 和 Block_descriptor_3
//根据flags的标志位来判断的
if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
//里面是descriptor的拷贝赋值
uintptr_t oldDesc = ptrauth_blend_discriminator(
&aBlock->descriptor,
_Block_descriptor_ptrauth_discriminator);
uintptr_t newDesc = ptrauth_blend_discriminator(
&result->descriptor,
_Block_descriptor_ptrauth_discriminator);
result->descriptor =
ptrauth_auth_and_resign(aBlock->descriptor,
ptrauth_key_asda, oldDesc,
ptrauth_key_asda, newDesc);
}
#endif
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
//result:是在堆区创建的block aBlock:外面传进来的栈的
//调用Block_descriptor_2`copy`方法
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
//将isa设置位_NSConcreteMallocBlock即堆block
result->isa = _NSConcreteMallocBlock;
return result;
}
}
源码的流程如下:
- 如果
block
需要释放,则直接释放 - 如果
block
是全局block
,则不需要操作直接返回 - 因为堆
block
需要在堆区申请开辟内存,编译时并不会生成堆block,所以只能是栈block
- 通过
malloc
开辟新的内存 - 通过
memmove
将栈区block
数据拷贝到新开辟的内存中 - 通过
_Block_call_copy_helper
调用Block_descriptor_2
中的copy
方法 - 将堆上
block的isa
设置为_NSConcreteMallocBlock
_Block_call_copy_helper
源码探究
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{ // 获取`copy`函数的函数指针
if (auto *pFn = _Block_get_copy_function(aBlock))
// 调用copy方法
pFn(result, aBlock);
}
- 通过
_Block_get_copy_function
方法获取copy函数的函数指针
- 然后调用
copy
函数
_Block_get_copy_function
方法探究
//获取方法的指针
#define _Block_get_relative_function_pointer(field, type) \
((type)((uintptr_t)(intptr_t)(field) + (uintptr_t)&(field)))
#define _Block_get_function_pointer(field) \
(field)
static inline __typeof__(void (*)(void *, const void *))
_Block_get_copy_function(struct Block_layout *aBlock)
{
//如果没有description_2,就没有copy方法,直接返回NULL
if (!(aBlock->flags & BLOCK_HAS_COPY_DISPOSE))
return NULL;
//获取Block_descriptor_1的首地址
void *desc = _Block_get_descriptor(aBlock);
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
//如果Block_descriptor_1,Block_descriptor_2,Block_descriptor_3 全都有
if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
struct Block_descriptor_small *bds =
(struct Block_descriptor_small *)desc;
//_Block_get_relative_function_pointer是一个宏
return _Block_get_relative_function_pointer(
bds->copy, void (*)(void *, const void *));
}
#endif
struct Block_descriptor_2 *bd2 =
(struct Block_descriptor_2 *)((unsigned char *)desc +
sizeof(struct Block_descriptor_1));
//_Block_get_copy_fn 获取copy的函数指针
return _Block_get_copy_fn(bd2);
}
- 判断是否有
Block_descriptor_2
,如果没有返回NULL
- 如果有
Block_descriptor_2
,根据不同的情况下去获取copy
函数的函数指针
** 结论:_Block_call_copy_helper
方法的作用就是获取copy
函数指针,调用copy函数**
** 注意:此时的copy
函数是Block_descriptor_2
中的copy
函数**
2.7.2 _Block_object_assign
源码探究
main.cpp
文件中block
结构体初始化时,结构体中的descriptor
是通过外面传进来参数进行赋值的,如下所示:
图中显示
block
结构体中的copy
变量存储的就是__main_block_copy_0
函数的地址,调用block
结构体中的copy
就是调用__main_block_copy_0
函数,方法如下:
static void __main_block_copy_0(struct __main_block_impl_0*dst,
struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
}
-
__main_block_copy_0
函数中有两个参数dst
和src
,dst
是堆区block
,src
是栈区block
- 在探究
_Block_copy
方法时_Block_call_copy_helper(result, aBlock)
方法中调用了copy
方法,result
是堆区block
,aBlock
是栈区block
-
__main_block_copy_0
函数中调用了_Block_object_assign
方法。 -
_Block_object_assign
有三个参数,前两个参数是block
捕获的变量,第三个参数捕获变量的类型
捕获变量的类型是枚举的,如下:
enum {
// see function implementation for a more complete description of these fields and combinations
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
常用的类型
-
BLOCK_FIELD_IS_OBJECT
,变量类型是普通对象
-
BLOCK_FIELD_IS_BLOCK
,变量类型是block
类型 -
BLOCK_FIELD_IS_BYREF
,变量类型是__block
修饰变量
在libclosure-79
源码中全局搜索_Block_object_assign
,找到其源码如下:
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
// _Block_retain_object_default = fn (arc)
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
*dest = object;
break;
default:
break;
}
}
- 如果变量类型是
普通对象
,交给系统处理
,此时进行指针拷贝*dest = object
,引用计数+1
- 如果变量类型是
block
类型,进行_Block_copy
操作 - 如果变量类型是
__block
修饰变量,进行_Block_byref_copy
操作
2.7.3 _Block_byref_copy
源码探究
** Block_byref
结构体探究**
_Block_byref_copy
主要是对变量拷贝
,_block
修饰的变量底层编译成Block_byref
类型,其结构如下:
/ Values for Block_byref->flags to describe __block variables
enum {
// Byref refcount must use the same bits as Block_layout's refcount.
// BLOCK_DEALLOCATING = (0x0001), // runtime
// BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_BYREF_LAYOUT_MASK = (0xf << 28), // compiler
BLOCK_BYREF_LAYOUT_EXTENDED = ( 1 << 28), // compiler
BLOCK_BYREF_LAYOUT_NON_OBJECT = ( 2 << 28), // compiler
BLOCK_BYREF_LAYOUT_STRONG = ( 3 << 28), // compiler
BLOCK_BYREF_LAYOUT_WEAK = ( 4 << 28), // compiler
BLOCK_BYREF_LAYOUT_UNRETAINED = ( 5 << 28), // compiler
BLOCK_BYREF_IS_GC = ( 1 << 27), // runtime
BLOCK_BYREF_HAS_COPY_DISPOSE = ( 1 << 25), // compiler
BLOCK_BYREF_NEEDS_FREE = ( 1 << 24), // runtime
};
// __block -> {}
// 结构体
struct Block_byref {
void * __ptrauth_objc_isa_pointer isa; // 8
struct Block_byref *forwarding; // 8
volatile int32_t flags; // contains ref count//4
uint32_t size; // 4
};
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep; //= __Block_byref_id_object_copy_131
BlockByrefDestroyFunction byref_destroy; // = __Block_byref_id_object_dispose_131
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
-
__block
修饰的变量底层被编译成了Block_byref
结构体类型 -
Block_byref
的类型和Block_layout
的descriptor
比较类似,都是通过flags
来判断Block_byref_2
和Block_byref_3
是否存在。既Block_byref_2
和Block_byref_3
是可选
的。
_Block_byref_copy
方法探究
static struct Block_byref *_Block_byref_copy(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
// __block 内存是一样 同一个家伙
//
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
// 在堆区开辟内存将外界变量拷贝一份到堆区
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
// 堆区中新开辟的copy和 外界变量src的forwarding指向同一片内存
// 也就意味着它们持有着同一个对象
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
//这里和_Block_copy比较类似
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
// 捕获到了外界的变量 - 内存处理 - 生命周期的保存
//调用 Block_byref_2 中的byref_keep方法
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
- 通过
malloc
方法开辟内存将外界变量拷贝一份,存放到堆区新开辟的内存
中 -
copy->forwarding = copy
和src->forwarding = copy
表明它们的forwarding
指向同一片内存,那么它们就持有同一个对象 -
(*src2->byref_keep)(copy, src)
调用Block_byref_2
中的byref_keep
方法
总结:_Block_byref_copy
对__block
修饰的变量进行拷贝即对Block_byref
进行拷贝
2.7.4 byref_keep
源码探究
byref_keep
方法在结构体Block_byref_2
在底层编译时已经被初始化赋值了。查看main.cpp
文件,如下:
main.cpp
文件显示Block_byref_2
结构体中byref_keep
存储__Block_byref_id_object_copy_131
函数指针,而byref_destroy
存储__Block_byref_id_object_dispose_131
函数指针
结构体大小统计
__Block_byref_id_object_copy_131
再次调用_Block_object_assign
方法,此次_Block_object_assign
方法中的参数是Block_byref
结构体中的obj
对象。所以此次会走BLOCK_FIELD_IS_BLOCK
流程
2.7.5 总结
- 通过
_Block_copy
方法,将栈区blcok
拷贝一份放在堆区
-
__block
修饰的对象,通过_Block_byref_copy
方法,将Block_byref
结构体类型进行拷贝
- 通过
_Block_object_assign
方法,对Block_byref
中的对象处理。实际上这一层没有进行拷贝,但是走的还是拷贝的流程
2.8 _Block_object_dispose探究
_Block_object_dispose
方法和_Block_object_assign
方法是对应的,那么我们还是先从汇编入手,如下所示:
汇编显示在
block
释放时会调用_Block_object_dispose
方法。在libclosure-79
源码中全局搜索_Block_object_dispose
,如下:
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
_Block_object_dispose
方法就是调用了block
结构体中Block_descriptor_2
中的disponse
,根据捕获的变量类型_Block_object_dispose
进行不同的释放操作。如果是__block
修饰的变量会调用_Block_byref_release
方法
_Block_byref_release
方法探究
static void _Block_byref_release(const void *arg) {
struct Block_byref *byref = (struct Block_byref *)arg;
byref = byref->forwarding;
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
if (latching_decr_int_should_deallocate(&byref->flags)) {
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
//byref_destroy 释放销毁变量 和 byref_keep对应
(*byref2->byref_destroy)(byref);
}
//释放
free(byref);
}
}
}
_Block_byref_release
方法就是对象
,变量
的释放
和销毁
3. 总结
block
的底层探索过程非常的绕,需要满满的去理清内部的关系。
网友评论