深入分析block

0x01 Block结构

   我们通过命令将Objective-C代码转换为C++代码,看下底层block的结构

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

  测试代码如下:

1
2
3
4
5
6
7
8
int main(int argc, char * argv[]) {

^{
NSLog(@"Hello Block");
};

return 0;
}

  转换后的代码如下:

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
33
34
35
36
37
38
39
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

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; // fp就是前面传进来的__main_block_func_0
Desc = desc; // desc就是前面传进来的__main_block_desc_0_DATA
}
};

// block里面的函数,被解析成一个独立的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_main_a5d38c_mi_0);
}

// block的描述信息,包括block大小
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

// main函数
int main(int argc, char * argv[]) {

// 结构体初始化并赋值
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

return 0;
}

  main函数中初始化了__main_block_impl_0结构体,通过与结构体同名的构造函数将函数实现(__main_block_func_0)地址和函数描述(__main_block_desc_0_DATA)地址传递进了__main_block_impl_0结构。

  我们的block函数被转换成了单独了一个函数__main_block_func_0并赋值给block结构体内FuncPtr成员,这样做我们想必也知道以后要是调用这个函数,直接调用结构体的FuncPtr成员即可。为了证明这点,我们将测试代码改成如下:

1
2
3
4
5
6
7
8
9
10
int main(int argc, char * argv[]) {

void(^helloBlock)(void) = ^{
NSLog(@"Hello Block");
};

helloBlock();

return 0;
}

  我们继续转换代码,这次主要看下main函数里面的实现:

1
2
3
4
5
6
7
8
9
int main(int argc, char * argv[]) {

void(*helloBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

// 使用结构体的FuncPtr成员来调用函数
((void (*)(__block_impl *))((__block_impl *)helloBlock)->FuncPtr)((__block_impl *)helloBlock);

return 0;
}

  并且在前面的结构体内发现了isa,我们知道只要OC对象都会有一个isa指针,所以我们可以知道block本质上也是一个OC对象。

0x02 捕获变量

1. auto修饰词

  我们知道局部变量前面默认都是有个auto修饰符的,所以我们写下如下测试代码:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
int main(int argc, char * argv[]) {

int age = 10;
void(^helloBlock)(void) = ^{
NSLog(@"小明今天%d岁", age);
};

helloBlock();

return 0;
}

// 转换代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age; // 结构体多了一个age成员
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age)/* 成员变量的age在这里被赋值,初始化的时候外部会传值 */ {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // 使用的是__main_block_impl_0成员age

NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_main_942932_mi_0, age);
}

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)};


int main(int argc, char * argv[]) {

int age = 10;
// 初始化,并把10传进结构体
void(*helloBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

((void (*)(__block_impl *))((__block_impl *)helloBlock)->FuncPtr)((__block_impl *)helloBlock);

return 0;

}

  在转换代码里,结构体多了个名为age的成员变量,并且main函数里对block结构体初始化的时候就把10这个值传递进去了,取来用的时候直接调取结构体age这个成员变量。初始化完成后,结构体内的age成员和main函数里定义的age变量其实已经没啥关系了,所以这也是为什么,之后修改变量age这个值,block内读取age的值是不会变的。

2. static修饰词

  修改测试代码,加上static修饰词

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
int main(int argc, char * argv[]) {

static int age = 10;
void(^helloBlock)(void) = ^{
NSLog(@"小明今天%d岁", age);
};

helloBlock();

return 0;
}

// 转换后的代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *age; // 同样是新增了age成员,不同的是这次是个指针
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *age = __cself->age;

NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_main_5c5d34_mi_0, (*age));
}

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)};


int main(int argc, char * argv[]) {

static int age = 10;
// 初始化的时候,也是直接将地址传递进去
void(*helloBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age));

((void (*)(__block_impl *))((__block_impl *)helloBlock)->FuncPtr)((__block_impl *)helloBlock);

return 0;

}

  这次,同样新增了一个age的成员变量,但是跟之前不同的是这次是一个指针成员,初始化的时候直接将变量的地址传递了进去,我们知道如果使用指针传递,我们可以随时随地的修改这个变量内的值,并且读取的时候也是被改变后的值,因为block内部直接访问的是变量的地址。所以,这里就跟前面不一样了,后面修改了值,调用block后,block内部读取变量的值是修改后的值。

