Kevinlma的博客

物来顺应,未来不迎,当时不杂,既过不恋

0%

ObjC中的Block机制学习

项目中有时会碰到与block有关的疑难问题,要解决的话对block的原理需要有一个较清晰的认识。虽然网上介绍很多,不过还是自己尝试分析一次记忆更深刻一些。

block类型

  • 非arc工程中,block分为三种,stack、heap和global。
    stack的block在生命周期结束时就会释放(花括号)
    heap的只有当引用计数为0时才会被释放
  • arc的工程中,stack的block会自动转化为heap。
  • block是一个伪对象,针对stack的block, 对其进行retain,release没有效果。所以在非arc工程中,要讲block作为参数外传,赋值时,必须调用copy
  • 在arc下,只有block赋值给了一个strong类型,或者是作为参数向外传,都会自动变为堆类型block

Block的类型

  • 在 ARC 中,捕获外部了变量的 block 的类会是 __NSMallocBlock__ 或者 __NSStackBlock__,如果 block 被赋值给了某个变量在这个过程中会执行 _Block_copy 将原有的 __NSStackBlock__ 变成 __NSMallocBlock__;但是如果 block 没有被赋值给某个变量,那它的类型就是 __NSStackBlock__;没有捕获外部变量的 block 的类会是 __NSGlobalBlock__ 即不在堆上,也不在栈上,它类似 C 语言函数一样会在代码段中。
  • 在非 ARC 中,捕获了外部变量的 block 的类会是 __NSStackBlock__,放置在栈上,没有捕获外部变量的 block 时与 ARC 环境下情况相同

在每个 block 结构体的下面就会存放当前 block 持有的所有对象,无论强弱

Block的实现

*将objc的代码用c重写后,可以看清楚block的原理 * 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
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
__block int count = 2;
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
NSMutableDictionary *Item = [NSMutableDictionary dictionary];
void(^block)(int ,int) = ^(int a, int b){
NSLog(@"this is block,a = %d,b = %d",a,b);

count += 1;
NSLog(@"this is block,age = %d, count=%d",age, count);
NSLog(@"%@", Item);


[userInfo setObject:@"a" forKey:@"key"];


NSLog(@"%@",userInfo);
userInfo = nil;

NSMutableDictionary *obj = [NSMutableDictionary dictionary];
NSLog(@"%@",obj);

};

dispatch_async(dispatch_get_main_queue(), ^{
block(3,5);
});

}
return 0;
}

使用命令进行转化

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

可以得到转化后的c++代码关键部分

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#pragma clang assume_nonnull end

struct __Block_byref_count_0 {
void *__isa;
__Block_byref_count_0 *__forwarding;
int __flags;
int __size;
int count;
};
struct __Block_byref_userInfo_1 {
void *__isa;
__Block_byref_userInfo_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSMutableDictionary *userInfo;
};

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
NSMutableDictionary *Item;
__Block_byref_count_0 *count; // by ref
__Block_byref_userInfo_1 *userInfo; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, NSMutableDictionary *_Item, __Block_byref_count_0 *_count, __Block_byref_userInfo_1 *_userInfo, int flags=0) : age(_age), Item(_Item), count(_count->__forwarding), userInfo(_userInfo->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
__Block_byref_count_0 *count = __cself->count; // bound by ref
__Block_byref_userInfo_1 *userInfo = __cself->userInfo; // bound by ref
int age = __cself->age; // bound by copy
NSMutableDictionary *Item = __cself->Item; // bound by copy

NSLog((NSString *)&__NSConstantStringImpl__var_folders_ks_7j1g9n816j95j9sr80kztrn40000gp_T_main_641ab4_mi_0,a,b);

(count->__forwarding->count) += 1;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_ks_7j1g9n816j95j9sr80kztrn40000gp_T_main_641ab4_mi_1,age, (count->__forwarding->count));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_ks_7j1g9n816j95j9sr80kztrn40000gp_T_main_641ab4_mi_2, Item);


