余额不足.

解析__block

字数统计: 1.2k阅读时长: 5 min
2018/11/12 Share

__block在什么情况下使用?

前一篇文章中我们知道了block拥有捕获变量的能力,但是block却不能改变自动变量的值,每当我们尝试着在block中修改某个自动变量的时候编译器都会抛出一个错误Variable is not assignable (missing __block type specifier),表示该变量在block中不可被赋值,需要使用__block修饰。但是当我们尝试在block中去修改全局变量、静态变量、全局静态变量却是可以通过的,这是为什么?

block捕获到的自动变量只是该变量的值,而全局变量、静态变量、全局静态变量的值为什么能修改?
全局变量和静态全局变量是存储在内存全局区,因为作用域的原因使得他们可以在block中被修改。
静态变量被block捕获的是内存地址的值,所以也能被修改。

__block修饰符都做了些啥?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int val = 1;
__block NSString *str = [NSString string];
NSLog(@"val初始化地址-%p", &val);
NSLog(@"str初始化地址-%p", &str);
void (^aBlock)(void) = ^{
val++;
str = @"a";
NSLog(@"val block地址-%p", &val);
NSLog(@"str block地址-%p", &str);
};
NSLog(@"%@", aBlock);
aBlock();
}
return 0;
}

老套路,clang -rewrite-objc maim.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};

struct __Block_byref_str_1 {
void *__isa;
__Block_byref_str_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSString *str;
};

头部先是建了两个结构体,分别是基础数据类型和对象数据类型,基础数据类型结构体有五个成员:
1、isa指针
2、指向自身类型的指针
3、标记flag
4、占用内存大小
5、变量的值
而对象数据类型则多出了__Block_byref_id_object_copy()__Block_byref_id_object_dispose(),从字面上看着两个函数大概是用于内存管理的。

附上重写后的完整代码,一步一步来分析

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
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_str_1 *str; // by ref
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_str_1 *_str, __Block_byref_val_0 *_val, int flags=0) : str(_str->__forwarding), val(_val->__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_val_0 *val = __cself->val; // bound by ref
__Block_byref_str_1 *str = __cself->str; // bound by ref

(val->__forwarding->val)++;
(str->__forwarding->str) = (NSString *)&__NSConstantStringImpl__var_folders_tr_t3h53q856n11q1hrk0gc6bgm0000gn_T_main_00d5a1_mi_2;
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->str, (void*)src->str, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->str, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->val, 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, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 初始化val
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 1};
__attribute__((__blocks__(byref))) __Block_byref_str_1 str = {(void*)0,(__Block_byref_str_1 *)&str, 33554432, sizeof(__Block_byref_str_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSString * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("string"))};
void (*aBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_str_1 *)&str, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
}
return 0;
}

可以看见block的构造函数中isa是个_NSConcreteStackBlock,但是在__main_block_desc_0()中出现了( copy) 和( dispose),这两个函数会将block推入堆中以及释放block(详情可查阅从源码分析Block的内存管理)。

从main函数开始,先是初始化了val变量,注意观察第二个成员也就是__forwarding,他好像指向了val自己?带着这个疑问咱们继续往下走,NSLog直接忽略,在block构造的时候有个参数是fp也就是__main_block_func_0,这个参数就是block内部回调方法的实现。

1
2
3
4
5
6
7
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
__Block_byref_str_1 *str = __cself->str; // bound by ref

(val->__forwarding->val)++;
(str->__forwarding->str) = (NSString *)&__NSConstantStringImpl__var_folders_tr_t3h53q856n11q1hrk0gc6bgm0000gn_T_main_00d5a1_mi_2;
}

原本我们代码中写的val++,clang重写之后变成了(val->__forwarding->val)++,然后再带上之前__forwarding指针指向的疑问,有点凌乱。。。一步一步捋过来吧,先run一下代码

1
2
3
4
5
val初始化地址-0x7ffeefbff5e8
str初始化地址-0x7ffeefbff5c8
<__NSMallocBlock__: 0x1007235f0>
val block地址-0x100723568
str block地址-0x100523608

执行完之后可以发现block已经变成了NSMallocBlock,而valstr的地址与block的地址相差很小,很大概率上他们也存在了堆区,然后结合下图去分析

__cself->val指向的是栈区的val,但是block中val已经被复制到了堆区,堆区的val指向自己没有任何的问题,但是当访问的是栈区的val要怎样才能正确的访问到变量的值?__forwarding 被设计出来就是为了解决这个问题,如上图所示,栈区的 __forwarding 指向了复制到堆后的 val ,而堆上val指向自己,这也就保证了不论是访问栈还是堆上的val都能获取到正确的值。

再看看重写后的(val->__forwarding->val),是不是已经明白了?

CATALOG
  1. 1. __block在什么情况下使用?
  2. 2. __block修饰符都做了些啥?