3. 全局变量

  测试代码修改如下:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
int age = 10;

int main(int argc, char * argv[]) {

void(^helloBlock)(void) = ^{
NSLog(@"小明今天%d岁", age);
};

helloBlock();

return 0;
}

// 转换后的代码:
int age = 10; // 变量同时也放在了全局

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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_main_4e4a2d_mi_0, age);
}

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)};
int main(int argc, char * argv[]) {

void(*helloBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

((void (*)(__block_impl *))((__block_impl *)helloBlock)->FuncPtr)((__block_impl *)helloBlock);

return 0;



}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

  转换后的代码里,全局变量同样还是全局变量,并没有被吸收进结构体内。既然是也是全局变量,那么这个变量也是想怎么改就怎么改,block内部读取的值也是最新被赋值的值。

property属性

  这次将代码修改为如下,方便可以添加属性:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@interface ViewController ()

@property (nonatomic, assign) int age;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

void(^helloBlock)(void) = ^{
NSLog(@"小明今天%d岁", self.age);
};

helloBlock();
}
@end

// 转换后的代码
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
ViewController *self; // 多了一个控制器自己的成员
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, ViewController *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
ViewController *self = __cself->self; // bound by copy
// 通过runtime读取成员变量的值,所以也可以保证被修改的值,后面block内部读取的时候也是读取最新的
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_ViewController_bc83d3_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("age")));
}
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};

// OC方法默认会传两个参数:self和SEL
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));

// 初始化Block的时候传进去了self
void(*helloBlock)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, self, 570425344));

((void (*)(__block_impl *))((__block_impl *)helloBlock)->FuncPtr)((__block_impl *)helloBlock);
}

  使用属性的时候,block结构体多了一个self的成员变量,初始化的时候将控制器实例传递了进去。读取值的时候通过消息发送机制获取最新的成员变量的值。

0x03 block类型

1. NSGlobalBlock

  block内部没有调用auto修饰符变量的block都是NSGlobalBlock类型。

  这里不使用clang转换代码,因为block都是运行时确定的,所以通过打断点确定block类型。

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
int main(int argc, char * argv[]) {

void(^helloBlock)(void) = ^{
NSLog(@"Hello World");
};

helloBlock();

return 0;
}

// lldb指令打印:
(lldb) po [helloBlock class]
__NSGlobalBlock__

// 又或者使用static修饰符
int main(int argc, char * argv[]) {
static int age = 10;
void(^helloBlock)(void) = ^{
NSLog(@"小明今天%d岁", age);
};

helloBlock();

return 0;
}

// lldb指令打印:
(lldb) po [helloBlock class]
__NSGlobalBlock__

2. NSStackBlock

  首先,我们需要将Xcode里ARC改为MRC

ARC修改

  测试代码如下

1
2
3
4
5
6
7
8
9
10
11
12
int main(int argc, char * argv[]) {
int age = 10;
void(^helloBlock)(void) = ^{
NSLog(@"小明今天%d岁", age);
};

helloBlock();
}

// lldb指令打印:
(lldb) po [helloBlock class]
__NSStackBlock__

  也就是block内部使用了auto修饰符的都是 NSStackBlock类型。

3. NSMallocBlock

  NSStackBlock调用copy得到的就是NSMallocBlock类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, char * argv[]) {
int age = 10;
void(^helloBlock)(void) = [^{
NSLog(@"小明今天%d岁", age);
} copy];

helloBlock();

return 0;
}

// lldb指令打印
(lldb) po [helloBlock class]
__NSMallocBlock__

0x04 Copy

  在ARC下,下列情况会自动将block从栈区复制到堆区。

  • block作为返回值
  • 赋值给strong强引用对象
  • 在Cocoa里作为方法的参数(包括GCD)。

  所以下面的情况,肯定不会自动复制到堆区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main(int argc, char * argv[]) {
int age = 10;
__weak void(^helloBlock)(void) = ^{
NSLog(@"小明今天%d岁", age);
};

helloBlock();

return 0;
}

