block 探索

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//_C_ 表示为类方法 _I_ 为实例方法
static void _C_TestClang_testBlcok(Class self, SEL _cmd) {

void(*block1)(void) = ((void (*)())&__TestClang__testBlcok_block_impl_0((void *)__TestClang__testBlcok_block_func_0, &__TestClang__testBlcok_block_desc_0_DATA));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nc_qvb_bh854tz1y0p1hdk5y6km0000gn_T_TestClang_cf514e_mi_1, block1);
((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
static int numStatic = 12;
int num = 10;
__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};

    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));
num = 33;
numStatic = 121;
numGlobel = 129;
(numBlock.__forwarding->numBlock) = 22222;
((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nc_qvb_bh854tz1y0p1hdk5y6km0000gn_T_TestClang_cf514e_mi_3, block2);
}

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
16
struct __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
10
static 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
    15
    struct __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 的 FuncPtr
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    static 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
    6
    static 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
2
3
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )

上面提到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 持有。