block 转 c++ 源码
如下代码 .h .m文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32//TestClang.h
@interface TestClang : NSObject
+ (void)testBlcok;
@end
//TestClang.m
#import "TestClang.h"
static int numGlobel = 29;
@implementation TestClang
+ (void)testBlcok {
//没有截获局部变量 __NSGlobalBlock__
void(^block1)(void) = ^{
NSLog(@"just a block");
};
NSLog(@" block1 = %@", block1);
block1();
static int numStatic = 12;
int num = 10;
__block int numBlock = 19;
__block int numBlock2 = 30;
__block int numBlockTest = 30;
void(^block2)(void) = ^{
NSLog(@"just a block === %d, numStatic = %d numGlobel = %d numBlock=%d numBlock2=%d numBlockTest = %d", num,numStatic,numGlobel,numBlock,numBlock2,numBlockTest);
};
num = 33;
numStatic = 121;
numGlobel = 129;
numBlock = 22222;
block2();
NSLog(@"block2 = %@", block2);
}
@end
执行:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc TestClang.m
不要引用其他头文件,以免导出报Error
目录下生成了一个TestClang.cpp文件
- testBlock 对应的方法变为:
1 | //_C_ 表示为类方法 _I_ 为实例方法 |
block 变量定义
OC 代码中定义了了block1 和 block2 两个 block 变量,我们看 block1 变量定义1
void(*block1)(void) = ((void (*)())&__TestClang__testBlcok_block_impl_0((void *)__TestClang__testBlcok_block_func_0, &__TestClang__testBlcok_block_desc_0_DATA));
上述定义代码中,可以发现,block1 定义中调用了 __TestClang__testBlcok_block_impl_0
并且将 __TestClang__testBlcok_block_func_0
函数 和 __TestClang__testBlcok_block_desc_0_DATA
地址赋值给了 block1。(你可能注意到了命名规则 __类名__方法名__block__impl_第几个block
,desc 和 func 类似)。
block1() 的调用简化后就是 block1->FuncPtr 了。
那么我们来看一下 __TestClang__testBlcok_block_impl_0
结构体的内部结构1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __TestClang__testBlcok_block_impl_0 {
struct __block_impl impl;
struct __TestClang__testBlcok_block_desc_0* Desc;
__TestClang__testBlcok_block_impl_0(void *fp, struct __TestClang__testBlcok_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;//用于初始化 __block_impl 结构体的isa成员
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
结构体实现了一个同名的构造函数,也就是说将 __TestClang__testBlcok_block_impl_0
结构体的地址赋值给了 block1 变量;__TestClang__testBlcok_block_impl_0
的构造函数参数有 (void*)__TestClang__testBlcok_block_func_0
、__TestClang__testBlcok_block_desc_0_DATA
flags=0。1
2
3
4
5
6
7
8
9
10static void __TestClang__testBlcok_block_func_0(struct __TestClang__testBlcok_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nc_qvb_bh854tz1y0p1hdk5y6km0000gn_T_TestClang_61ed5f_mi_0);
}
static struct __TestClang__testBlcok_block_desc_0 {
size_t reserved;
size_t Block_size;
} __TestClang__testBlcok_block_desc_0_DATA = { 0, sizeof(struct __TestClang__testBlcok_block_impl_0)};
我们可以看到 __TestClang__testBlcok_block_desc_0
中存储着两个参数,reserved 和 Block_size,并且 reserved 赋值为0而 Block_size 则存储着 __TestClang__testBlcok_block_impl_0
的占用空间大小。最终将 __TestClang__testBlcok_block_desc_0
结构体的地址传入 __TestClang__testBlcok_block_impl_0
中赋值给 Desc。
然而 block1 只是一个最简单的 block ,没有截获任何变量。运行时打印 block1 会发现 block1 是 __NSGlobalBlock__
类型(原因:只要block没有引用栈或堆上的数据,编译器则会吧 block1 优化为 __NSGlobalBlock__
类型)。
Block即为 OC 对象。
下面我们来看下截获了外部值的 block。
截获局部变量、静态变量、全局静态变量、__block 变量的 block
block2 变量的定义1
2
void(*block2)(void) = ((void (*)())&__TestClang__testBlcok_block_impl_1((void *)__TestClang__testBlcok_block_func_1, &__TestClang__testBlcok_block_desc_1_DATA, num, &numStatic, (__Block_byref_numBlock_0 *)&numBlock, (__Block_byref_numBlock2_1 *)&numBlock2, (__Block_byref_numBlockTest_2 *)&numBlockTest, 570425344));
block2 变量的声明看起来比较复杂,对应 OC 源码分析:
- 可以看到
__TestClang__testBlcok_block_impl_1
中的结构体成员变多了,那就是截获的自动变量1
2
3
4
5
6
7
8
9
10
11
12
13
14
15struct __TestClang__testBlcok_block_impl_1 {
struct __block_impl impl;
struct __TestClang__testBlcok_block_desc_1* Desc;
int num;
int *numStatic; //静态局部变量地址截获, 对于全局的静态变量,可以直接使用,不需要截获为结构题成员变量
__Block_byref_numBlock_0 *numBlock; // by ref 声明了 __block 的局部变量 (命名规则:__Block_byref_变量名_第几个ref)
__Block_byref_numBlock2_1 *numBlock2; // by ref 声明了 __block 的局部变量
__Block_byref_numBlockTest_2 *numBlockTest; // by ref 声明了 __block 的局部变量
__TestClang__testBlcok_block_impl_1(void *fp, struct __TestClang__testBlcok_block_desc_1 *desc, int _num, int *_numStatic, __Block_byref_numBlock_0 *_numBlock, __Block_byref_numBlock2_1 *_numBlock2, __Block_byref_numBlockTest_2 *_numBlockTest, int flags=0) : num(_num), numStatic(_numStatic), numBlock(_numBlock->__forwarding), numBlock2(_numBlock2->__forwarding), numBlockTest(_numBlockTest->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
接下来同样看下TestClangtestBlcok_block_impl_1 构造函数的参数
__TestClang__testBlcok_block_func_1
block2 的 FuncPtr1
2
3
4
5
6
7
8
9
10static void __TestClang__testBlcok_block_func_1(struct __TestClang__testBlcok_block_impl_1 *__cself) {
__Block_byref_numBlock_0 *numBlock = __cself->numBlock; // bound by ref
__Block_byref_numBlock2_1 *numBlock2 = __cself->numBlock2; // bound by ref
__Block_byref_numBlockTest_2 *numBlockTest = __cself->numBlockTest; // bound by ref
int num = __cself->num; // bound by copy
int *numStatic = __cself->numStatic; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nc_qvb_bh854tz1y0p1hdk5y6km0000gn_T_TestClang_d96c9e_mi_2, num,(*numStatic),numGlobel,(numBlock->__forwarding->numBlock),(numBlock2->__forwarding->numBlock2),(numBlockTest->__forwarding->numBlockTest));
}__self
相当于C++实例方法中指向实例自身的变量this,或是Objevtive-C 实例方法中指向对象自身的变量self,即__this
为指向Block值的变量__TestClang__testBlcok_block_desc_1_DATA
1
2
3
4
5
6static struct __TestClang__testBlcok_block_desc_1 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __TestClang__testBlcok_block_impl_1*, struct __TestClang__testBlcok_block_impl_1*);
void (*dispose)(struct __TestClang__testBlcok_block_impl_1*);
} __TestClang__testBlcok_block_desc_1_DATA = { 0, sizeof(struct __TestClang__testBlcok_block_impl_1), __TestClang__testBlcok_block_copy_1, __TestClang__testBlcok_block_dispose_1};新增了
__TestClang__testBlcok_block_copy_1
和__TestClang__testBlcok_block_dispose_1
方法1
2
3
4
static void __TestClang__testBlcok_block_copy_1(struct __TestClang__testBlcok_block_impl_1*dst, struct __TestClang__testBlcok_block_impl_1*src) {_Block_object_assign((void*)&dst->numBlock, (void*)src->numBlock, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->numBlock2, (void*)src->numBlock2, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->numBlockTest, (void*)src->numBlockTest, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __TestClang__testBlcok_block_dispose_1(struct __TestClang__testBlcok_block_impl_1*src) {_Block_object_dispose((void*)src->numBlock, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->numBlock2, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->numBlockTest, 8/*BLOCK_FIELD_IS_BYREF*/);}__TestClang__testBlcok_block_copy_1
函数中所使用的_Block_object_assign
函数将对象类型对象复制给 Block 用结构体的成员变量 arc 并持有该对象,调用_Block_object_assign
函数相当于retain函数,将对象赋值在对象类型的结构体成员变量中。__TestClang__testBlcok_block_dispose_1
函数中使用_Block_object_dispose
函数释放赋值在 Block 用结构体成员变量 arc 中的对象。调用_Block_object_dispose
函数相当于调用release函数,释放赋值在对象类型结构体中的对象。带
__block
的局部变量变为了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//查看__Block_byref的定义
struct __Block_byref_numBlock_0 {
void *__isa; //(void*)0
__Block_byref_numBlock_0 *__forwarding; //指向结构体地址
int __flags; //0
int __size; //byref 大小
int numBlock; //byref 截获的值
};
__attribute__((__blocks__(byref))) __Block_byref_numBlock_0 numBlock = {(void*)0,(__Block_byref_numBlock_0 *)&numBlock, 0, sizeof(__Block_byref_numBlock_0), 19};
__attribute__((__blocks__(byref))) __Block_byref_numBlock2_1 numBlock2 = {(void*)0,(__Block_byref_numBlock2_1 *)&numBlock2, 0, sizeof(__Block_byref_numBlock2_1), 30};
__attribute__((__blocks__(byref))) __Block_byref_numBlockTest_2 numBlockTest = {(void*)0,(__Block_byref_numBlockTest_2 *)&numBlockTest, 0, sizeof(__Block_byref_numBlockTest_2), 30};
通过上面的拆解,那么改变 __block 修饰的变量的值会变为下面这种形式
(numBlock.__forwarding->numBlock) = 22222;
block 底层关系图解
先拿网络上的图吧,懒得画。
block 的三种类型
1 | __NSGlobalBlock__ ( _NSConcreteGlobalBlock ) |
上面提到block1是 __NSGlobalBlock__
类型,原因是因为没有截获堆上或栈上的变量。测试如下代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24- (void)testBlcok {
//没有截获局部变量 __NSGlobalBlock__
void(^blockA)(void) = ^{
NSLog(@"just a block");
};
NSLog(@"%@", blockA);
//原因:只要block literal里没有引用栈或堆上的数据,那么这个block会自动变为__NSGlobalBlock__类型,这是编译器的优化
// int value = 10; 改为 const int value = 10; 则会为__NSGlobalBlock__ ,因为加上const 的 value存储在常量区
//截获了局部变量 __NSMallocBlock__
int value = 10;
void(^blockB)(void) = ^{
NSLog(@"just a block === %d", value);
};
NSLog(@"%@", blockB);
//一般并不会这么使用
// __NSStackBlock__
void(^ __weak blockC)() = ^{
NSLog(@"just a block === %d", value);
};
NSLog(@"%@", blockC);
}
类型定义:
Block在内存中的存储
数据段中的 __NSGlobalBlock__
直到程序结束才会被回收,不过我们很少使用到__NSGlobalBlock__
类型的block,因为这样使用block并没有什么意义。__NSStackBlock__类
型的block存放在栈中,我们知道栈中的内存由系统自动分配和释放,作用域执行完毕之后就会被立即释放,而在相同的作用域中定义block并且调用block似乎也多此一举。__NSMallocBlock__
是在平时编码过程中最常使用到的。存放在堆中需要我们自己进行内存管理。
Block 超出作用域还能存在的理由 & __forwarding 的作用
配置在全局变量上的 Block, 从变量作用域外也可以通过指针安全的使用,但是设置在栈上的 Block,如果其所属的变量作用域结束,该Block就被废弃。如果__block
(如:Block_byref_numBlock_0) 变量也会配置在栈上,则该`block`变量也会被废弃。将 Block 复制到堆上那么变量作用域结束时不受影响,
impl.isa = &_NSConcreteMallocBlock;
而__block
结构体成员变量__forwarding
可以实现无论__block
变量配置在栈上还是堆上都能够正确的访问__block
变量。通过 Block的复制,__block
也一起复制到堆上,就可以同时访问栈上和堆上的__block
,只要栈上的结构体实例成员变量__forwarding
指向堆上的结构体实例。
在堆上的 Block 持有__block
变量,__block
变量的引用计数 +1,__block
可以被多个 Block 持有。