// lldb指令
(lldb) po [helloBlock class]
__NSStackBlock__

  那么,如果对Block的三种类型进行copy操作会有什么效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// NSGlobalBlock
int main(int argc, char * argv[]) {
void(^helloBlock)(void) = [^{
NSLog(@"Hello World");
} copy];

helloBlock();

return 0;
}

// lldb指令
(lldb) po [helloBlock class]
__NSGlobalBlock__

// NSStackBlock上面已经说过
// NSMallocBlock还是NSMallocBlock

   所以三种block类型使用copy的结果如下:

  • NSGlobalBlock使用copy还是NSGlobalBlock
  • NSStackBlock使用copy,变为NSMallocBlock
  • NSMallocBlock使用copy还是NSMallocBlock,只是引用计数+1

  我们知道ARC下,会自动将block从栈区拷贝到堆区,同时block内部的成员会调用copy函数将成员也拷贝到堆区。

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
int main(int argc, char * argv[]) {
Animal *a = [Animal new];
void(^helloBlock)(void) = ^{
NSLog(@"小明今天%d岁", a.age);
};

helloBlock();

return 0;
}

// 转换代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Animal *__strong a; // __strong 修饰,强引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Animal *__strong _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Animal *__strong a = __cself->a; // bound by copy

NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_main_d37033_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)a, sel_registerName("age")));
}

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, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 3/*BLOCK_FIELD_IS_OBJECT*/);}

// 描述里面多了copy函数和销毁函数
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};


int main(int argc, char * argv[]) {
Animal *a = ((Animal *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Animal"), sel_registerName("new"));
void(*helloBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, 570425344));

((void (*)(__block_impl *))((__block_impl *)helloBlock)->FuncPtr)((__block_impl *)helloBlock);

return 0;

}

  copy函数内部会调用_Block_object_assign函数,这个函数会根据auto 对象的修饰符(strong,weak,unsafe_unretained)做出相应操作,形成强引用还是弱引用。这里先讲auto修饰的对象,当block被拷贝到堆区的时候,其内部也会调用__main_block_copy_0函数将内部的Animal *a对象也拷贝到堆区,同时引用计数+1,我们看下源码,_Block_object_assign函数第三个参数flag,这里是3,即BLOCK_FIELD_IS_OBJECT。其他的情况在__block修饰的时候讲。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
enum {

// 一个对象
BLOCK_FIELD_IS_OBJECT = 3,
// 一个block变量
BLOCK_FIELD_IS_BLOCK = 7,
// 被__block修饰的变量
BLOCK_FIELD_IS_BYREF = 8,
// 被__weak修饰的变量,只能被辅助copy函数使用
BLOCK_FIELD_IS_WEAK = 16,
// block辅助函数调用,仅赋值,内部实现不进行retain或copy
BLOCK_BYREF_CALLER = 128
};

// flag上层传了3,即BLOCK_FIELD_IS_OBJECT
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
......
// 也就是来到这里
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
// 持有对象
_Block_retain_object(object);
_Block_assign((void *)object, destAddr);
}
}

  我们可以看到其内部通过_Block_retain_object函数强引用了Animal *a对象。那么还有一种__weak修饰的变量,代码如下:

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
int main(int argc, char * argv[]) {
__weak Animal *a = [Animal new];
void(^helloBlock)(void) = ^{
NSLog(@"小明今天%d岁", a.age);
};

helloBlock();
return 0;
}

// 转换后的代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Animal *__weak a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Animal *__weak _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Animal *__weak a = __cself->a; // bound by copy

NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_main_39c8fe_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)a, sel_registerName("age")));
}

  可以看到,Animal *a是被weak修饰的,所以block对a成员的持有是弱引用。所以我们需要记住,auto修饰的对象,如果不是weak修饰过的,那么block内部会对对象强引用。那么先看下面这段代码:

1
2
3
4
5
6
int main(int argc, char * argv[]) {
{
Animal *a = [Animal new];
} // 出了这个括号,对象会被销毁
return 0;
}

  对象会在出了对象后被销毁,那么如下代码会发生什么,出了括号后会被销毁吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, char * argv[]) {
void(^helloBlock)(void) = nil;
{
Animal *a = [Animal new];
helloBlock = ^{
NSLog(@"小明今天%d岁", a.age);
};
}

NSLog(@"");// 断点在这里,会发生什么

return 0;
}

  根据上面讲的,想必大家都知道这里Animal对象不会被销毁。因为auto修饰的对象,会被block强引用。

  还有一个,如果要从堆上移出会调用block内部的dispose函数,内部调用_Block_object_dispose函数,会自动释放引用的auto变量,作用类似于release,具体不详细表述了。

