iOS进阶笔记(四) Block - 原理篇
本章目录
一、BlocK对象本质分析
2、通过LLVM开源的block源码分析block结构
3、根据Block本质及变量捕获原理,通过Objective-C++实现block及变量捕获过程
4、小结
二、Block类型分析
1、Block类型及继承链
2、__NSGlobalBlock、__NSStackBlock、__NSMallocBlock所处内存区域分析
3、__NSGlobalBlock、__NSStackBlock、__NSMallocBlock的Copy操作分析
4、分别位于栈和堆上的Block与__block变量
5、小结
三、Block捕获变量及相应内存管理分析
1、全局变量
2、static变量
3、auto变量
1)值类型变量捕获
2)ObjC对象类型变量捕获
3)汇编查看值类型和对象类型区别
4、修改auto变量值的异常问题
1)在block块后面修改auto变量值
2)block块内部修改外部auto变量值,Xcode编译器报错分析
5、小结
四、__block修饰的auto变量的copy及dispose分析
1、__block修饰值类型变量
2、__block修饰对象类型变量
3、__block修饰__weak对象类型变量
4、__forwarding指针的用意
5、小结
五、Block的方法签名探索
1、普通Objc对象的Signature
2、Block的方法签名分析
1)从Block源码角度分析
2)从汇编角度分析
六、附录:Block相关源码解读
1、struct Block_layout
2、Block_layout的flags
3、descriptor
4、signature
5、Block的copy和dispose的参数
6、_Block_object_assign
7、_Block_copy
8、_Block_byref_copy
9、_Block_release
10、_Block_byref_release
一、BlocK对象本质分析
1、通过clang分析block结构
int a = 1;
void (^block)(void) = ^{
NSLog(@"a %d",a);// 结果 1
};
block();
clang编译后如下:clang -rewrite-objc main.m -o main.cpp
// block本质结构(定义)
struct __block_impl {
void *isa;// block内部也有isa指针,无疑是对象了
int Flags;// 标识,用来作标识符的(包括引用计数信息),类似于isa中的位域,按bit位表示一些block的附加信息
int Reserved;// 系统保留位,用于储存block内部变量信息
void *FuncPtr;// 函数指针,指向了封装block块内部代码的函数
};
// block实现
struct __Block_impl_0 {
struct __block_impl impl; // block实现
struct __Block_desc_0* Desc; // block的描述信息
int a ;// block 捕获的外部变量
// 结构体构造函数
__Block_impl_0(void *fp, struct __Block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;// 栈block
impl.Flags = flags;
impl.FuncPtr = fp;// 函数指针
Desc = desc;// 描述
}
};
// 封装了block块内部代码的函数(即block要执行的内容)
static void __block_func_0(struct __block_impl_0 *__cself) {
// 通过__cself指针传入a,赋值给了一个与外部变量a同名的内部变量a
int a = __cself->a; // bound by copy
}
// block描述信息
static struct __block_desc_0 {
size_t reserved;// 系统保留字段
size_t Block_size;// block大小
} __block_desc_0_DATA = { 0, sizeof(struct __block_impl_0)};// 这里直接进行了结构体初始化
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool;
int a = 1;
void (*block)(void) = ((void (*)())&__block_impl_0((void *)__block_func_0, &__block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
-
__block_impl
通过clang可以看出Block本质为
__block_impl
结构体(Objc对象),包含了指向其类型的isa指针、Flags(用来作标识符)、Reserved(系统保留位)及FuncPtr(封装block块内部代码的函数的指针)。
-
__Block_impl_0
__Block_impl_0结构体为是对Block的实现,是对其进一步封装。内部包含了Block、Block的描述信息及捕获的auto变量。
-
__block_func_0
该函数内容正是Block块真正执行的内容。在main函数中通过初始化
__Block_impl_0
结构体构造函数与__Block_impl_0->FunPtr
进行绑定。 -
__block_desc_0
保存Block的描述信息的结构体,如Block_size。
-
main函数
① __autoreleasepool
创建自动释放池对象,通过objc_autoreleasePoolPush()和objc_autoreleasePoolPop函数维护AutoreleasePoolPage。
② __Block_impl_0
void (block)(void) = ((void ()())&__Block_impl_0((void *)__block_func_0, &__block_desc_0_DATA, a))
- 定义一个名为block的函数指针,并指向__Block_impl_0结构体构地址。
- 第一个参数:block结构体。 在此过程中将__Block_func_0函数地址,通过__Block_impl_0结构体构造函数,传给了__Block_impl->FuncPtr。
- 第二个参数:block描述信息的结构体。将__Block_desc_0_DATA(包含Block_size信息)传给__Block_impl_0->Desc。
- 第三个参数:捕获的外部变量
a
。
Tips:这里clang翻译的C++代码,直接编译会报错,原因是__block_impl_0返回的是一个临时地址 (Taking the address of a temporary object of type '__block_impl_0')。本节下文会讲到解决办法。
③block的调用
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
-
这里由于
__Block_impl
为__Block_impl_0
结构体的第一成员,所以__Block_impl_0
和__Block_impl
地址相同,因此可以将block强转为__Block_impl
类型。 -
由于
block->FuncPtr
指向了__Block_func_0
函数,此时block->FuncPtr
类型为void*
,需要强转为((void (*)(__Block_impl *, int))
类型的函数指针,最后再调用__Block_func_0
函数。 -
以上可简写为:
block->FuncPtr(block);
可以看出此过程实际是函数指针调用,执行block所指向函数。FuncPtr函数指针指向了__block_func_0
函数,参数为block自身,以便__block_func_0
函数内部访问block捕获的外部变量。
- 小结:
通过clang可以知,block结构本质Objc对象的本质一致。Block结构本质为struct __block_impl
结构体,其主要成员成员为isa
和函数指针FuncPtr
(指向了封装block块内部代码的函数)。
2、通过LLVM开源的block源码分析block结构
-
Block源码中block的结构
struct Block_layout { // isa指针,通过isa可以找到block的类型 void * __ptrauth_objc_isa_pointer isa; // 用来作标识符的(包括引用计数信息),类似于isa中的位域,按bit位表示一些block的附加信息 volatile int32_t flags; // contains ref count // 保留信息,用于储存block内部变量信息 int32_t reserved; // 函数指针,指向包装了block所执行代码块的函数地址 // BlockInvokeFunction定义:typedef void(*BlockInvokeFunction)(void *, ...); BlockInvokeFunction invoke; // block描述信息( blockSize等信息) struct Block_descriptor_1 *descriptor; // 捕获的外部变量 };
对比block源码和clang结果,发现二者对block结构描述很相似。
3、根据Block本质及变量捕获原理,通过Objective-C++实现block及变量捕获过程
Objective-C代码:
@interface GGPerson : NSObject
@property (nonatomic, assign) int age;
@end
@implementation GGPerson
@end
void test()
{
__block GGPerson *p = [[GGPerson alloc] init];
p.age = 1;
void (^block)(void) = ^{
NSLog(@"age:%d",p.age);
};
// block调用
block();
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
}
return 0;
}
Objective-C++ 代码:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>
@interface GGPerson : NSObject
@property (nonatomic, assign) int age;
@end
@implementation GGPerson
@end
#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
// block对象本质
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// 堆
struct __Block_byref_p_0 {
void *__isa;
__Block_byref_p_0 *__forwarding;
int __flags;
int __size;
// 此处对p为强引用,所以需要对处于堆上的p对象进行内存管理(copy和dispose)
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
GGPerson *__strong p;
};
// 从栈拷贝到堆的block
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
__Block_byref_p_0 *p; // by ref
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, __Block_byref_p_0 *_p, int flags=0) : p(_p->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
__Block_byref_p_0 *p = __cself->p; // bound by ref
int age = p->__forwarding->p.age;
NSLog(@"age->%d",age);
// NSLog((NSString *)&__NSConstantStringImpl__var_folders_75_mphjrxws47b6srgd_j5ypg000000gp_T_ab4144_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)(p->__forwarding->p), sel_registerName("age")));
}
static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) {
_Block_object_assign((void*)&dst->p, (void*)src->p, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __test_block_dispose_0(struct __test_block_impl_0*src) {
_Block_object_dispose((void*)src->p, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __test_block_desc_0 {
size_t reserved;
size_t Block_size;
// 此处block对象(即__test_block_impl_0)处于堆上,所有需要对其进行相应的内存管理(copy和dispose)
void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
void (*dispose)(struct __test_block_impl_0*);
} __test_block_desc_0_DATA = {
0,
sizeof(struct __test_block_impl_0),
__test_block_copy_0,
__test_block_dispose_0
};
void test()
{
// __block把p对象包装成__Block_byref_p_0类型结构体,初始化的结构如下
__attribute__((__blocks__(byref))) __Block_byref_p_0 p = {
(void*)0,// __isa
(__Block_byref_p_0 *)&p,// __forwarding
33554432,//__flags
sizeof(__Block_byref_p_0),// __size
__Block_byref_id_object_copy_131,// copy
__Block_byref_id_object_dispose_131,// dispose
// 实例化GGPerson对象,返回 GGPerson* __strong 类型指针p
((GGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((GGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("GGPerson"),sel_registerName("alloc")), sel_registerName("init"))
};
// age = 1,向person对象发送setAge:消息
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)(p.__forwarding->p), sel_registerName("setAge:"), 1);
// 可以用临时变量impl_0接收函数返回值
__test_block_impl_0 impl_0 = __test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_p_0 *)&p, 570425344);
printf("%14p impl_0地址\n",&impl_0);
// 再用指针指向impl_0地址,以达到访问__test_block_impl_0内部成员的目的
__test_block_impl_0* impl_0_ptr = &impl_0;
// 声明一个block指针,指向block的结构内存
void (*block)(void) = (void (*)(void))impl_0_ptr;
// 函数指针调用,执行__test_block_func_0函数
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
}
return 0;
}
4、小结
(Block内存结构图 来源:Matt Galloway)
Block本质就是Objc对象(结构体),它包装了以下内容:
-
表示其类型,指向其父类的
isa
指针; -
标识block一些附件信息(包括引用计数)的
flags
; -
用于储存block内部变量信息的
reserved
; -
指向包装了block所执行代码块的函数地址的指针
invoke
; -
block描述信息
Block_descriptor_1
(根据具体情况可能还包含Block_descriptor_2
(block捕获auto变量内存管理的descriptor)、Block_descriptor_3
(block签名的descriptor)信息。Block_descriptor_2
及Block_descriptor_3
是由Block_descriptor_1
通过内存平移得到); -
捕获的外部变量。
二、Block类型分析
1、Block类型及继承链
Block类型共有6种:
类型 | 存储区域 | 源码定义 |
---|---|---|
_NSConcreteGlobalBlock | 全局变量/静态变量区 | 源码文件: Block.h |
_NSConcreteStackBlock | 栈区 | 源码文件: Block.h |
_NSConcreteMallocBlock | 堆区 | 源码文件: Block_private.h |
_NSConcreteAutoBlock | 堆区 | 源码文件: Block_private.h |
_NSConcreteFinalizingBlock | 堆区 | 源码文件: Block_private.h |
_NSConcreteWeakBlockVariable | 堆区 | 源码文件: Block_private.h |
注:其中后三种类型的Block,是系统级别属于ARC和GC范畴。接下来我们就研究前三种类型的Block(即:__NSGlobalBlock/__NSStackBlock/__NSMallocBlock)。
另外,以上类型的block均属于编译器层调用,我们不能直接使用它。在Block.h
文件中,你可以看到这段注解
// Used by the compiler. Do not use these variables yourself
- __NSGlobalBlock
没有访问外部auto变量,或访问了外部static或者全局变量类型的Block为__NSGlobalBlock,此种情况不会被block捕获。
代码验证:
1)没有访问auto变量的Block,也没访问static或者全局变量情况
void globalBlockTest(){
void (^block)(id) = ^(id objc){
NSLog(@"The Block type is %@",objc);// 输出类型为:__NSGlobalBlock__
};
block(object_getClass(block));
}
2)没有访问auto变量的Block,但访问了static或者全局变量情况
int a = 10;// 全局变量
void globalBlockTest(){
static int b = 20;// 静态变量
void (^block)(id) = ^(id objc){
NSLog(@"%d",a+b);// block访问了全局变量和静态变量
NSLog(@"The Block type is %@",objc);// 输出类型为:__NSGlobalBlock__
};
block(object_getClass(block));
}
- __NSStackBlock
MRC环境,访问了auto变量类型的block为__NSStackBlock。
void stackBlockTest(){
int a = 10;// auto 变量a
void (^block)(id) = ^(id objc){
NSLog(@"访问了auto变量a:%d block类型为:%@",a,objc);
};
block(object_getClass(block));
}
同样上述代码,ARC环境输出block类型为__NSMallocBlock。
原因:在ARC环境下,会自动为__NSStackBlock类型的block进行copy操作(由栈拷贝到堆)。目的是栈类型的Block生命周期为当前所处的作用域,防止其出了作用域再次访问会造成异常错误结果。在执行完copy操作后,将堆block的isa
设置成了_NSConcreteMallocBlock
(详见_Block_copy)。关于栈block拷贝到堆的相关过程请查看分别位于栈和堆上的Block与__block变量。
验证这一说法:
MRC环境下,下面这段代码的出现坏内存访问.
void (^stackBlock)(void);
void stackBlockErrorTest(){
int a = 10;// auto 变量a
stackBlock = ^{
NSLog(@"访问了auto变量a:%d",a);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
stackBlockErrorTest();
stackBlock();
}
return 0;
}
同样在ARC环境下,运行上述代码会产生正确✅结果。
- __NSMallocBlock
无论在MRC还是ARC环境下,对__NSStackBlock进行copy操作都会得到__NSMallocBlock。即在堆区生成一个与栈block相同的副本(由栈copy到堆)。
下面这段代码输出的block类型为__NSMallocBlock。
void mallocBlockTest(){
int a = 10;// auto 变量a
void (^block)(id) = [^(id objc){
NSLog(@"访问了auto变量a:%d block类型为:%@",a,objc);
} copy];
block(object_getClass(block));
}
小结:
Block类型 | 条件 |
---|---|
__NSGlobalBlock | 没有访问外部auto变量,或访问了外部static或者全局变量 |
__NSStackBlock | 访问了auto变量 |
__NSMallocBlock | 对__NSStackBlock进行了copy操作 |
2、__NSGlobalBlock、__NSStackBlock、__NSMallocBlock所处内存区域分析
接下来我们根据上图表示情况,通过代码进行验证(注意需要MRC环境下验证)
void blockMemoryTest() {
// __NSGlobalBlock
void (^globalBlock)(void) = ^{
};
globalBlock();
NSLog(@"%@ 地址: %p",[globalBlock class],globalBlock);
// __NSMallocBlock
int a = 1;
void (^mallocBlock)(void) = [^{
int c = a;
} copy];
mallocBlock();
NSLog(@"%@ 地址: %p",[mallocBlock class],mallocBlock);
// __NSStackBlock
int b = 1;
void (^stackBlock)(void) = ^{
int c = b;
// NSLog(@"%d",a);
};
stackBlock();
NSLog(@" %@ 地址:%p",[stackBlock class],stackBlock);
}
下面是我运行的结果:
首先我们需要了解应用内存分配地址的特点是程序区、数据区、堆、栈这四个区内存地址是依次增大的。
① 栈区的地址值比较大,一般以0x7F
开头,__NSStackBlock__
地址为0x7ffeefbff408
,符合这一特点;
② 堆区存放的是iOS对象,对象内存地址有word_align
对齐规则,其地址的低三位均为0(详见:iOS进阶笔记(一)对象本质相关源码解读)。上面__NSMallocBlock__
的地址为:0x1028328b0
,其低三位是0。同时我们知道对象的isa占用33位,第3-35位用来表示类信息,而该地址符合这一特点。故断定__NSMallocBlock__
内存地址存放于堆空间。
③ 对于iOS系统数据区的变量特点是地址值比较小,最高位两个字节一般都是0(即4个0),__NSGlobalBlock__
的地址为0x1000040d8
也符合这一特点。
综上,__NSGlobalBlock__
位于数据区,__NSMallocBlock__
位于堆区,__NSStackBlock__
位于栈区。
3、__NSGlobalBlock、__NSStackBlock、__NSMallocBlock的Copy操作分析
我们通过下面示例进行分析:
void blockCopyTest()
{
// 1、对全局block进行copy
void (^globalBlockCopy)(void) = [^{
} copy];
globalBlockCopy();
NSLog(@" %@ 地址:%p",[globalBlockCopy class],globalBlockCopy);
// 2、对栈block进行copy
int a = 10;
void (^stackBlockCopy)(void) = [^{
int c = a;
} copy];
stackBlockCopy();
NSLog(@" %@ 地址:%p",[stackBlockCopy class],stackBlockCopy);
// 3、对堆block进行copy
int b = 20;
void (^mallocBlockCopy)(void) = [[^{
int c = b;
} copy] copy];// 先对栈block进行copy得到堆block,该堆block再进行copy即为mallocBlockCopy
mallocBlockCopy();
NSLog(@" %@ 地址:%p",[mallocBlockCopy class],mallocBlockCopy);
}
运行结果如下:
因此,对__NSGlobalBlock
进行copy操作,结果依然是__NSGlobalBlock
;
对__NSStackBlock
和__NSMallocBlock
进行copy操作,结果是__NSMallocBlock
。
4、分别位于栈和堆上的Block与__block变量
栈Block和__block变量
对于栈空间,变量作用域结束后,栈上的Block变量和__block变量都会被回收。
从栈copy到堆上的Block及__block变量
但将栈Block(和__block)拷贝到堆上的Block(和__block)变量不受此影响。(对象内存管理由程序员管理)
5、小结
Block类型 | 内存区域 | copy后结果 |
---|---|---|
NSGlobalBlock |
数据区 | 数据区:什么也不做 |
NSStackBlock |
栈区 | 堆区:从栈copy到堆 |
NSMallocBlock |
堆区 | 堆区:引用计数增加 |
三、Block捕获变量及相应内存管理分析
1、全局变量
ObjC代码:
int a = 10;// 全局变量为全局可访问,不需要捕获
int main(int argc, const char * argv[]) {
^{
int c = a;
}();
return 0;
}
clang代码:
int a = 10;
// __block_impl_0并没有捕获变量a
struct __block_impl_0 {
struct __block_impl impl;
struct __block_desc_0* Desc;
__block_impl_0(void *fp, struct __block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __block_func_0(struct __block_impl_0 *__cself) {
int c = a;// 这里直接访问全局变量,并没有通过__cself间接访问
}
static struct __block_desc_0 {
size_t reserved;
size_t Block_size;
} __block_desc_0_DATA = { 0, sizeof(struct __block_impl_0)};
int main(int argc, const char * argv[]) {
((void (*)())&__block_impl_0((void *)__block_func_0, &__block_desc_0_DATA))();
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
block不会捕获全局变量:全局变量为可在全局进行访问,不需要捕获。通过clang代码__block_desc_0
结构体的描述信息,可以看出没有对全局变量进行内存管理。
2、static变量
ObjC代码:
int main(int argc, const char * argv[]) {
static int a = 10;
^{
int c = a;
}();
return 0;
}
clang代码
struct __block_impl_0 {
struct __block_impl impl;
struct __block_desc_0* Desc;
int *a;// 外部变量a为静态变量,这里声明了一个指向该变量的指针a
__block_impl_0(void *fp, struct __block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __block_func_0(struct __block_impl_0 *__cself) {
// 先通过__cself指针找到指向外部变量a的指针
int *a = __cself->a; // bound by copy
// 对指针a进行取值(即拿到所捕获的变量a的值),并赋值给c
int c = (*a);
}
static struct __block_desc_0 {
size_t reserved;
size_t Block_size;
} __block_desc_0 = { 0, sizeof(struct _block_impl_0)};
int main(int argc, const char * argv[]) {
static int a = 10;
// 这里传入的是变量a的地址
((void (*)())&_block_impl_0((void *)_block_func_0, &__block_desc_0_DATA, &a))();// 这里匿名block,但在转成clang时,还是会将
return 0;
}
block捕获static变量为指针传递:在__Block_impl_0
结构体初始化时,是将静态变量的地址传入该结构体初始化函数内部,并赋值给结构体同名的局部函数指针。在执行block()
时(即调用__block_func_0
函数),通过形参__cself->a
将main()
函数中静态变量a
地址传入,并通过(*a)取值。
通过上述clang代码,在__block_desc_0中没有相关对指针变量a进行相关内存管理。
3、auto变量
1)值类型变量捕获
开篇第一个例子即为对值类型的捕获。据此我们可知:
Block捕获值类型auto变量是值传递:Block捕获值类型后,将值传给Block结构体构造函数__Block_impl_0(void *fp, struct __Block_desc_0 *desc, int _a, int flags=0) : a(_a)
进行保存,当在执行block(即调用表示Block代码块内容的__block_func_0
函数)时,会在__block_func_0(struct __block_impl_0 *__cself)
内部,经由__cself
指针传入所捕获的变量值,再在内部声明一个同名的局部变量来保存该值。
通过该例clang源码可以看出,在__block_desc_0
中没有相关对值类型auto变量进行相关内存管理。
2)ObjC对象类型变量捕获
同样我们以示例进行分析
ObjC代码:
int main(int argc, const char * argv[]) {
NSObject *objc = [[NSObject alloc] init];
^{
objc;
}();
return 0;
}
clang代码:
// block layout
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// block实现
struct __block_impl_0 {
struct __block_impl impl;
struct __block_desc_0* Desc;
NSObject *objc;// 捕获对象类型auto变量
__block_impl_0(void *fp, struct __block_desc_0 *desc, NSObject *_objc, int flags=0) : objc(_objc) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __block_func_0(struct __block_impl_0 *__cself) {
// 与值类型变量一样操作一致
NSObject *objc = __cself->objc; // bound by copy
objc;
}
// 由于捕获的是对象类型,ARC环境下,会自动对栈Block进行copy到堆,因此需要对其进行相应的内存管理
// copy操作
static void __block_copy_0(struct __block_impl_0*dst, struct __block_impl_0*src)
{
// 注意:这里第一个参数传入的是dst的地址,可以在_Block_object_assign函数内部操作dst
// 由于所捕获的是普通Objc对象类型,这里第三个参数为BLOCK_FIELD_IS_OBJECT (即:3)
// 该函数内部执行了_Block_retain_object(src->objc),即对Objc对象自身进行相应内存管理
_Block_object_assign((void*)&dst->objc, (void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
// 释放操作
static void __block_dispose_0(struct __block_impl_0*src)
{
_Block_object_dispose((void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
// 相比值类型的block实现中的Desc,增加了所捕获对象类型的内存管理描述
static struct __block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __block_impl_0*, struct __block_impl_0*);
void (*dispose)(struct __block_impl_0*);
} __block_desc_0_DATA = {// 直接对结构体进行初始化
0,
sizeof(struct __block_impl_0),
__block_copy_0,// 传入copy函数指针
__block_dispose_0 // 传入dispose函数指针
};
int main(int argc, const char * argv[]) {
// [[NSOject alloc] init];
NSObject *objc = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
// block
((void (*)())&__block_impl_0((void *)__block_func_0, &__block_desc_0_DATA, objc, 570425344))();
return 0;
}
上述clang代码中_Block_object_assign
函数flag参数为BLOCK_FIELD_IS_OBJECT
,因此内部执行的是block源码中_Block_object_assign
函数内部的_Block_retain_object
函数。由于捕获的是对象类型,因此对象需要进行内存管理,这也符合Objc对象内存管理的基本原则。这也是与值类型变量的区别。
Block捕获对象类型变量也是值传递:Block捕获该类型变量,也是通过__cself
指针传入所捕获的值,并在__block_func_0
函数内部声明一个同名的指针以保存捕获的对象。
不同的是,需要对该类型变量进行内存管理:通过_Block_object_assign
和_Block_object_dispose
函数对其进行相应的内存管理。其中_Block_object_assign内部执行
_Block_retain_object
即retain
,_Block_object_dispose内部执行_Block_release_object
即release
。
详情查看_Block_object_assign 和_Block_object_dispose源码解读。
3)汇编查看值类型和对象类型区别
- 值类型auto变量捕获
- 对象类型auto变量捕获
通过上面对比也可以看出,Block捕获值类型变量在执行完block调用后,函数直接退出了(retq
);而Block捕获的对象类型变量,会有retain操作。
4、修改auto变量值的异常问题
-
在block块后面修改auto变量值 ObjC代码示例:
int a = 1; void (^block)(void) = ^{ NSLog(@"a %d",a);// 结果 1 }; a = 2;// 修改auto变量值 block();
根据前面Clang代码可知:^{ }
内部实际执行的是__block_func_0
函数。
static void __block_func_0(struct __block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
}
通过__block_func_0
函数参数__cself
指针带入的函数体的a,赋值给了一个同名的内部变量a
。这两个变量a
,分别属于main()
和__block_func_0()
两个不同的作用域。修改main()
中a
的值,对__block_func_0()
中a
没有影响。故修改block块外部修改auto变量的值不会对^{}
内部同名变量值造成影响。
block块内部修改外部auto变量值,Xcode编译器报错分析
至于上图在block内部修改 a = 2
时报错,原因是这block内部个a
,是接收__cself->a
的内部临时变量与外面的a
分别属于不同的作用域。即便对block内部变量a
作了修改,外部也无法访问(这样做没有意义,下面代码模拟了该过程)。
typedef struct {
int a;
}Impl;
void impl_func(Impl *__cself) {
int a = __cself->a;
a++;
printf("%d\n",a);// 输出2
}
void test() {
Impl impl = {1}; // 结构体初始化,a=1
Impl *implPtr = &impl; // 声明一个结构体指针指向该结构体
impl_func(implPtr); // 函数调用
printf("%d\n",impl.a);// 输出 1
}
再者,在ARC环境下,上面的栈block会被copy到堆上。此时上述错误提示代码a = 2
中的a
就是堆上的a
。然而block外部的a
又处于栈上。很明显,这两者是相互隔离的。即便修改堆上的变量a
,通过外部栈的也是访问不到的。除非是将栈上的block与堆block进行关联(即后面讲到的通过__block修饰下__forwarding
指针,可以由栈指向堆)。
另外,某些情况下,我们是可以通过位于栈的block的__cself->a
也是可以修外部变量值的。
通过C模拟实现
typedef struct {
int a;
}Impl;
void impl_func(Impl *__cself) {
int a = ++ __cself->a;// 前提是__cself所指向的Impl作用域还在
printf("%d\n",a);// 输出2
}
void test() {
Impl impl = {1}; // 结构体初始化,a=1
Impl *implPtr = &impl; // 声明一个结构体指针指向该结构体
impl_func(implPtr); // 函数调用
printf("%d\n",impl.a);// 输出 2
}
通过C++模拟实现
// block.hpp
/**************************************************************/
#include <stdio.h>
namespace block {
struct Impl{
int a;
void *FuncPtr;
// C++结构体构造函数,: a(_a)相当于把形参_a传给了结构体变量a
Impl(void *fp, int _a) : a(_a) {
FuncPtr = fp;
}
};
class BlockClass {
public:
void blockTest();
BlockClass();// 构造函数,相当于Objective-C的 init函数
~BlockClass();// 析构函数,相当于Objective-C的 dealloc函数
};
}
// block.cpp
/**************************************************************/
#include "block.hpp"
#include <iostream>
using namespace std;
namespace block {
void BlockClass::blockTest(void)
{
// C++闭包(匿名函数) 学习下C++闭包,就不在外部写函数了 ~~
auto f = [](struct Impl *__cself){
int a = ++ __cself->a;// 前提是__cself所指向的Impl作用域还在
cout << "__cself->a ++结果:" << a << endl;
};
// 结构体构函数初始化
Impl impl = Impl(&f, 1);
// 类型强转
((void (*)(struct Impl *__cself))f)(&impl);
cout << "最终结果:" << impl.a << endl;// 2
}
BlockClass::BlockClass(void) { }
BlockClass::~BlockClass(void){ }
}
// 调用
/**************************************************************/
block::BlockClass block;
block.blockTest();
但值得注意的是,这种操作不安全,前提是栈block没有出作用域。例如异步情况下可能栈block已出作用域,被系统回收了,再次执行++ __cself->a
就会发生错误。
分析至此,Xcode编译器给出的报错提示,我猜测是主要有以下几个原因:
① 为了避免二义性(不知道这个a
是上面提到的哪个a
);
② ARC环境下,栈block会自动拷贝到堆上,但无法通过栈block操作堆block(二者是相互隔离的)。
③ MRC环境下,某些情况下,在栈block块内部(即代表block块所执行的代码的函数)操作该block是不安全的(异步情况下可能栈block已出作用域,被系统回收了,再次执行++ __cself->a
就会发生错误)。
④ 再者,也可能是ObjC语言就是这么设计的,既然作为闭包,那就在闭包作用域内做自己内部的数据处理,就不要修改外部的auto变量了。若真要修改,则通过__block
修饰符显式说明就好了。
5、小结
变量类型 | 是否捕获到block内部 | 访问方式 | 是否有相应内存管理 | ||
---|---|---|---|---|---|
局部变量 | auto | 值类型 | ✔️ | 值传递 | 无 |
对象类型 | ✔️ | 对象自身遵守内存管理 | |||
static | ✔️ | 指针传递 | 无 | ||
全局变量 | ❌ | 直接访问 | 无 |
四、__block修饰的auto变量的copy及dispose分析
1、__block修饰值类型变量
Objc代码:
void test()
{
__block int a = 1;
void (^block)(void) = ^{
a = 99;
};
}
对应Clang代码
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// __block修饰的auto变量被包装成对象
struct __Block_byref_a_0 {
void *__isa;
struct __Block_byref_a_0 *__forwarding;// 指向自身
int __flags;
int __size;
int a;
};
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
__Block_byref_a_0 *a; // 指针传递,指向包装了block所捕获变量a的对象
__test_block_impl_0(void *fp, struct __test_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;
}
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
// 指针传递,指向包装了block所捕获变量a的对象(即__Block_byref_a_0)
__Block_byref_a_0 *a = __cself->a; // bound by ref
// 通过__forwarding指针,指向该结构体自身内部的变量a并对其修改。但这里修改的不是外部变量a的值,而是由栈copy到堆的__Block_byref_a_0中的变量a。
// 具体可查看block源码的 `static struct Block_byref *_Block_byref_copy(const void *arg)`函数
(a->__forwarding->a) = 99;
}
// __block修饰的变量copy(retain)操作
static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src)
{
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}
// __block修饰的变量dispose(release)操作
static void __test_block_dispose_0(struct __test_block_impl_0*src)
{
_Block_object_dispose((void*)src->a, 8);
}
static struct __test_block_desc_0 {
size_t reserved;
size_t Block_size;
// copy函数指针
void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
// dispose函数指针
void (*dispose)(struct __test_block_impl_0*);
} __test_block_desc_0_DATA = {
0,
sizeof(struct __test_block_impl_0),
__test_block_copy_0,
__test_block_dispose_0
};
void test()
{
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
(void*)0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
1
};
// 使用blcok指针指向了block实现的地址
void (*block)(void) = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
// 执行block
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
上述代码对比未使用__block捕获变量a的情况,可以看出:__block修饰auto值类型变量,在被block捕获后,将其包装成一个对象(结构体)__Block_byref_a_0
(Block源码称之为__block container
)。
- copy操作:
上面clang代码中_Block_object_assign
函数flag参数为BLOCK_FIELD_IS_BYREF
,因此内部执行的是_Block_byref_copy函数。
从_Block_byref_copy
源码 中提取关键内部执行的代码块如下:
static struct Block_byref *_Block_byref_copy(const void *arg) {
// 栈__block container
struct Block_byref *src = (struct Block_byref *)arg;
// 1、在堆区开辟一块内存,保存栈__block container信息
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
// 2、copy的forwarding指向自身(堆)
copy->forwarding = copy; // patch heap copy to point to itself
// 3、让位于栈区的src的forwarding指向新分配位于堆区的__block container
src->forwarding = copy;
......
}
据此可知,此种情况在堆区开辟一块内存,来copy栈__block container
内容到堆上(包含__block修饰的auto变量也由栈copy堆),即堆有一个栈__block container
副本。
堆__block container
的__forwarding
指向自身堆的变量a
;
栈__block container
的__forwarding
由原来指向自身转变为指向堆__block container
。
这样就可以在外面通过栈__block container
操作堆__block container
内部的变量a
并修改其值。如下面的测试代码,输出结果是100
。
void test()
{
__block int a = 1;
void (^block)(void) = ^{
NSLog(@"%d",a);// 100
};
a = 100;
block();
}
对于更多__forwarding
解读请查看__forwarding指针的用意?。
- dispose操作:
根据Block源码的_Block_object_dispose
函数可知,ARC环境下,对于Block捕获的__block变量会调用内部的_Block_byref_release
函数。由于__block变量是值类型,因此只需将封装了该值__block container
对象释放即可,此过程是通过free
函数将__block container
对象释放的(详见_Block_object_dispose源码解读)
MRC环境下,由于__block container
是对象类型,因此需要程序员手动的去release。
2、__block修饰对象类型变量
ObjC代码:(MRC环境)
void test()
{
__block NSObject *objc = [[NSObject alloc] init];
void (^block)(void) = [^{
objc = [[NSObject alloc] init];// objc指针指向了一个新对象
} copy];// 当ARC下,会自动copy,此处就不用加copy了
block();
}
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __Block_byref_objc_0 {
void *__isa;// 8
__Block_byref_objc_0 *__forwarding;// 8
int __flags;// 4
int __size;// 4
void (*__Block_byref_id_object_copy)(void*, void*);// 8
void (*__Block_byref_id_object_dispose)(void*);// 8
NSObject *objc;// 通过__Block_byref_objc_0结构体地址 +40,找到该对象
};
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
__Block_byref_objc_0 *objc; // by ref
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, __Block_byref_objc_0 *_objc, int flags=0) : objc(_objc->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
__Block_byref_objc_0 *objc = __cself->objc; // bound by ref
(objc->__forwarding->objc) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}
// 【第二层copy】将栈__block container拷贝到堆(__block container表示:__block所修饰包装了被block所捕获的外部变量的对象__Block_byref_xx)
static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src)
{
_Block_object_assign(
(void*)&dst->objc,
(void*)src->objc,
8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __test_block_dispose_0(struct __test_block_impl_0*src)
{
_Block_object_dispose((void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
void (*dispose)(struct __test_block_impl_0*);
} __test_block_desc_0_DATA = {
0,
sizeof(struct __test_block_impl_0),
__test_block_copy_0,
__test_block_dispose_0
};// 结构体默认初始化
// 【第三层copy】浅拷贝:对block所捕获的对象类型的auto变量自身的copy。【注意此过程在MRC环境下,根据131这个flag,执行object的retainCount加1】
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
// + 40:*det拿到__Block_byref_objc_0结构体地址首地址,由于该结构体object变量前面成员内存总共为40,这里通过+40找到该object.
// 第三个参数是 131代表:BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT,表示为__block container,并捕获了对象类型的auto变量
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
void test()
{
__attribute__((__blocks__(byref))) __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)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))
};
// 【第一层copy】深拷贝:block自身由栈copy到堆。
void (*block)(void) = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_objc_0 *)&objc, 570425344)), sel_registerName("copy"));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
小结
-
__block所修饰的block捕获对象类型的auto变量的copy操作有三层 ,即:
【第一层copy】深拷贝:block自身由栈copy到堆。
【第二层copy】深拷贝:将栈
__block container
拷贝到堆(__block container
表示:__block所修饰包装了被block所捕获的外部变量的对象__Block_byref_xx)。该过程详细描述见_Block_byref_copyNOTE:ARC环境下在_Block_byref_copy内部已经执行了第三层拷贝。
【第三层copy】浅拷贝:block所捕获的对象类型的auto变量自身的copy(指针拷贝)。即该对像会被Block持有。
NOTE:此过程在MRC环境下,根据131这个flag,执行object的retainCount加1。ARC环境下在该copy过程发生在第二层copy内部。
dispose操作 :
根据Block源码可知,ARC环境下,对于Block捕获的__block变量会调用内部的_Block_byref_release
函数。由于__block变量是对象类型,因此会调用该对象的dispose函数对其进行释放。最后再调用free
函数将__block container
对象释放。(详见_Block_object_dispose源码解读)
MRC环境下,由于__block container
是对象类型,因此需要程序员手动的去release。
3、__block修饰__weak对象类型变量
void test()
{
__weak __block NSObject *objc = [[NSObject alloc] init];
void (^block)(void) = ^{
objc = [[NSObject alloc] init];// objc指针指向了一个新对象
};// __weak适用于ARC环境,所以此处位于栈的block,编译器会自动将其copy到堆
block();
}
clang代码:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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 *__weak objc;
};
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
__Block_byref_objc_0 *objc; // by ref
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, __Block_byref_objc_0 *_objc, int flags=0) : objc(_objc->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
__Block_byref_objc_0 *objc = __cself->objc; // bound by ref
(objc->__forwarding->objc) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}
static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) {
_Block_object_assign((void*)&dst->objc, (void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __test_block_dispose_0(struct __test_block_impl_0*src) {
_Block_object_dispose((void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
void (*dispose)(struct __test_block_impl_0*);
} __test_block_desc_0_DATA = {
0,
sizeof(struct __test_block_impl_0),
__test_block_copy_0,
__test_block_dispose_0
};
void test()
{
__attribute__((objc_ownership(weak))) __attribute__((__blocks__(byref))) __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)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
void (*block)(void) = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_objc_0 *)&objc, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
将clang代码与__Block_byref_objc_0
对象中的objc对象类型为__weak
,即block与捕获的对象为弱引用关系。这也符合ARC下,__weak对象的内存管理原则(即__weak类型的对象引用计数为0时,__weak对象的指针被置为nil,这一点有别于MRC下使用的unsafe unretained
)。
同理将上面Objc代码中的__weak
改为__strong
,再转成clang代码后,会发现__Block_byref_objc_0
对象中的objc对象类型为__strong
,即block与捕获的对象为强引用关系。由于Block已持有了该__strong
类型的__block
变量,在某种情况下,若该变量反过来又直接或间接引用了该Block,这就造成了循环引用。(循环引用问题详见:iOS进阶笔记--Block基础篇)
至于__weak
和__strong
类型的__block
变量第dispose
基本是一样的,都还是走__block修饰对象类型变量中的dispose流程。区别还是在于强引用和弱引用的不同。
4、__forwarding指针的用意?
- 拷贝前栈Block的
__forwarding
指针指向自身
根据前面clang源码可知,__forwarding指针是 __block在修饰auto变量后,将其包装成一个类似struct __Block_byref_xx
的对象。在该结构(源码称作:__block container
)中,__forwarding指向了自身。
- 拷贝到堆上后,栈上__block变量用结构体(源码称作:
__block container
,也就是被__block所修饰并将所捕获了auto变量包装成类似__Block_byref_objc_0
的结构体)会将成员变量的__forwarding
指针替换为目标堆上的__block container
地址。
为了保证栈与堆上的__block所修饰的变量修改同步,在将__block所修饰的变量从对栈copy到堆的同时,栈的__block container
结构体的__forwarding
指针将指向了堆__block container
结构体。
5、小结
-
__block修饰的auto变量(包括值类型、__weak对象和__strong类型对象)会被包装成
__block container
对象。① 值类型变量的三层copy:即block自身由栈到堆的copy、
__block container
对象由栈到堆的copy及__block变量值的copy(在对堆上复制一个栈__block变量的副本)。② 对象类型的三层copy:即block自身由栈到堆的copy、
__block container
对象由栈到堆的copy和对象指针的浅拷贝(即被Block持有,引用计数增加)。
-
Block从栈copy到堆时,__block变量产生的影响如下:
__block变量的位置 Block从栈copy到堆的结果 栈 从栈copy到堆并被Block持有 堆 被Block持有(引用计数增加)
-
栈Block被copy到堆的时机:
① 主动调用Block的copy实例方法时(内部执行了_Block_copy函数)。
② Block作为函数返回值。
③ 将Block赋值个给__strong修饰符id类型的类或Block类型成员变量。
④ 在方法名中含有usingBlock的Cocoa框架或GCD的API中的Block。
-
Block捕获__block变量的dispose其实是copy的逆操作。
① 若为__block值类型的变量:则只释放封装该类型的位于堆的
__block container
对象。② 若为__block对象类型的变量:先释放该对象(调用
dispose
函数),然后再释放封装该类型的位于堆的__block container
对象。
-
__forwarding
指针是Block捕获__block
变量的产物。目的是通过栈的
__forwarding
访问堆的内存,并达到修改位于堆的__block变量(即栈__block变量的副本)的目的。
五、Block的方法签名探索
据前面所述,Block本质是Objc对象,并且Block的调用过程也等同于方法(函数)调用。因此Block的方法应该也少不了方法签名(Signature)。接下来我们先看个普通Objc对象方法的签名的例子复习下签名表示含义。
1、普通Objc对象的Signature
示例如下:
@interface Person : NSObject{
@public
int _hours;
}
- (void)sleep:(int)hours;
- (NSString *)printHours;
@end
@implementation Person
- (void)sleep:(int)hours {
_hours = hours;
}
- (NSString *)printHours {
return [NSString stringWithFormat:@"%d",_hours];
}
+ (NSArray *)getPersonInfoWithName:(NSString *)name age:(int)age {
return @[name,@(age)];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// import<objc/runtime.h>
// 获取无返回值且含参的instance方法的Type Encoding
Method m_sleep = class_getInstanceMethod([Person class], @selector(sleep:));
const char *m_sleep_chr = method_getTypeEncoding(m_sleep);
printf("%s\n\n",m_sleep_chr);// v20@0:8i16
// 获取有返回值无参的instance方法的Type Encoding
Method m_hours = class_getInstanceMethod([Person class], @selector(printHours));
const char *m_hours_chr = method_getTypeEncoding(m_hours);
printf("%s\n\n",m_hours_chr);// @16@0:8
// 获取含返回值且含参的Class方法的Type Encoding
Method m_info = class_getClassMethod([Person class], @selector(getPersonInfoWithName:age:));
const char *m_info_chr = method_getTypeEncoding(m_info);
printf("%s\n\n",m_info_chr);// @28@0:8@16i24
return 0;
}
通过runtime提供的API,很容易获得一个对象方法的Signature,简单说明下@28@0:8@16i24
这个签名的打印结果:
-
第一个
@
表示返回值为对象; -
28
表示参数总共内存大小(字节数)。第一个参数为self,占8个字节;
第二个参数为SEL struct objc_selector *类型,占8个字节;
第三个参数为对象,占8个字节;
第四个参数为int类型,占4个字节。
(每个Objc方法都默认含有self和SEL两个参数,所以才能在方法体中访问self,self是局部变量) -
第二个
@
表示第一个参数类型为对象类型(即self),0表示第一个参数内存起始值,因为是第一个参数,内存从0开始; -
:
表示第二个参数类型为SEL,8表示第二个参数内存起始值,由于第一次参数为self,占用8个字节,故第二个参数从8开始; -
第三个
@
表示第三个参数类型为对象,由于self和SEL各占8个字节,故该参数内存起始值为16; -
i
表示第四个参数为int类型,起始地址为24。(0+8+8+8)
综上,方法签名描述了方法的返回值类型、所有参数占用内存大小的总和、参数个数、参数类型、各个参数的内存起始地址的描述信息。
另外还可以通过NSMethodSignature
这个类获取Objc对象的签名(这种方式比method_getTypeEncoding
相比,不含有各参数起始地址信息)。代码块如下:
NSString * getMethodTypeEncoding(Class cls, SEL sel, BOOL isInstanceMethod)
{
NSMutableString *result = [NSMutableString string];
NSMethodSignature *signature;
if (isInstanceMethod) {
signature = [cls instanceMethodSignatureForSelector:sel];
}else {
signature = [cls methodSignatureForSelector:sel];
}
// 返回值类型
const char * returnType = signature.methodReturnType;
[result appendString:[NSString stringWithUTF8String:returnType]];
for (NSUInteger i = 0; i < signature.numberOfArguments; i++) {
// 参数类型
const char *argType = [signature getArgumentTypeAtIndex:i];
[result appendString:[NSString stringWithUTF8String:argType]];
}
return [result copy];
}
2、Block的方法签名分析
1)从Block源码角度分析
从Block源码Block_descriptor_3
结构体中可以找到signature信息。
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature; //签名
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT 内存布局
};
下面是对Block内存经过平移得到signature的过程
// Block的signature的实现
const char * _Block_signature(void *_Block_descriptor_3)
{
struct Block_layout *layout = (struct Block_layout *)aBlock;
// 【关键1】确保有BLOCK_HAS_SIGNATURE这个标识位
if (!(layout->flags & BLOCK_HAS_SIGNATURE))
return nullptr;
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
if (layout->flags & BLOCK_SMALL_DESCRIPTOR) {
auto *bds = (Block_descriptor_small *)_Block_get_descriptor(layout);
return unwrap_relative_pointer<const char>(bds->signature);
}
#endif
struct Block_descriptor_3 *desc3 = _Block_descriptor_3(layout);
return desc3->signature;
}
// _Block_descriptor_3通过block内存经过两次平移得到
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
// 先由aBlock得到Block_descriptor_1
uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
//Block_descriptor_1内存平移得到Block_descriptor_2
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {// 【关键2】确保有这个标识位
// 对Block_descriptor_2内存平移得到Block_descriptor_3
desc += sizeof(struct Block_descriptor_2);
}
return (struct Block_descriptor_3 *)desc;
}
// 通过block内存,找到Block_descriptor_1
static inline void *_Block_get_descriptor(struct Block_layout *aBlock)
{
void *descriptor;
#if __has_feature(ptrauth_signed_block_descriptors)
if (!(aBlock->flags & BLOCK_SMALL_DESCRIPTOR)) {
descriptor =
(void *)ptrauth_strip(aBlock->descriptor, ptrauth_key_asda);
} else {
uintptr_t disc = ptrauth_blend_discriminator(
&aBlock->descriptor, _Block_descriptor_ptrauth_discriminator);
descriptor = (void *)ptrauth_auth_data(
aBlock->descriptor, ptrauth_key_asda, disc);
}
#elif __has_feature(ptrauth_calls)
descriptor = (void *)ptrauth_strip(aBlock->descriptor, ptrauth_key_asda);
#else
descriptor = (void *)aBlock->descriptor;
#endif
return descriptor;
}
概括就是:先通过Block内存地址+24字节(根据Block_layout结构:isa[8字节] + flags[4字节] + reserved[4字节] + invoke[8字节] = 24
)得到Block_descriptor_1地址,然后将Block_descriptor_1内存+16字节(Block_descriptor_1和Block_descriptor_2为指针类型分别占8个字节)得到Block_descriptor_3内存地址。而Block_descriptor_3
内部第一个员为signature,因此就拿到了signature
。
根据这个思路,我们可以自己实现获取Block的signature
过程。(借鉴了Aspects)
① 先定义自定义block结构:struct GGBlock_layout
typedef NS_ENUM(int, GGBlockFlags) {
// 是否拥有拷贝辅助函数,它决定了description_2
GG_BLOCK_HAS_COPY_DISPOSE = (1 << 25),
// 是否有签名,它决定了description_3
GG_BLOCK_HAS_SIGNATURE = (1 << 30)
};
// descriptor_1
struct GGBlock_descriptor_1 {
unsigned long int reserved;
unsigned long int size;
};
// descriptor_2
struct GGBlock_descriptor_2 {
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
};
// descriptor_3
struct GGBlock_descriptor_3 {
const char *signature;
const char *layout;
};
typedef struct GGBlock_layout {
__unused Class isa;
GGBlockFlags flags;
__unused int reserved;
// block块内真正执行的函数指针
void (__unused *invoke)(struct Block_layout *block, ...);
struct GGBlock_descriptor_1 *descriptor_1;
struct GGBlock_descriptor_2 *descriptor_2;
struct GGBlock_descriptor_3 *descriptor_3;
} *GGBlock_layoutRef;// 声明一个Block_layout类型的结构体指针
② 根据前面的思路进行实现
// 获取方法签名的 Type Encoding
NSString * gg_signatureTypeEncoding(NSMethodSignature *signature)
{
NSMutableString *result = [NSMutableString string];
// 返回值类型
const char * returnType = signature.methodReturnType;
[result appendString:[NSString stringWithCString:returnType encoding:NSUTF8StringEncoding]];
for (NSUInteger i = 0; i < signature.numberOfArguments; i++) {
const char *argType = [signature getArgumentTypeAtIndex:i];
[result appendString:[NSString stringWithCString:argType encoding:NSUTF8StringEncoding]];
}
return [result copy];
}
// 获取block的signature
NSMethodSignature * gg_blockMethodSignature(id block)
{
// 拿到block结构体
GGBlock_layoutRef layout = (__bridge void *)block;
if (!(layout->flags & GG_BLOCK_HAS_SIGNATURE)) return nil;
// 首先拿到descriptor_1
void *desc = layout->descriptor_1;
// 按descriptor_1结构内存平移16字节拿到descriptor_2
desc += sizeof(struct GGBlock_descriptor_2);
if (layout->flags & GG_BLOCK_HAS_COPY_DISPOSE) {
// 按descriptor_2结构内存平移16字节拿到descriptor_3
desc += sizeof(struct GGBlock_descriptor_3);
}
if (!desc) return nil;
// 至此,此时desc内存地址里的内容就是signature的信息了
const char *signature = (*(const char **)desc);
return [NSMethodSignature signatureWithObjCTypes:signature];
}
// 获取block签名的类型编码(Type Encoding)
NSString * gg_blockSignatureTypeEncoding(id block)
{
if (!block) return nil;
NSMethodSignature *signature = gg_blockMethodSignature(block);
if (!signature) return nil;
return gg_signatureTypeEncoding(signature);
}
const char *signature = (*(const char **)desc)解释如下:
const char ** :由于desc类型为 struct *,而signature类型为 const char *,需要进行类型强转;
最外层的 * :desc为地址,所以要用 * 取里面的值,即signature内容。
③ 下面开始验证一下
-
全局block:无参数无返回值的
void (^block1)(void) = ^{ printf("hello\n"); }; block1(); NSString *s1 = gg_blockSignatureTypeEncoding(block1); NSLog(@"%@",s1);
输出结果:
v@?
返回值:为
void
类型;
第一个参数:为@
表示block对象(这也侧面证明了block本质是对象);
第二个参数:(可理解为SEL
)为?
,表示block方法的Type Encoding
类型。对于
?
这个Type Encoding
,Apple官方解释是这样:An unknown type (among other things, this code is used for function pointers) ,即该类型被用作函数指针的特殊类型。这也与block源码的Block_descriptor_2的描述一致。即void (*copy)(void *dst, const void *src); void (*dispose)(const void *);
-
有返回值且含参的栈block,在_Block_copy函数返回时被copy到堆,成为堆block
int b = 1; int (^block2)(int) = ^(int a){ return a+b; }; block2(2); NSString *s2 = gg_blockSignatureTypeEncoding(block2); NSLog(@"%@",s2);
输出结果:
i@?i
返回值为int类型;
第一个参数:为@
block对象类型;
第二个参数:?
为block的Type Encoding
。
第三个参数:为int类型。
-
返回值为所捕获的对象类型
NSObject *obj = [[NSObject alloc] init]; NSObject* (^block3)(void) = ^{ return obj; }; block3(); NSString *s3 = gg_blockSignatureTypeEncoding(block3); NSLog(@"%@",s3);
输出结果:
@"NSObject"@?
返回值:
@
为NSObject
对象类型;
第一个参数:@
为block对象类型;
第二个参数:?
为block的Type Encoding
。
-
__block的情况
__block int c = 1; void (^block4)(void) = ^{ c =2; }; block4(); NSString *s4 = gg_blockSignatureTypeEncoding(block4); NSLog(@"%@",s4);
输出结果:
v@?
返回值:
v
为void
类型;
第一个参数:@
为block对象类型;
第二个参数:?
为block的Type Encoding
。
-
返回值为block的情况
typedef void(^GGBlock)(float); GGBlock (^block5)(int) = ^(int a){ return ^(float b){ }; }; block5(1); NSString *s5 = gg_blockSignatureTypeEncoding(block5); NSLog(@"%@",s5);
输出结果:
@?<v@?f>@?i
返回值:
@?
表示返回值为block类型,只有默认参数,不含其它参数;
(<v@?f>
描述了这个返回block的签名:返回值为void
类型,第一个参数为@
block对象类型;第二个参数为block的Type Encoding
;第三个参数为float
类型。)第一个参数:
@
为block对象类型;
第二个参数:?
为block的Type Encoding
。
2) 从汇编角度分析
以下面栈block代码为例:
int a=1;
void (^block)(int) = ^(int b){
printf("a+b:%d",a+b);
}
block(2);
根据前面所掌握的知识,我们知道ARC环境下会将栈block拷贝到堆上,会执行_Block_copy
函数。我们就断点定位到此。
从通用寄存器x0
中可以通过register read x0
打印栈block的内存地址;
在_Block_copy
返回前(req
命令前),再次读取x0
就是copy栈block后的堆block内存地址。
前面我们讲到过,Block_descriptor_1地址 = block内存地址 + 24个字节;
Block_descriptor_3地址 = Block_descriptor_1地址 + 16个字节;
因此,最终再打印Block_descriptor_3的内容即为block的sigature
。
最终得到该block的签名为:v8@?0
,这里我们可以看出参数内存起始位置0是在?
后面,这是因为@?
这两个参数为block的默认参数,不计入在内。(偷工减料,这优化简直了~~~)
3、小结
根据以上分析得知block方法签名的Type Encoding为 ?
。@?
这两个参数为block的默认参数。
六、附录:Block相关源码解读
struct Block_layout
struct Block_layout {
// isa指针,通过isa可以找到block的类型
void * __ptrauth_objc_isa_pointer isa;
// 用来作标识符的(包括引用计数信息),类似于isa中的位域,按bit位表示一些block的附加信息
volatile int32_t flags; // contains ref count
// 保留信息,用于储存block内部变量信息
int32_t reserved;
// 函数指针,指向包装了block所执行代码块的函数地址
// BlockInvokeFunction定义:typedef void(*BlockInvokeFunction)(void *, ...);
BlockInvokeFunction invoke;
// block描述信息( blockSize等信息)
struct Block_descriptor_1 *descriptor;
// 捕获的外部变量
};
flags->flags
// Values for Block_layout->flags to describe block objects
enum {
//释放标记,一般常用于BLOCK_BYREF_NEEDS_FREE做位与运算,一同传入flags,告知该block可释放
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 SMALL 类型的Block
#endif
BLOCK_IS_NOESCAPE = (1 << 23), // compiler
//低16位是否有效的标志,程序根据它来决定是否增加或者减少引用计数位的值
BLOCK_NEEDS_FREE = (1 << 24), // runtime
//是否拥有拷贝辅助函数,(a copy helper function)决定block_description_2
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
//是否拥有block C++析构函数
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
//标志是否有垃圾回收,OSX
BLOCK_IS_GC = (1 << 27), // runtime
//标志是否是全局block
BLOCK_IS_GLOBAL = (1 << 28), // compiler
//与BLOCK_HAS_SIGNATURE相对,判断是否当前block拥有一个签名,用于runtime时动态调用
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
//是否有签名
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
//使用有拓展,决定block_description_3
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
descriptor
typedef void(*BlockCopyFunction)(void *, const void *);
typedef void(*BlockDisposeFunction)(const void *);
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;//保留信息
uintptr_t size;//block大小
};
// 对block捕获的auto变量进行内存管理的descriptor
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;//拷贝函数指针
BlockDisposeFunction dispose;// 销毁函数指针
};
// block签名的descriptor
#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 内存布局
};
// SMALL 类型的Block,对以上三种descriptor简单封装,编译器的优化
struct Block_descriptor_small {
uint32_t size;
int32_t signature;
int32_t layout;
/* copy & dispose are optional, only access them if
Block_layout->flags & BLOCK_HAS_COPY_DIPOSE */
int32_t copy;
int32_t dispose;
};
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);// 对Block_descriptor_1内存地址向左平移
return (struct Block_descriptor_2 *)desc;
}
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);// 对Block_descriptor_2内存地址向左平移
}
return (struct Block_descriptor_3 *)desc;
}
signature
const char * _Block_signature(void *aBlock)
{
struct Block_layout *layout = (struct Block_layout *)aBlock;
if (!(layout->flags & BLOCK_HAS_SIGNATURE))
return nullptr;
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
if (layout->flags & BLOCK_SMALL_DESCRIPTOR) {
auto *bds = (Block_descriptor_small *)_Block_get_descriptor(layout);
return unwrap_relative_pointer<const char>(bds->signature);
}
#endif
// _Block_descriptor_3()函数内部首先拿到Block_descriptor_1,然后再进行内存平移得到Block_descriptor_3
struct Block_descriptor_3 *desc3 = _Block_descriptor_3(layout);
return desc3->signature;// 通过Block_descriptor_3拿到方法签名
}
Block的copy和dispose的参数
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类型作为变量
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
// 经过__block修饰的变量
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
// weak 弱引用变量
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
// 处理__block container(__block所修饰包装了被block所捕获的外部变量的对象__Block_byref_xx) 修饰并捕获变量为对象类型auto变量)
// 不单独使用,需结合 id objc 或 __weak id objc使用。
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
_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所捕获对象类型的auto变量
id object = ...;
[^{ object; } copy];
********/
_Block_retain_object(object);// 对object对象进行相应内存管理
*dest = object;// 指针赋值
break;
case BLOCK_FIELD_IS_BLOCK:
/******* 【第一层copy】 block自身copy,即将栈block拷贝到堆
void (^object)(void) = ...;
[^{ object; } copy];
********/
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/******* 【第二层copy】将__block container 从栈copy到堆
__block container解释为:__block修饰,包装block所捕获auto变量的对象(即:__Block_byref_xx的结构体)
// 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 = _Block_byref_copy(object);// ARC下,在该函数内部执行了第三层copy
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;// MRC下,由于object本身就是对象类型,这里直接执行引用,retainCount加1,在使用完该对象时需要手动执行[objc release].
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;// MRC下, 由于object本身就是对象类型,这里直接执行引用,retainCount加1,与上面区别,此处为unretained引用。在使用完毕该对象时需要手动执行[objc release].
break;
default:
break;
}
}
_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:// __block修饰的变量
// get rid of the __block data structure held in a Block
_Block_byref_release(object);//
break;
case BLOCK_FIELD_IS_BLOCK:// 若为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_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
// 强转为Block_layout类型
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {// 判断是否需要释放
// latches on high
latching_incr_int(&aBlock->flags);// 引用计数增加
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {// 若是全局block直接返回
return aBlock;
}
else {// 若是栈block,则malloc申请一块内存,将blockcopy到堆
// 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;
// 用于将当前栈块按位复制到刚刚分配给堆块的内存中,确保所有元数据都被复制
memmove(result, aBlock, size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;// 直接调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
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
// 对block的descriptor进行处理
// 具体是:将discriptor_1经过内存平移得到discriptor_2,并执行discriptor_2中的copy函数指针调用
// 函数执行顺序:_Block_call_copy_helper -> _Block_get_copy_function -> _Block_get_copy_fn -> _Block_get_function_pointer -> _Block_set_function_pointer
_Block_call_copy_helper(result, aBlock);//
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;// 完成copy后,将堆block的isa设置为_NSConcreteMallocBlock堆类型
return result;
}
}
_Block_byref_copy
static struct Block_byref *_Block_byref_copy(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
// 若blcok捕获的对象没有分配内存,则进行malloc分配内存
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// 【第二层拷贝】👇这些操作意思是将位于栈的__block所修饰的对象(即__Block_byref_objc_0)copy到堆上
// 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
// byref引用计数加减以2为单位,所以这里+4
// 为什么引用计数是 +2 而不是 +1 ?,因为flags的第一号位置已经存储着释放标记
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
// copy的forwarding指针指向了自身(堆)
copy->forwarding = copy; // patch heap copy to point to itself
// 让位于栈区的src的forwarding指向新分配位于堆区的__block container
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {// 若有copy能力
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
// 对src内存平移得到src2
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;
// 对src2内存平移得到src3
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {// BLOCK_BYREF_LAYOUT_EXTENDED类似于TiggedPointer,是对Block_byref_3的指针进行的优化
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所修饰的变量的copy
// 当__block修饰的是对象,对象通过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
// 已经拷贝到堆,这里只进行引用计数加1
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
// 这里__forwarding实际指向的是src拷贝后的堆
// 因为上边执行了代码:src->forwarding = copy;
// 这样做,有点里氏替换原则的意思
return src->forwarding;
}
_Block_release
void _Block_release(const void *arg) {
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock) return;
// 若是全局block,直接返回
if (aBlock->flags & BLOCK_IS_GLOBAL) return;
// 若引用计数不为0
if (!(aBlock->flags & BLOCK_NEEDS_FREE)) return;
// 以上都不满足,则释放block对象
if (latching_decr_int_should_deallocate(&aBlock->flags)) {
_Block_call_dispose_helper(aBlock);
_Block_destructInstance(aBlock);
free(aBlock);
}
}
_Block_byref_release
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->forwarding;// 取消指针引用,又将原来指向堆的又重新指回到栈;但这里的栈byref指针任然指向堆空间
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {// 若判断__block变量可被释放
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) {// 是否有辅助的copy函数,即表示__block变量为对象类型
// 通过Block_byref_1内存平移找到Block_byref_2
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
// 将对象类型__block类型变量进行dispose操作
(*byref2->byref_destroy)(byref);
}
free(byref);// 释放__block container 对象自身。
}
}
}
以上
----------End------------
参考文章:
https://clang.llvm.org/docs/Block-ABI-Apple.html
http://blog.devtang.com/2013/07/28/a-look-inside-blocks/
https://www.galloway.me.uk/2013/05/a-look-inside-blocks-episode-3-block-copy/
https://github.com/steipete/Aspects
参考书籍:《Objective-C 高级编程 iOS与OS X多线程和内存管理》
这里有电子书,由于Github访问时不时的不稳定,于是换到了Gitee(Gitee有文件下载大于1M需要登录授权限制 ~~~~)
(限于水平,本文可能存在瑕疵甚至错误的地方。如有发现,还请留言指正,相互学习。thx! )
KEEP LOOKING, DON`T SETTLE!