1. Block类型
在日常开发中,Block
分为三种类型:
GlobalBlock
位于全局区
在
Block
内部不能捕获外部变量,或只使用静态变量或全局变量
MallocBlock
位于堆区
在
Block
内部使用局部变量或OC
属性,并且赋值给强引用或Copy
修饰的变量
StackBlock
位于栈区
与
MallocBlock
一样,可以在内部使用局部变量或OC
属性。但不能赋值给强引用或Copy
修饰的变量
1.1 GlobalBlock
全局Block
,不能捕获外部变量,但可以使用静态变量或全局变量
int age;
- (void)viewDidLoad {
[super viewDidLoad];
static int number;
void (^block)(void) = ^{
int a = 0;
NSLog(@"%d - %d - %d", number, age, a);
};
NSLog(@"%@",block);
}
-------------------------
//输出以下内容:
<__NSGlobalBlock__: 0x1047900f8>
- 使用静态变量、全局变量、
Block
内部声明的变量,都没有问题
1.2 MallocBlock
堆区Block
,内部使用局部变量或OC
属性,并且赋值给强引用或Copy
修饰的变量
- (void)viewDidLoad {
[super viewDidLoad];
int number = 0;
void (^block)(void) = ^{
NSLog(@"%d", number);
};
NSLog(@"%@",block);
}
-------------------------
//输出以下内容:
<__NSMallocBlock__: 0x281487930>
- 对外部的
number
变量进行捕获 - 赋值给强引用的
block
- 其中
block
持有的是堆区Block
的内存地址
1.3 StackBlock
栈区Block
,和堆区Block
的使用大致相同,区别在于不能赋值给强引用或Copy
修饰的变量
- (void)viewDidLoad {
[super viewDidLoad];
int number = 0;
void (^__weak block)(void) = ^{
NSLog(@"%d", number);
};
NSLog(@"%@",block);
}
-------------------------
//输出以下内容:
<__NSStackBlock__: 0x16b5e0f68>
- 同样对外部的
number
变量进行捕获 - 和堆区
Block
的区别:赋值给弱引用的block
2. Block案例
2.1 Block
的内存拷贝
- (void)blockDemo{
int a = 0;
void(^__weak weakBlock)(void) = ^{
NSLog(@"a:%d", a);
};
struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
id __strong strongBlock = weakBlock;
NSLog(@"weakBlock:%@",weakBlock);
NSLog(@"strongBlock:%@",strongBlock);
blc->invoke = nil;
void(^strongBlock1)(void) = strongBlock;
NSLog(@"strongBlock1:%@",strongBlock1);
strongBlock1();
}
-------------------------
//输出以下内容:
weakBlock:<__NSStackBlock__: 0x16f6b4f48>
strongBlock:<__NSStackBlock__: 0x16f6b4f48>
strongBlock1:<__NSMallocBlock__: 0x283a48ff0>
程序闪退,坏地址访问(EXC_BAD_ACCESS)
代码解读:
第一步,
weakBlock
为栈区Block
第二步,按
Block
的底层源码,自定义_LGBlock
结构体。只要内存结构一致,即可将Block
桥接为自定义对象第三步,
Block
的本质是结构体,将结构体首地址赋值给__strong
修饰的对象第四步,将结构体的
invoke
置空,即:Block
的函数指针第五步,将
strongBlock
赋值给强引用的strongBlock1
,然后对其进行调用
闪退原因:
在第三步中,将结构体首地址赋值给对象,二者指向相同内存空间
在第四步中,将结构体的
invoke
置空,修改的是同一片内存空间在第五步中,将
invoke
置空后的Block
赋值给strongBlock1
,调用时坏地址访问,程序闪退
修改案例:
- (void)blockDemo{
int a = 0;
void(^__weak weakBlock)(void) = ^{
NSLog(@"a:%d", a);
};
struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
void(^strongBlock)(void) = weakBlock;
NSLog(@"weakBlock:%@",weakBlock);
NSLog(@"strongBlock:%@",strongBlock);
blc->invoke = nil;
strongBlock();
}
-------------------------
//输出以下内容:
weakBlock:<__NSStackBlock__: 0x16d220f48>
strongBlock:<__NSMallocBlock__: 0x2829d2d60>
程序闪退,坏地址访问(EXC_BAD_ACCESS)
- 先将栈区
Block
赋值给强引用的Block
,然后将invoke
置空,但依然闪退
闪退原因:Block
的赋值只是进行了浅拷贝,相当于对指向对象的指针进行复制,产生一个新的指向对象的指针,但两个指针依然指向同一个对象
解决办法,必须在invoke
置空之前,将Block
进行深拷贝
- (void)blockDemo{
int a = 0;
void(^__weak weakBlock)(void) = ^{
NSLog(@"a:%d", a);
};
struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
void(^strongBlock)(void) = [weakBlock copy];
NSLog(@"weakBlock:%@",weakBlock);
NSLog(@"strongBlock:%@",strongBlock);
blc->invoke = nil;
NSLog(@"invoke置空");
strongBlock();
}
-------------------------
//输出以下内容:
weakBlock:<__NSStackBlock__: 0x16d3acf48>
strongBlock:<__NSMallocBlock__: 0x28029adf0>
invoke置空
a:0
使用lldb
,观察两个Block
在invoke
置空前后的变化
- 深拷贝相当于将对象进行复制,产生一个新的对象。并且会递归复制每个指针类型的实例变量,直到两个对象没有任何公共的部分
所以,栈区Block
将invoke
置空,并不影响堆区Block
的调用
2.2 对外部变量的引用计数处理
- (void)blockDemo{
NSObject *objc = [NSObject new];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
void(^strongBlock)(void) = ^{
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
strongBlock();
void(^__weak weakBlock)(void) = ^{
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
weakBlock();
void(^mallocBlock)(void) = [weakBlock copy];
mallocBlock();
}
//输出以下内容:
1
3
4
5
代码解读:
第一步,
objc
初始化后的打印1
,没有任何问题第二步,在
strongBlock
中打印3
,拆分成两步进行分析
1、外部objc
变量,被栈区Block
捕获,引用计数+1
2、栈区Block
赋值给强引用的strongBlock
,将栈区Block
拷贝到堆区,底层进行深拷贝,引用计数也会+1
第三步,赋值给弱引用的
weakBlock
,属于栈区Block
,仅对外部objc
变量进行捕获,引用计数+1
第四步,将栈区
Block
调用copy
方法,赋值给mallocBlock
,仅对栈区Block
进行了深拷贝,引用计数+1
其实第三步和第四步,等同于第二步的分解,所以打印结果:1、3、4、5
2.3 堆栈Block
的释放
- (void)blockDemo{
int a = 10;
void(^__weak weakBlock)(void) = nil;
{
void(^strongBlock)(void) = ^{
NSLog(@"a:%i", a);
};
weakBlock = strongBlock;
NSLog(@"weakBlock:%@",weakBlock);
NSLog(@"strongBlock:%@",strongBlock);
}
weakBlock();
}
-------------------------
//输出以下内容:
weakBlock:<__NSMallocBlock__: 0x280d7cba0>
strongBlock:<__NSMallocBlock__: 0x280d7cba0>
程序闪退,坏地址访问(EXC_BAD_ACCESS)
代码解读:
第一步,
weakBlock
使用__weak
修饰,赋值为nil
第二步,定义代码块,实现一个堆区
Block
第三步,将堆区
Block
赋值给代码块外面的weakBlock
第四步,在代码块执行完毕后,调用
weakBlock
闪退原因:
在第三步中,堆区
Block
赋值__weak
修饰的weakBlock
,相当于映射关系在第四步中,当代码块执行完毕,
strongBlock
由于是堆区Block
,出了代码块就会被释放。作为映射的weakBlock
,自然也会被置为nil
。此时对其进行调用,出现坏地址访问,程序闪退
修改案例:
- (void)blockDemo{
int a = 10;
void(^__weak weakBlock)(void) = nil;
{
void(^__weak strongBlock)(void) = ^{
NSLog(@"a:%i", a);
};
weakBlock = strongBlock;
NSLog(@"weakBlock:%@",weakBlock);
NSLog(@"strongBlock:%@",strongBlock);
}
weakBlock();
}
-------------------------
//输出以下内容:
weakBlock:<__NSStackBlock__: 0x16f6c4f40>
strongBlock:<__NSStackBlock__: 0x16f6c4f40>
a:10
- 将
strongBlock
使用__weak
修饰,即可正常打印
当strongBlock
使用__weak
修饰后,成为栈区Block
。将其赋值给__weak
修饰的weakBlock
,此时依然是栈区Block
。栈区Block
的生命周期与代码块无关,依赖于函数栈帧,所以可以正常打印
3. Block拷⻉到堆区
如果Block
为全局Block
,使用任何方式都不会拷贝到堆区,即使手动copy
也没用,它依然是全局Block
- (void)blockCopy {
void (^block)(void) = ^{
NSLog(@"全局Block");
};
NSLog(@"%@", block);
NSLog(@"%@", [block copy]);
}
-------------------------
//输出以下内容:
<__NSGlobalBlock__: 0x1009e00f8>
<__NSGlobalBlock__: 0x1009e00f8>
除此之外,以下四种操作,系统会将Block
复制到堆上:
手动
Copy
Block
作为返回值被强引用或
Copy
修饰系统
API
包含usingBlock
3.1 Block
作为返回值
由于栈区Block
所属的变量域一旦结束,那么该Block
就会被销毁。在ARC
环境下,编译器会自动的判断,把Block
自动的从栈区copy
到堆区。例如:当Block
作为函数返回值的时候,肯定会copy
到堆区
3.2 系统API
包含usingBlock
当Block
为函数参数时,需要将其手动copy
到堆区。但系统API
我们不需要处理,比如GCD
中携带的usingBlock
方法。其他自定义方法传递Block
为参数时,都需要进行手动copy
4. 循环引用
对比以下两种Block
代码:
self.name = @"lg_cooci";
//Block1
self.block = ^{
NSLog(@"%@",self.name);
};
//Block2
[UIView animateWithDuration:1 animations:^{
NSLog(@"%@",self.name);
}];
Block1
会出现循环引用,因为block
被self
持有,而block
中使用self
,所以self
又被block
持有。这种相互持有的情况下,就会出现循环引用Block2
不会出现循环引用,因为block
的持有者是UIView,和self
无关,不会出现相互持有的情况,所以不会循环引用
对象正常释放的过程:
- 当
A
持有B
,B
的retainCount
进行+1
- 当
A
触发dealloc
时,会给B
放信号,B
的retainCount
进行-1
。此时B的retainCount
如果为0
,就会调用dealloc
,正常释放
对象循环引用的过程:
- 当
A
和B
相互持有时,A
的dealloc
无法触发,因为A
要等B
放信号才能对retainCount
进行-1
- 但是
B
的dealloc
也无法触发,因为B
也在等待A
的信号。此时A
和B
都在等待对方的释放,最终出现循环引用
避免循环引用的方式:
weak-strong-dance
Block
中强行切断持有者将持有者作为
Block
参数进行传递和使用
4.1 weak-strong-dance
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
NSLog(@"%@",strongSelf.name);
};
- 将
self
赋值给__weak
修饰的weakSelf
,此时weakSelf
属于self
的映射,指向同一片内存空间,并且self
的引用计数不会发生变化 - 在
Block
中将weakSelf
赋值给__strong
修饰的strongSelf
,避免self
提前释放导致访问为nil
的情况 - 因为
strongSelf
为临时变量,在Block
作用域结束后,即可自动释放,因此不会循环引用
4.2 Block
中强行切断持有者
__block ViewController *vc = self;
self.block = ^{
NSLog(@"%@",vc.name);
vc = nil;
};
self.block();
- 使用
__block
修饰对象,否则vc
无法改变,也就是说无法置为nil
- 在
Block
中使用结束,手动将对象置为nil
。相当于手动切断持有关系,可以避免循环引用 - 缺陷:这种方式
Block
必须调用,否则将无法手动切断持有关系,self
和block
都无法释放,最终出现循环引用
4.3 将持有者作为Block
参数进行传递和使用
self.vcBlock = ^(ViewController *vc){
NSLog(@"%@",vc.name);
};
self.vcBlock(self);
- 将
self
作为参数,提供给Block
内部使用。当Block
执行结束,vc
会自动释放,然后相互持有关系的切断,self
也会释放 - 这种方式,
self
的是否依赖于Block
的执行结束。如果Block
中有延迟执行的代码,self
的释放也会延迟
4.4 面试题
以下几个案例,是否会出现循环引用?
案例1:
static ViewController *staticSelf_;
- (void)blockWeak_static {
__weak typeof(self) weakSelf = self;
staticSelf_ = weakSelf;
}
[self blockWeak_static];
- 会出现循环引用
- 将
self
赋值__weak
修饰的对象,它们属于映射关系,指向同一片内存空间。当weakSelf
赋值全局静态变量,staticSelf_
在程序运行过程中不会主动释放,它会持续持有self
,所以self
也无法释放
案例2:
- (void)block_weak_strong {
__weak typeof(self) weakSelf = self;
self.doWork = ^{
__strong typeof(self) strongSelf = weakSelf;
weakSelf.doStudent = ^{
NSLog(@"%@", strongSelf);
};
weakSelf.doStudent();
};
self.doWork();
}
[self block_weak_strong];
- 会出现循环引用
- 在
doWork
内部,strongSelf
持有的是self
。虽然strongSelf
是临时变量,但在doStudent
中又被持有,导致引用计数+1
。在doWork
执行完毕后引用计数-1
,但doStudent
中的持有还存在,所以会出现循环引用
5. cpp分析
5.1 Block
本质
创建block.c
文件,写入以下代码:
#include "stdio.h"
int main(){
void(^block)(void) = ^{
printf("LG");
};
block();
return 0;
}
- 使用
.c
文件,生成的cpp
代码更加干净简洁
使用xcrun
命令,生成block.cpp
文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc block.c -o block.cpp
打开block.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
- 调用
block
的FuncPtr
函数,传入block
- 调用
找到__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;
}
};
//参数1
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("LG");
}
//参数2
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)};
不难看出,
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
中的代码块
5.2 捕获外界变量
打开block.c
文件,改为以下代码:
#include "stdio.h"
int main(){
int a = 18;
void(^block)(void) = ^{
printf("LG - %d",a);
};
block();
return 0;
}
生成block.cpp
文件,找到main
函数
int main(){
int a = 18;
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
函数的入参变成三个 - 增加的第三个参数为外界变量
a
找到__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;
}
};
//参数1
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("LG - %d",a);
}
//参数2
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)};
结构体中的成员变量也发生了变化,当捕获外界变量时,在结构体内部,会生成相应的成员变量用来存储
成员变量
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
5.3 __block
的作用
打开block.c
文件,改为以下代码:
#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(){
__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
结构体。成员变量2
,对结构体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
的值 - 结构体中存储了自身的地址和值
找到__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;
}
};
//参数1
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));
}
//参数2
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*/);}
局部变量
a
和__cself->a
指针地址相同,它的值一旦改变,相当于对外界变量的值进行修改没有
__block
修饰,属于值拷贝,也就是深拷贝。拷贝的值不可更改,它们指向不同的内存空间使用
__block
修饰,属于地址拷贝,也就是浅拷贝。生成的对象指向同一片内存空间,内部修改等同于对外界变量的修改
6. 汇编分析
6.1 流程分析
搭建App
项目,写入以下代码:
- (void)viewDidLoad {
[super viewDidLoad];
__block NSObject *objc = [NSObject alloc];
void (^block)(void) = ^{
NSLog(@"LG_Block %@ ",objc);
};
block();
}
针对block
的定义设置断点,运行项目,查看汇编代码
在项目中,设置objc_retainBlock
符号断点
- 来自
libobjc.A.dylib
框架
打开objc4-818.2
源码,找到objc_retainBlock
函数
id objc_retainBlock(id x) {
return (id)_Block_copy(x);
}
- 调用
_Block_copy
函数,但是objc
源码中找不到它的实现
在项目中,设置_Block_copy
符号断点
来自libsystem_blocks.dylib
框架,但该框架暂未开源,可以在libclosure
替代工程中查看
6.2 Block
结构
通过cpp
分析,Block
的本质是结构体。在libclosure-79
源码中,来到_Block_copy
函数,可以找到Block
的真实类型:Block_layout
找到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
};
isa
:标示Block
类型的类flags
:标识符,按bit
位表示Block
的附加信息,类似于isa
中的位域reserved
:预留位置invoke
:函数指针,指向Block
实现的调用地址descriptor
:附加信息,例如:存储保留变量数、Block
的大小、进行copy
或dispose
的函数指针
找到flags
的标示:
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
};
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
6.3 运行时Copy
进入_Block_copy
函数的汇编代码,读取x0寄存器的值,并对其进行打印
- 进入
_Block_copy
函数,当前Block
标记为StackBlock
直接运行到函数结尾,读取返回值x0
寄存器,并对其进行打印
- 程序运行时, 当前
Block
符合MallocBlock
的条件,经过_Block_copy
函数,会将Block
复制到堆区
6.4 Block
调用
_Block_copy
函数执行完毕,回到viewDidLoad
方法,继续进行Block
的调用
ldr x8, [x0, #0x10]
:x0
为当前Block
,读取x0 + 16字节
的值,赋值给x8
Block_layout
结构体中,首地址偏移16字节
,相当于跳过isa
、flags
和reserved
,读取到invoke
函数地址,赋值给x8
blr x8
:跳转到invoke
函数地址,相当于Block
的调用
6.5 descriptor
当_Block_copy
函数执行完毕,打印当前Block
为MallocBlock
- 同时还打印出
signature
、copy
、dispose
等数据
6.5.1 descriptor
类型
signature
、copy
、dispose
等数据,存储在Block_layout
结构体的descriptor
中
descriptor
分为三种类型:
Block_descriptor_1
Block_descriptor_2
Block_descriptor_3
其中Block_descriptor_1
一定存在,Block_descriptor_2
和Block_descriptor_3
为可选
找到descriptor
的定义:
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
#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_1
:存储预留字段和Block
大小Block_descriptor_2
:存储copy
和dispose
的函数指针Block_descriptor_3
:存储signature
签名和layout
Block_descriptor_1
的读取:
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;
}
- 由于
Block_descriptor_1
一定存在,直接通过Block
的descriptor
读取
Block_descriptor_2
的读取:
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
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
的读取:
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
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_descriptor_2
和Block_descriptor_3
的内存结构相同,所以偏移Block_descriptor_1
之后的地址,即可强转为2
,可强转为3
6.5.2 lldb
验证descriptor
通过flags
进行位与运算,可以得知Block_descriptor_2
和Block_descriptor_3
是否存在
使用x/8g
命令,输出Block
的内存结构
(lldb) x/8g 0x283aaa430
0x283aaa430: 0x00000001de1c0880 0x00000000c3000002
0x283aaa440: 0x00000001021422ac 0x0000000102144018
0x283aaa450: 0x0000000283aaa400 0x0000000000000000
0x283aaa460: 0x000021a1de1c6221 0x0000000000000000
0xc3000002
:Block
的flags
标识符
验证Block_descriptor_2
:
//BLOCK_HAS_COPY_DISPOSE = (1 << 25)
(lldb) p/x 1 << 25
(int) $7 = 0x02000000
(lldb) p/x (0xc3000002 & 0x02000000)
(unsigned int) $8 = 0x02000000
- 运算结果不为
0
,证明Block_descriptor_2
存在
验证Block_descriptor_3
:
//BLOCK_HAS_SIGNATURE = (1 << 30)
(lldb) p/x 1 << 30
(int) $10 = 0x40000000
(lldb) p/x (0xc3000002 & 0x40000000)
(unsigned int) $16 = 0x40000000
- 运算结果不为
0
,证明Block_descriptor_3
存在
6.6 Block
签名
6.6.1 lldb
验证签名
使用x/8g
命令,输出Block
的内存结构
(lldb) x/8g 0x283aaa430
0x283aaa430: 0x00000001de1c0880 0x00000000c3000002
0x283aaa440: 0x00000001021422ac 0x0000000102144018
0x283aaa450: 0x0000000283aaa400 0x0000000000000000
0x283aaa460: 0x000021a1de1c6221 0x0000000000000000
- 首地址平移
24字节
,0x0000000102144018
就是descriptor
的结构体指针
使用x/8g
命令,输出descriptor
的内存结构
(lldb) x/8g 0x0000000102144018
0x102144018: 0x0000000000000000 0x0000000000000028
0x102144028: 0x00000001021422e0 0x00000001021422f0
0x102144038: 0x00000001021433e3 0x0000000000000010
0x102144048: 0x00000001de78a280 0x00000000000007c8
0x00000001021422e0
:copy
函数指针0x00000001021422f0
:dispose
函数指针0x00000001021433e3
:signature
签名
对0x00000001021433e3
进行强转输出
(lldb) po (char *)0x00000001021433e3
"v8@?0"
6.6.2 签名含义
对于签名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字节
7. 源码分析
使用替代工程libclosure
进行源码分析:
当Block
捕获使用__block
修饰的对象,底层会触发Block
的三层拷贝
7.1 _Block_copy
进入_Block_copy
函数
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
// 如果 arg 为 NULL,直接返回 NULL
if (!arg) return NULL;
// The following would be better done as a switch statement
// 强转为 Block_layout 类型
aBlock = (struct Block_layout *)arg;
// 如果现在已经在堆上
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
// 就只将引用计数加 1
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {// 栈 - 堆 (编译期)
// Its a stack block. Make a copy.
// block 现在在栈上,现在需要将其拷贝到堆上
// 在堆上重新开辟一块和 aBlock 相同大小的内存
size_t size = Block_size(aBlock);
struct Block_layout *result = (struct Block_layout *)malloc(size);
// 开辟失败,返回 NULL
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)
if (aBlock->flags & BLOCK_SMALL_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
// 将 flags 中的 BLOCK_REFCOUNT_MASK 和 BLOCK_DEALLOCATING 部分的位全部清为 0
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
// 将 result 标记位在堆上,需要手动释放;并且引用计数初始化为 1
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
// copy 方法中会调用做拷贝成员变量的工作
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
// isa 指向 _NSConcreteMallocBlock
result->isa = _NSConcreteMallocBlock;
return result;
}
}
_Block_copy
函数,负责Block
对象的自身拷贝,从栈区拷贝到堆区参数
arg
就是Block_layout
对象如果原来就在堆上,就将引用计数
+1
如果
Block
在全局区,不用加引用计数,也不用拷贝,直接返回Block
本身如果原来在栈上,会拷贝到堆上,引用计数初始化为
1
,并且会调用_Block_call_copy_helper
方法(如果存在的话)返回值是拷贝后
Block
的地址
7.2 _Block_object_assign
7.2.1 cpp
分析
打开cpp
文件,找到声明Block
时的第二个参数,__main_block_desc_0_DATA
结构体的定义:
//void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
__main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
__main_block_copy_0
:Block_descriptor_2
中的copy
__main_block_dispose_0
:Block_descriptor_2
中的dispose
找到__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->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}
- 调用
_Block_object_assign
函数
7.2.2 lldb
分析
使用x/8g
命令,输出Block
的内存结构
(lldb) x/8g 0x000000028167eca0
0x28167eca0: 0x00000001de1c0880 0x00000000c3000002
0x28167ecb0: 0x0000000104f1a2ac 0x0000000104f1c018
0x28167ecc0: 0x000000028167ec70 0x0000000000000000
0x28167ecd0: 0x000021a1de1c6221 0x0000000000000000
使用x/8g
命令,输出descriptor
的内存结构
(lldb) x/8g 0x0000000104f1c018
0x104f1c018: 0x0000000000000000 0x0000000000000028
0x104f1c028: 0x0000000104f1a2e0 0x0000000104f1a2f0
0x104f1c038: 0x0000000104f1b3e3 0x0000000000000010
0x104f1c048: 0x00000001de78a280 0x00000000000007c8
0x0000000104f1a2e0
:copy
函数指针
使用dis -s
读取汇编代码
(lldb) dis -s 0x0000000104f1a2e0
004-Block结构与签名`__copy_helper_block_e8_32r:
0x104f1a2e0 <+0>: add x0, x0, #0x20 ; =0x20
0x104f1a2e4 <+4>: ldr x1, [x1, #0x20]
0x104f1a2e8 <+8>: mov w2, #0x8
0x104f1a2ec <+12>: b 0x104f1a484 ; symbol stub for: _Block_object_assign
- 调用
_Block_object_assign
函数
7.2.3 调用时机
在_Block_copy
函数中,调用_Block_call_copy_helper
函数
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
if (auto *pFn = _Block_get_copy_function(aBlock))
pFn(result, aBlock);
}
进入_Block_get_copy_function
函数
static inline __typeof__(void (*)(void *, const void *))
_Block_get_copy_function(struct Block_layout *aBlock)
{
if (!(aBlock->flags & BLOCK_HAS_COPY_DISPOSE))
return NULL;
void *desc = _Block_get_descriptor(aBlock);
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
struct Block_descriptor_small *bds =
(struct Block_descriptor_small *)desc;
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));
return _Block_get_copy_fn(bd2);
}
- 如果存在
copy
和dispose
,通过内存获取Block_descriptor_2
的结构体指针 - 调用
_Block_get_copy_fn
函数,传入bd2
进入_Block_get_copy_fn
函数
static inline __typeof__(void (*)(void *, const void *))
_Block_get_copy_fn(struct Block_descriptor_2 *desc)
{
return (void (*)(void *, const void *))_Block_get_function_pointer(desc->copy);
}
- 经过处理返回
Block_descriptor_2
下的copy
函数
最终,回到_Block_call_copy_helper
函数,将copy
函数地址赋值pFn
,然后通过pFn(result, aBlock)
对其进行调用
7.2.4 源码分析
在源码中,找到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
7.3 _Block_byref_copy
进入_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
进行拷贝,属于三层拷贝中的第二层如果引用计数为
0
,为新的byref
在堆中分配内存将堆上
byref
的forwarding
指向自己将原来栈上的
byref
的forwarding
也指向堆上的byref
由
byref_keep
发起Block
的第三层拷贝
如果已经在堆上,就只将引用计数
+1
7.3.1 byref_keep
分析
找到byref_keep
的定义:
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep;
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
对应cpp
文件查看:
struct __Block_byref_objc_0 {
void *__isa;
__Block_byref_objc_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *objc;
};
- 源码中的
byref_keep
,对应cpp
中的__Block_byref_id_object_copy
找到__Block_byref_id_object_copy
的赋值
__Block_byref_objc_0 objc = {
(void*)0,
(__Block_byref_objc_0 *)&objc,
33554432,
sizeof(__Block_byref_objc_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"))
};
- 传入
__Block_byref_id_object_copy_131
找到__Block_byref_id_object_copy_131
的定义:
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
对应源码查看传入的参数:
(*src2->byref_keep)(copy, src);
- 传入堆上的
Block_byref
,对应cpp
中的__Block_byref_objc_0
结构体
7.3.2 byref_keep
的结论
调用byref_keep
,底层又调用一次_Block_object_assign
函数
(char*)dst + 40
参数,结构体内存平移,传入的就是objc
实例对象
进入_Block_object_assign
函数,命中普通对象类型的copy
逻辑
将对象拷贝到堆区,完成Block
的第三层拷贝
7.4 Block
三层拷贝的结论
当Block
捕获使用__block
修饰的对象,底层会触发Block
的三层拷贝
【第一层】
_Block_copy
函数,负责Block
对象的自身拷贝,从栈区拷贝到堆区通过
_Block_copy
→_Block_call_copy_helper
,调用_Block_object_assign
函数传入
Block_byref
结构体指针,类型为BLOCK_FIELD_IS_BYREF
,调用_Block_byref_copy
函数
【第二层】
_Block_byref_copy
函数,将Block_byref
拷贝到堆区- 通过
byref_keep
函数,调用_Block_object_assign
函数,传入objc
实例对象
- 通过
【第三层】
_Block_object_assign
函数,将捕获的外界变量拷贝到堆区
7.5 Block
释放
如果Block
在堆上,需要进行release
。在全局区和栈区的Block
,都不需要release
7.5.1 _Block_release
进入_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
函数
7.5.2 _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
操作
7.5.3 _Block_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
逻辑
8. 总结
Block
类型:
GlobalBlock
位于全局区
在
Block
内部不能捕获外部变量,或只使用静态变量或全局变量
MallocBlock
位于堆区
在
Block
内部使用局部变量或OC
属性,并且赋值给强引用或Copy
修饰的变量
StackBlock
位于栈区
与
MallocBlock
一样,可以在内部使用局部变量或OC
属性。但不能赋值给强引用或Copy
修饰的变量
以下四种操作,系统会将Block
复制到堆上:
手动
Copy
Block
作为返回值被强引用或
Copy
修饰系统
API
包含usingBlock
避免Block
循环引用的方式:
weak-strong-dance
Block
中强行切断持有者将持有者作为
Block
参数进行传递和使用
当Block
捕获使用__block
修饰的对象,底层会触发Block
的三层拷贝:
【第一层】
_Block_copy
函数,负责Block
对象的自身拷贝,从栈区拷贝到堆区通过
_Block_copy
→_Block_call_copy_helper
,调用_Block_object_assign
函数传入
Block_byref
结构体指针,类型为BLOCK_FIELD_IS_BYREF
,调用_Block_byref_copy
函数
【第二层】
_Block_byref_copy
函数,将Block_byref
拷贝到堆区- 通过
byref_keep
函数,调用_Block_object_assign
函数,传入objc
实例对象
- 通过
【第三层】
_Block_object_assign
函数,将捕获的外界变量拷贝到堆区
Block
释放:
如果
Block
在堆上,需要进行release
。在全局区和栈区的Block
,都不需要release
释放流程和
Block
拷贝流程基本一致