0x05 __block修饰符

  前面我们说过,通过static修饰的局部变量和全局变量被block捕获后,外部修改变量的值后,block内部读取的时候也是最新的值,但是实际需求中,我只是临时使用下这个变量,函数执行完毕后,这个变量销毁就可以了,不希望变量被放到全局区,所以这时候需要通过__block来修饰。

1. 修饰局部变量

  还是代码走起

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
int main(int argc, char * argv[]) {
__block int age = 10;
void(^helloBlock)(void) = nil;
{
helloBlock = ^{
NSLog(@"小明今天%d岁", age);
};
}

age = 20;

helloBlock();

return 0;
}

// 转换代码
// 变量被转换成这种结构体了
struct __Block_byref_age_0 {
void *__isa; // 有isa,可以理解为被转换后,在内部,该变量变成了一个OC对象
__Block_byref_age_0 *__forwarding; // 指向该实例自身的指针
int __flags;
int __size;
int age; // 原来的变量
};

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // block修饰的变量,变成了一个结构体
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref

NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_main_264df2_mi_0, (age->__forwarding->age));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};

int main(int argc, char * argv[]) {
// 初始化__Block_byref_age_0,并把age地址传给forwarding,说明forwarding是指向自己的
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
// 初始化block函数
void(*helloBlock)(void) = __null;
{
helloBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
}

// 重新赋值,已经变成给结构体赋值了
(age.__forwarding->age) = 20;
// 调用block函数
((void (*)(__block_impl *))((__block_impl *)helloBlock)->FuncPtr)((__block_impl *)helloBlock);

return 0;

}

  这里就很有趣了,__block修饰的变量会被改成一个结构体,而且该结构含有isa成员,那么这个变量很明显被转换成一个OC对象了,原本的变量也被包含在这个对象内,而且对于这个成员的管理,block这个对象默认是强引用这个变量,这个是不同于没被__block修饰的变量。

1
2
3
Animal *a = [Animal new]; // block会对其强引用
__weak Animal *b = [Animal new]; // block会对其弱引用
__block Animal *c = [Animal new]; // block会对其强引用

  对__block修饰的变量的修改,也就是对这个对象的age成员的修改((age.__forwarding->age) = 20;)。同时这个对象还有一个forwarding指针,这个的作用就是指向自己,如果在栈区就指向在栈区的实例地址,如果在堆区就指向在堆区的实例地址,下面源代码里也会证明这一点:

forwarding指针示意图

  同时,也可以发现_Block_object_assign的第三个参数flag变为8了,即BLOCK_FIELD_IS_BYREF,继续看源码

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
.......
else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF) {
_Block_byref_assign_copy(destAddr, object, flags);
}
......
}

static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
struct Block_byref **destp = (struct Block_byref **)dest;
struct Block_byref *src = (struct Block_byref *)arg;

// 如果没有被引用,说明还没拷贝到堆上。这里的操作就是将block修饰的变量(即已经转换成的__Block_byref_age_0结构体)拷贝到堆上
else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
// if its weak ask for an object (only matters under GC)
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (isWeak) {
copy->isa = &_NSConcreteWeakBlockVariable; // mark isa field so it gets weak scanning
}
if (src->flags & BLOCK_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
copy->byref_keep = src->byref_keep;
copy->byref_destroy = src->byref_destroy;
(*src->byref_keep)(copy, src);
}
else {
// just bits. Blast 'em using _Block_memmove in case they're __strong
_Block_memmove(
(void *)&copy->byref_keep,
(void *)&src->byref_keep,
src->size - sizeof(struct Block_byref_header));
}
}
// __Block_byref_age_0已经拷贝到堆上了,只增加引用计数。因为可能多个block调用同一个对象
else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
// 将复制到堆上的变量地址赋值给__Block_byref_age_0的forwarding实例指针
// 我们可以看到赋值到堆上,被转换的对象结构体里的forwarding指针被指向堆上地址了
_Block_assign(src->forwarding, (void **)destp);
}

  我们__block修饰的变量不仅被转换成一个对象结构体,并且第一次使用的时候还会被拷贝到堆上。这里还需要注意一个地方,就是如果是同一个变量被多个block捕获,那么这个变量在堆上只存在一份地址,block对其只是引用计数加1,而不是说多个block捕获这个变量,这个变量就会被多次拷贝到堆上。

  最后,因为这个变量被拷贝到堆区了,所以需要将变量转换后的结构体里的forwarding指向这个堆区地址,由于只是拷贝操作,所以栈上存在一份,堆上也存在一份。因此,如果__block修饰的对象或变量在栈区,则forwarding执行栈区地址,如果被复制到的堆区,则栈上的forwarding指向堆区地址,被拷贝到堆上的对象的forwarding则指向自己在堆上的地址。