((void (*)(id, SEL, ObjectType _Nonnull, id))(void *)objc_msgSend)((id)(userInfo->__forwarding->userInfo), sel_registerName("setObject:forKey:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_ks_7j1g9n816j95j9sr80kztrn40000gp_T_main_641ab4_mi_3, (id)(NSString *)&__NSConstantStringImpl__var_folders_ks_7j1g9n816j95j9sr80kztrn40000gp_T_main_641ab4_mi_4);


NSLog((NSString *)&__NSConstantStringImpl__var_folders_ks_7j1g9n816j95j9sr80kztrn40000gp_T_main_641ab4_mi_5,(userInfo->__forwarding->userInfo));
(userInfo->__forwarding->userInfo) = __null;

NSMutableDictionary *obj = ((NSMutableDictionary * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableDictionary"), sel_registerName("dictionary"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_ks_7j1g9n816j95j9sr80kztrn40000gp_T_main_641ab4_mi_6,obj);

}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->count, (void*)src->count, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->Item, (void*)src->Item, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->userInfo, (void*)src->userInfo, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->count, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->Item, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->userInfo, 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};

struct __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
struct __block_impl *block;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, void *_block, int flags=0) : block((struct __block_impl *)_block) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_1(struct __main_block_impl_1 *__cself) {
void (*block)(int, int) = (void (*)(int, int))__cself->block; // bound by copy

((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 3, 5);
}
static void __main_block_copy_1(struct __main_block_impl_1*dst, struct __main_block_impl_1*src) {_Block_object_assign((void*)&dst->block, (void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);}

static void __main_block_dispose_1(struct __main_block_impl_1*src) {_Block_object_dispose((void*)src->block, 7/*BLOCK_FIELD_IS_BLOCK*/);}

static struct __main_block_desc_1 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_1*, struct __main_block_impl_1*);
void (*dispose)(struct __main_block_impl_1*);
} __main_block_desc_1_DATA = { 0, sizeof(struct __main_block_impl_1), __main_block_copy_1, __main_block_dispose_1};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
__attribute__((__blocks__(byref))) __Block_byref_count_0 count = {(void*)0,(__Block_byref_count_0 *)&count, 0, sizeof(__Block_byref_count_0), 2};
__attribute__((__blocks__(byref))) __Block_byref_userInfo_1 userInfo = {(void*)0,(__Block_byref_userInfo_1 *)&userInfo, 33554432, sizeof(__Block_byref_userInfo_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableDictionary * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableDictionary"), sel_registerName("dictionary"))};
NSMutableDictionary *Item = ((NSMutableDictionary * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableDictionary"), sel_registerName("dictionary"));
void(*block)(int ,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, Item, (__Block_byref_count_0 *)&count, (__Block_byref_userInfo_1 *)&userInfo, 570425344));

dispatch_async(dispatch_get_main_queue(), ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, (void *)block, 570425344)));

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

通过__block修饰关键词将一个变量转化为mutable时,其本质是创建一个结构体,然后创建一个与变量同名的结构体。比如count变量,就创建了一个__Block_byref_count_0的结构体

1
2
3
4
5
6
7
8
9
struct __Block_byref_count_0 {
void *__isa;
__Block_byref_count_0 *__forwarding;
int __flags;
int __size;
int count;
};
...
__attribute__((__blocks__(byref))) __Block_byref_count_0 count = xxx

当要访问cout的值时,真正的实现是

1
count->__forwarding->count

要修改count的值时,也是修改结构体里的值,而结构体地址并没有改变,从而达到count被动态修改的目的。

1
count->__forwarding->count = 22 //example

之前以为是通过把变量的地址直接捕获传给其他block,方便之后动态修改,然而其实理解有误,是用结构体封装过渡了一下。

可以看到block的本质,就是一个2个结构体+一个static静态函数。每个block会对应生成这三个部分

struct block_impl_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
struct __xxx_block_impl_0
struct __block_impl impl; //一个函数指针,存储静态方法. __block_impl可以理解为void*
struct __xxx_block_desc desc; //desp结构体的指针,存储当前结构体的描述,主要是内存大小
__Block_byref_count_0 *count; //带有__block前缀,block中可以改变值的被捕获变量。最终会传来变量的地址
int age; //捕获的值类型变量,是值拷贝,所以也就是在外修改变量,为什么不影响block里
NSMutalArray* item; //对象变量,也是简单的对象赋值操作
}

struct __xxx_block_desc
{
size_t Block_size;//impl结构的大小,因为有捕获的变量占据内存,所以需要知道大小
void (*copy)(struct __xxx_block_impl_0* , struct __xxx_block_impl_0* ); //copy方法
void (*)(struct __xxx_block_impl_0*); //释放方法
}

static void __xxx_block_func_1(struct __main_block_impl_1 *__cself) { //生成的静态方法
//block中执行代码,执行block代码,相当于执行这个静态函数
int age = __cself->age; 用到的外部变量,从结构体中去拿
balabala
}

block的变量就是拿到了 __xxx_block_impl_0 结构体的指针,
block = & __xxx_block_impl_0(__xxx_block_func_1, &__xxx_block_desc(), age)

执行block的过程,就是调用结构体中的fn, 并传递自身的过程
block->impl(block, age)

Refer

https://juejin.im/post/5b0181e15188254270643e88
https://draveness.me/block-retain-object/
https://cloud.tencent.com/developer/article/1517593