block分析(下)
block通过clang分析
带着下面的疑问,我们去探索block原理
- 探索
block底层源码
- block在底层是
如何编译
的?或者说block在底层会编译成什么样的结构
? - block的
内部源码结构
又是什么样的? - 我们经常说的
invoke
是在哪里呢? - 对象有isa的指向,那么
block的isa指向
又是什么呢? invoke的签名
- block
捕获的变量
会到哪里去? - block
捕获的对象
是如何保存和释放的?
block通过clang分析
捕获外界变量
#include "stdio.h"
int main() {
int a = 8;
void(^block)(void) = ^{
printf("LG_Cooci - %d",a);
};
block();
return 0;
}
- 通过
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc block.c -o block.cpp
生成.cpp文件,查看.cpp文件中的main
函数
int main() {
int a = 8;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
// 剔除强转代码,方便阅读
int main() {
int a = 8;
void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));
block->FuncPtr(block);
return 0;
}
- 调用
__main_block_impl_0
函数传入三个参数
- 调用block的
FuncPtr
函数并传入block
参数一
、参数三
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("LG_Cooci - %d",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)};
- 查看
__main_block_impl_0
的定义,发现是一个结构体
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;
}
};
- 当
捕获外界变量
时,在结构体内部,会生成相应的成员变量
用来存储 -
成员变量a
通过结构体的构造函数赋值:a(_a)
- main函数中调用Bolck,由于捕获外界变量,此时传入的
FuncPtr
中的block发挥作用:
block为自身结构体的指针,将block中的成员变量a赋值给临时变量a
,然后对其打印;
临时变量a
和__cself->a
的值相同,但地址不同;
由于a是值拷贝
,Bolck的代码块中不能对a的值进行改变
,会造成编译器的代码歧义。所以此时的a是只读的。 - 捕获外界变量并赋值强引用变量,本该是
堆区Block
,但结构体中impl.isa
赋值为&_NSConcreteStackBlock
,标记为栈区Block
。因为在编译时无法开辟内存空间,所以暂且标记为StackBlock
。在运行时会根据情况将Block拷贝到堆区,然后生成MallocBlock
。
未捕获外界变量
#include "stdio.h"
int main() {
void(^block)(void) = ^{
printf("LG");
};
block();
return 0;
}
- 生成
.cpp
文件查看main函数
int main() {
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);
return 0;
}
// 剔除强转代码,方便阅读
int main() {
void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
block->FuncPtr(block);
return 0;
}
- 调用
__main_block_impl_0
函数,传入两个参数
,取地址并赋值block - 缺少的第三个参数为
外界变量a
参数一
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("LG");
}
参数二
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)};
- 查看
__main_block_impl_0
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;
}
};
- Block的本质是
结构体
,main函数中调用的是结构体的构造函数
- 结构体中包含两个成员变量:
__block_impl
结构体类型的impl
__main_block_desc_0
结构体指针类型的Desc
- 构造函数中生成了
flags等于0
的默认值,赋值impl.Flags
- 参数
fp
为Block代码块
的函数指针
,赋值impl的FuncPtr
- 参数
desc
赋值给成员变量Desc
- 所以main函数中,代码
block->FuncPtr(block)
就是在对Block进行调用
由此可见当Block仅定义
不调用执行,不会触发Block中的代码块
捕获使用__block修饰的外部变量
#include "stdio.h"
int main() {
__block int a = 18;
void(^block)(void) = ^{
a++;
printf("LG - %d",a);
};
block();
return 0;
}
- 生成
block.cpp
文件查看main函数
int main() {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 18};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
// 剔除强转代码,方便阅读
int main() {
__Block_byref_a_0 a = {
0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
18
};
void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
block->FuncPtr(block);
return 0;
}
int类型a
对应生成__Block_byref_a_0
结构体。成员变量a对结构体a
取地址,转为结构体指针
- 查看
__Block_byref_a_0
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
- 其中
__forwarding
存储的就是a结构体的地址
- 最后的
成员变量a
存储18的值 - 结构体中存储了
自身的地址和值
参数一
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a)++;
printf("LG - %d",(a->__forwarding->a));
}
参数二
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};
//参数2使用的copy和dispose函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}
- 查看
__main_block_impl_0
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
-
局部变量a
和__cself->a
指针地址相同,它的值一旦改变,相当于对外界变量的值进行修改 - 没有
__block修饰
属于值拷贝
,也就是深拷贝
。拷贝的值不可更改,它们指向不同的内存空间 - 使用
__block修饰
属于地址拷贝
,也就是浅拷贝。生成的对象指向同一片内存空间,内部修改等同于对外界变量的修改
block汇编分析得到签名copy过程
Block流程分析
-
ViewController
中编写如下代码
- (void)viewDidLoad {
[super viewDidLoad];
void (^block1)(void) = ^{
NSLog(@"LG_Block");
};
block1();
}
-
image.pngblock定义处
添加断点,运行项目查看汇编
-
添加
image.pngobjc_retainBlock
符号断点调试,发现来自libobjc.A.dylib
框架
-
打开
image.pngobjc4-818.2源码
,查找objc_retainBlock
函数
-
image.pngobjc源码中
找不到_Block_copy
函数的实现,添加_Block_copy
符号断点,发现在libsystem_blocks.dylib
库中,但该框架暂未开源,我们可以在libclosure-79
替代工程中查看。
Block结构
- 在
libclosure-79
工程中查看,发现Block
的真实类型是Block_layout
- 查看
Block_layout
结构体
image.png
isa
:标示Block类型的类
;
flags
:标识符,按bit位表示Block的附加信息
,类似于isa中的位域;
reserved
:预留位置;
invoke
:函数指针,指向Block实现的调用地址
;
descriptor
:附加信息,例如:存储保留变量数、Block的大小、进行copy或dispose的函数指针。
- 查看标识符
flags
image.png
BLOCK_DEALLOCATING
:释放标记,一般常用于BLOCK_BYREF_NEEDS_FREE做位与运算,一同传入flags,告知该Block可释放;
BLOCK_REFCOUNT_MASK
:存储引用引用计数的值,是一个可选用参数;
BLOCK_NEEDS_FREE
:低16位是否有效的标志,程序根据它来决定是否增加或者减少引用计数位的值;
BLOCK_HAS_COPY_DISPOSE
:是否拥有拷贝辅助函数,用于拷贝到堆区,决定block_description_2;
BLOCK_HAS_CTOR
:是否拥有Block的C++析构函数;
BLOCK_IS_GC
:标志是否有垃圾回收,OSX;
BLOCK_IS_GLOBAL
:标志是否是全局Block;
BLOCK_USE_STRET
:与BLOCK_HAS_SIGNATURE相对,判断是否当前Block拥有一个签名,用于runtime时动态调用;
BLOCK_HAS_SIGNATURE
:是否有签名;
BLOCK_HAS_EXTENDED_LAYOUT
:是否有拓展,决定block_description_3。
运行时Copy
- 进入
_Block_copy
函数的汇编代码,通过读取寄存器
分析block的数据状态变化
image.png
发现是一个全局block
修改代码,让block捕获外部变量
NSObject *objc1 = [NSObject alloc];
void (^block1)(void) = ^{
NSLog(@"LG_Block %@", objc1);
};
block1();
image.png
- 调用
objc_retainBlock
方法时,此时是一个__NSStackBlock__
- 继续
跟踪汇编
运行至_Block_copy
,很显然block通过该方法完成了内存的变化,如何验证呢?在retab
的地方设置断点,也就是在方法return
的地方设置断点,查看其最终的处理结果
此时的block
为__NSMallocBlock__
,并且地址发生了改变,从栈区拷贝到了堆区
- 通过
libclosure-79
搜索_Block_copy
如下
得出结论:捕获了外部变量的block
,编译时是栈block
,在运行时通过_Block_copy
方法会copy到堆区,变成堆block
上面打印当前Block为MallocBlock
,同时还打印出signature
、invoke
、copy
、dispose
等数据
- 其中
signature
、copy
、dispose
等数据存储在Block_layout
结构体的descriptor
中 -
invoke
:函数的调用者指针 -
signature
:当前block的签名
签名含义
对于签名v8@?0
的解释
-
v
:返回值类型viod -
8
:方法所占用的内存8字节 -
@?
:参数0类型 -
0
:参数0的起始位置,从0字节开始
签名的详细信息,可以使用NSMethodSignature
的signatureWithObjCTypes
方法输出
(lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"] <NSMethodSignature: 0xbb2001894a750adf>
number of arguments = 1
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
type encoding (@) '@?'
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
-
number of arguments = 1
:表示传入一个参数; -
is special struct return? NO
:没有返回值; -
return value
:返回值:
type encoding (v) 'v'
:void类型的缩写;
memory {offset = 0, size = 0}
:无返回值,故此size为0。 -
argument 0
:参数0; -
type encoding (@) '@?'
:其中@为id类型,?未知类型; -
flags {isObject, isBlock}
:即是Object类型,也是Block类型; -
memory {offset = 0, size = 8}
:参数0从0字节开始,占8字节。
blocklayout的结构
libclosure-79
工程中查看Block_layout
结构体
struct Block_layout {
void * __ptrauth_objc_isa_pointer isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
Block_layout
中的参数descriptor
是Block_descriptor_1
类型的结构体
其中descriptor
有三种类型:Block_descriptor_1
、Block_descriptor_2
、Block_descriptor_3
。
Block_descriptor_1
一定存在,Block_descriptor_2
和Block_descriptor_3
为可选
-
Block_descriptor_1
:存储预留字段和Block大小
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
-
Block_descriptor_2
:存储copy和dispose的函数指针
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
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);
return (struct Block_descriptor_2 *)desc;
}
- 通过
位与
运算,判断Block_descriptor_2
不存在,返回NULL
- 否则读取
Block_descriptor_1
的首地址,偏移自身大小,即:Block_descriptor_2的首地址
- 强转为
Block_descriptor_2
的结构体指针返回。
-
Block_descriptor_3
:存储signature签名和layout
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
Block_descriptor_3
的读取:
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
- 通过
位与
运算,判断Block_descriptor_3
不存在,返回NULL
- 否则读取
Block_descriptor_1
的首地址,偏移自身大小,即:Block_descriptor_2
的首地址; - 通过
位与
运算,判断Block_descriptor_2
是否存在:
存在,再偏移Block_descriptor_2
大小,即:Block_descriptor_3的首地址
不存在,强转为Block_descriptor_3
的结构体指针返回
小结:
- block的
Block_descriptor_1
相关属性是必然存在,其中reserved
为保留字段,size
为block的大小 -
Block_descriptor_3
是可选的参数,而这里就通过flags
字段来判断block是否存在Block_descriptor_3
的相关属性 -
Block_descriptor
的get方法可以发现,通过地址平移
的方式获取对应的值,并且在获取Block_descriptor_3
时会判断Block_descriptor_2
是否存在,如果不存在,就不需要添加Block_descriptor_2
的地址空间
lldb验证descriptor
image.png
block的捕获变量生命周期
在探索block的捕获变量生命周期之前,这里有几个疑问?
-
block
是如何捕获外部变量的?__block
捕获变量又是什么呢? -
block
中的copy函数
和释放函数
又是什么呢? -
block三重拷贝
过程是怎样的?
查看转换后的cpp文件
中__ViewController__viewDidLoad_block_desc_0
结构体的定义
// @implementation ViewController
......
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->objc1, (void*)src->objc1, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->objc1, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};
......
// @end
- 该结构体即对应源码中的
__ViewController__viewDidLoad_block_desc_0
信息,其中reserved
和size
对应Block_descriptor_1
的两个属性 - 另外
void (*copy)
和void (*dispose)
对应Block_descriptor_2
的两个方法 - 在copy方法的实现中,会调用
_Block_object_assign
,此过程即为外部变量
的捕获
和释放
过程
在libclosure-79
源码中全局搜索_Block_object_assign
得到以下注释信息
The flags parameter of _Block_object_assign and _Block_object_dispose is set to
* BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object,
* BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and
* BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable.
If the __block variable is marked weak the compiler also or's in BLOCK_FIELD_IS_WEAK (16)
So the Block copy/dispose helpers should only ever generate the four flag values of 3, 7, 8, and 24.
_Block_object_assign
和_Block_object_dispose
的flags参数设置为:
BLOCK_FIELD_IS_OBJECT (3)
,捕获Objective-C Object
的情况
BLOCK_FIELD_IS_BLOCK (7)
,捕获另一个block
的情况
BLOCK_FIELD_IS_BYREF (8)
,捕获__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修饰的变量;
BLOCK_FIELD_IS_WEAK
:weak弱引用变量;
BLOCK_BYREF_CALLER
:返回的调用对象,处理block_byref
内部对象内存会增加一个额外标记,配合flags一起使用。
- 查看
_Block_object_assign
函数
// 当 block 和 byref 要持有对象时,它们的 copy helper 函数会调用这个函数来完成 assignment
// 参数 destAddr 其实是一个二级指针,指向真正的目标指针
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:
/*******
id object = ...;
[^{ object; } copy];
********/
// _Block_retain_object_default = fn (arc)
// 默认什么都不干,但在 _Block_use_RR() 中会被 Objc runtime 或者 CoreFoundation 设置 retain 函数
// 其中,可能会与 runtime 建立联系,操作对象的引用计数什么的
// 可以理解为交给系统 ARC 处理
_Block_retain_object(object);
// 使 dest 指向的目标指针指向
object *dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
// 使 dest 指向拷贝到堆上object
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
// 使 dest 指向拷贝到堆上的byref
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
// 使 dest 指向的目标指针指向 object
*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:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
// 使 dest 指向的目标指针指向 object
*dest = object;
break;
default:
break;
}
}
- 普通对象类型,交给
系统ARC
处理,使dest
指向的目标指针指向object
-
Block类型
作为变量,调用_Block_copy
函数,使dest
指向拷贝到堆上object
- 使用
__block
修饰的变量,调用_Block_byref_copy
函数,使dest
指向拷贝到堆上的byref
- 查看
_Block_byref_copy
函数
// 1. 如果 byref 原来在堆上,就将其拷贝到堆上,拷贝的包括 Block_byref、Block_byref_2、Block_byref_3
// 被 __weak 修饰的 byref 会被修改 isa 为 _NSConcreteWeakBlockVariable
// 原来 byref 的 forwarding 也会指向堆上的 byref;
// 2. 如果 byref 已经在堆上,就只增加一个引用计数。
static struct Block_byref *_Block_byref_copy(const void *arg) {
// arg 强转为 Block_byref * 类型
struct Block_byref *src = (struct Block_byref *)arg;
// 引用计数等于 0
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
// 为新的 byref 在堆中分配内存
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
// 新 byref 的 flags 中标记了它是在堆上,且引用计数为 2。
// 为什么是 2 呢?注释说的是 non-GC one for caller, one for stack
// one for caller 很好理解,那 one for stack 是为什么呢?
// 看下面的代码中有一行 src->forwarding = copy。src 的 forwarding 也指向了 copy,相当于引用了
copy copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
// 堆上 byref 的 forwarding 指向自己
copy->forwarding = copy; // patch heap copy to point to itself
// 原来栈上的 byref 的 forwarding 现在也指向堆上的 byref
src->forwarding = copy; // patch stack to point to heap copy
// 拷贝 size
copy->size = src->size;
// 如果 src 有 copy/dispose helper
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
// 取得 src 和 copy 的 Block_byref_2
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
// copy 的 copy/dispose helper 也与 src 保持一致
// 因为是函数指针,估计也不是在栈上,所以不用担心被销毁
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
// 如果 src 有扩展布局,也拷贝扩展布局
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);
// 没有将 layout 字符串拷贝到堆上,是因为它是 const 常量,不在栈上
copy3->layout = src3->layout;
}
// 调用 copy helper,因为 src 和 copy 的 copy helper 是一样的,所以用谁的都行,调用的都是同一个函数
// 发起第三层拷贝
(*src2->byref_keep)(copy, src);
} else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
// 如果 src 没有 copy/dispose helper
// 将 Block_byref 后面的数据都拷贝到 copy 中,一定包括 Block_byref_3
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
// src 已经在堆上,就只将引用计数加 1
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
- 将
外部对象
封装成结构体Block_byref *src
- 如果是
BLOCK_FIELD_IS_BYREF
,则会调用malloc
生成一个Block_byref *copy
- 设置
forwarding
保证block内部
和外部
都指向同一个对象
copy->forwarding = copy;
src->forwarding = copy;
-
Block_byref
中keep
函数和destroy
处理,并进行byref_keep
函数的调用
Block_byref
的设计思路和Block_layout
中descriptor
流程类似,通过byref->flag
标识码判断对应的属性,以此来判断Block_byref_2
是否存在
- 查看
Block_byref
结构体定义
// 结构体
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
修饰了外部变量
,编译生成的cpp文件中
,Block_byref
结构体中就会默认生成两个方法,即对应Block_byref_2
的keep
方法和destory
方法
在cpp文件
中搜索这两个函数的实现如下图
此过程会再次调用_Block_object_assign
函数,对Block_byref
结构体中的对象进行BLOCK_FIELD_IS_OBJECT
流程处理
至此block的三重拷贝流程如下
-
_Block_copy
函数,负责Block对象
的自身拷贝,从栈区
拷贝到堆区
-
_Block_byref_copy
函数,将Block_byref
拷贝到堆区
-
_Block_object_assign
函数,将捕获的外界变量
拷贝到堆区
Block释放
- 查看
_Block_release
函数
// block 在堆上,才需要 release,在全局区和栈区都不需要 release.
// 先将引用计数减 1,如果引用计数减到了 0,就将 block 销毁
void _Block_release(const void *arg) {
struct Block_layout *aBlock = (struct Block_layout *)arg;
// 如果 block == nil
if (!aBlock) return;
// 如果 block 在全局区
if (aBlock->flags & BLOCK_IS_GLOBAL) return;
// block 不在堆上
if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
// 引用计数减 1,如果引用计数减到了 0,会返回 true,表示 block 需要被销毁
if (latching_decr_int_should_deallocate(&aBlock->flags)) {
// 调用 block 的 dispose helper,dispose helper 方法中会做诸如销毁 byref 等操作
_Block_call_dispose_helper(aBlock);
// _Block_destructInstance 啥也不干,函数体是空的
_Block_destructInstance(aBlock); free(aBlock);
}
}
与_Block_copy
相似,通过_Block_call_dispose_helper
函数调用_Block_object_dispose
函数
- 查看
_Block_object_dispose
函数
// 当 block 和 byref 要 dispose 对象时,它们的 dispose helper 会调用这个函数
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
// 如果是 byref
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
// 对 byref 对象做 release 操作
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
// 对 block 做 release 操作
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
// 默认啥也不干,但在 _Block_use_RR() 中可能会被 Objc runtime 或者 CoreFoundation 设置一个 release 函数,里面可能会涉及到 runtime 的引用计数
_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;
}
}
- 普通对象类型,交给
系统ARC
处理 -
Block
类型作为变量,调用_Block_release
函数 - 使用
__block
修饰的变量,调用_Block_byref_release
函数,对byref对象做release操作
- 查看
_Block_byref_release
函数
// 对 byref 对象做 release 操作,
// 堆上的 byref 需要 release,栈上的不需要 release,
// release 就是引用计数减 1,如果引用计数减到了 0,就将 byref 对象销毁
static void _Block_byref_release(const void *arg) {
struct Block_byref *byref = (struct Block_byref *)arg;
// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
// 取得真正指向的 byref,如果 byref 已经被堆拷贝,则取得是堆上的 byref,否则是栈上的,栈上的不需要 release,也没有引用计数
byref = byref->forwarding;
// byref 被拷贝到堆上,需要 release
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
// 取得引用计数
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
// 引用计数减 1,如果引用计数减到了 0,会返回 true,表示 byref 需要被销毁
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);
// dispose helper 藏在 Block_byref_2 里
(*byref2->byref_destroy)(byref);
}
free(byref);
}
}
}
与_Block_byref_copy
相似,由byref_destroy
发起对象的release
- 对应
cpp代码
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
- 调用
_Block_object_dispose
函数,传入objc实例对象
- 进入
_Block_object_dispose
函数,命中普通对象类型的release
逻辑