2. 修饰对象

  同样的如果__block修饰的是对象的话,又会是什么样的结构?

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
int main(int argc, char * argv[]) {
__block Animal *a = [Animal new];
a.age = 10;
void(^helloBlock)(void) = nil;
{
helloBlock = ^{
NSLog(@"小明今天%d岁", a.age);
};
}

a.age = 20;

helloBlock();

return 0;
}

// 转换代码
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Animal *__strong a;
};

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;
}
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref

NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_main_62a055_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)(a->__forwarding->a), sel_registerName("age")));
}
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*/);}

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};

int main(int argc, char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 33554432, sizeof(__Block_byref_a_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Animal *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Animal"), sel_registerName("new"))};
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)(a.__forwarding->a), sel_registerName("setAge:"), 10);
void(*helloBlock)(void) = __null;
{
helloBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
}

((void (*)(id, SEL, int))(void *)objc_msgSend)((id)(a.__forwarding->a), sel_registerName("setAge:"), 20);

((void (*)(__block_impl *))((__block_impl *)helloBlock)->FuncPtr)((__block_impl *)helloBlock);

return 0;
}

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
// 参数131表示BLOCK_BYREF_CALLER|BLOCK_FIELD_IS_OBJECT
_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修饰的对象也被转换成一个结构体,这个结构体不同之处就是多了两个函数__Block_byref_id_object_copy__Block_byref_id_object_dispose,用来管理结构体的成员 Animal a的内存,之前因为是变量所以不需要管理,只需要管理其所在的结构体内存就可以了。其他跟之前也一样,对象被拷贝到堆区。不同于__main_block_copy_0__main_block_dispose_0__Block_byref_id_object_copy__Block_byref_id_object_dispose是用来管理Block_byref_a_0结构体内的Animal a成员的声明周期,而__main_block_copy_0__main_block_dispose_0管理的是__Block_byref_a_0的生命周期。

  我们看下多出两个函数的源码,里面同样调用了_Block_object_assign函数,只是flag变为了131,其实就是BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT,那么前面的40又是什么?首先dst指向的就是__Block_byref_a_0地址,回到这个结构体,发现+40其实就是成员Animal *a的地址

1
2
3
4
5
6
7
8
9
 struct __Block_byref_a_0 {
void *__isa; // 8
__Block_byref_a_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
Animal *__strong a;
};

  进入源码后就会执行如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
_Block_assign_weak(object, destAddr);
}
else {
// 来到这个分支,只是赋值,因为之前已经移动到堆区了
_Block_assign((void *)object, destAddr);
}
}
.....
}

  观察下面的测试代码:

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
typedef  void(^HelloBlock)(void);
@interface ViewController ()

@property (nonatomic, copy) HelloBlock block;

@end

@implementation ViewController

// ① 这个构造block的函数
- (void)contructBlock1 {
static int age = 10;
self.block = ^{
NSLog(@"小明今天%d岁", age);
};
}

// ② 另外一个构造block的函数
- (void)contructBlock2 {
__block int age = 10;
self.block = ^{
NSLog(@"小明今天%d岁", age);
};
}

- (void)viewDidLoad {
[super viewDidLoad];

[self contructBlock1];
// [self contructBlock2];
self.block();
}

  不卖关子,直接说结果,调用contructBlock1方法后再执行block,会闪退;而调用contructBlock2方法则是正常运行。这是因为static修饰的变量不会被block持有,离开作用域后再访问这个变量就会有问题;而__block修饰的变量,会被block持有,所以即使离开了作用域也没关系。

  总结:

  • 当在栈上的时候block对象不会对auto和block修饰的变量强引用

  • 当auto和block修饰的变量拷贝到堆上的时候,就会产生强引用

    _Block_object_assign((void)&dst->a, (void)src->a, 3);

    _Block_object_assign((void)&dst->a, (void)src->a, 8);

  • 当auto和block修饰的变量需要从堆上移出的时候

    _Block_object_dispose((void*)src->a, 3);

    _Block_object_dispose((void*)src->a, 8);

0x06 循环引用

  首先,创建一份会循环引用的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef  void(^HelloBlock)(void);
@interface ViewController ()

@property (nonatomic, assign) int age;
@property (nonatomic, copy) HelloBlock block;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

self.age = 10;
self.block = ^{
NSLog(@"小明今天%d岁", self.age);
};
self.age = 20;
self.block();
}

  这里因为用到了强引用和弱引用,设计runtime,所以clang转换命令改为如下:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.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
33
34
35
36
37
38
39
40
41
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
// block强引用了self
ViewController *const __strong self;
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, ViewController *const __strong _self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

// block函数
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
ViewController *const __strong self = __cself->self; // bound by copy

NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_ViewController_0193cf_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("age")));
}


static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};

// viewDidLoad方法
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));

((void (*)(id, SEL, int))(void *)objc_msgSend)((id)self, sel_registerName("setAge:"), 10);
((void (*)(id, SEL, HelloBlock))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, self, 570425344)));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)self, sel_registerName("setAge:"), 20);
((HelloBlock (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("block"))();
}

  我们看到转换代码中,block结构体内是强引用了self。而self又本来就强引用block的,所以这就造成了循环引用。一般解决方法是加上__weak。

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
- (void)viewDidLoad {
[super viewDidLoad];

__weak ViewController *weakSelf = self;
self.block = ^{
NSLog(@"小明今天%d岁", weakSelf.age);
};
self.age = 20;
self.block();
}

// 转换代码
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
ViewController *__weak weakSelf; // 变为弱引用self了
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, ViewController *__weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

// 但block函数里同样是弱引用,这就会造成一个问题。如果block执行的时候self被销毁了,那么这里就要出问题了,所以这也就是为什么,需要在block里面强引用回self。
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
ViewController *__weak weakSelf = __cself->weakSelf; // bound by copy

NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_ViewController_337367_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)weakSelf, sel_registerName("age")));
}

  加上__weak,block结构体弱引用self了,这就解决了循环引用问题了。

  同样,在MRC下,只要加上block修饰符也可以解决循环引用问题,如下代码:

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
33
34
35
36
37
38
39
- (void)viewDidLoad {
[super viewDidLoad];

__block ViewController *vc = self;
self.block = ^{
NSLog(@"小明今天%d岁", vc.age);
};
self.age = 20;
self.block();
}

// 转换代码
struct __Block_byref_vc_0 {
void *__isa;
__Block_byref_vc_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
ViewController *__strong vc;
};

struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
__Block_byref_vc_0 *vc; // by ref
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_vc_0 *_vc, int flags=0) : vc(_vc->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
__Block_byref_vc_0 *vc = __cself->vc; // bound by ref

NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_0_j9qb4d4fn9_wnf3fp9q40ssw1_56_T_ViewController_87e968_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)(vc->__forwarding->vc), sel_registerName("age")));
}

  这是因为,self被转换为一个结构体并复制到堆上,并且由__Block_byref_id_object_copy__Block_byref_id_object_dispose对这个self进行管理,需要注意的是MRC下__Block_byref_vc_0ViewController *vc是弱引用,这不同于ARC。所以只要block执行完毕后,会调用__ViewController_viewDidLoad_block_dispose_0__Block_byref_vc_0进行销毁,接着__Block_byref_vc_0会调用__Block_byref_id_object_dispose函数对self进行销毁,所以不存在循环引用的问题了。