Block默认不允许修改外部变量的值,我们可以通过对要修改的变量添加__block修饰,来达到可以在block内部修改外部变量的目的。那么__block到底都做了什么呢?为什么添加了__block就可以在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
| #import <Foundation/Foundation.h>
int global_i = 1;
static int static_global_j = 2;
int main(int argc, const char * argv[]) { static int static_k = 3; int val = 4; void (^myBlock)(void) = ^{ global_i ++; static_global_j ++; static_k ++; NSLog(@"Block中 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val); }; global_i ++; static_global_j ++; static_k ++; val ++; NSLog(@"Block外 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val); myBlock(); return 0; }
|
运行结果:
1 2
| Block 外 global_i = 2,static_global_j = 3,static_k = 4,val = 5 Block 中 global_i = 3,static_global_j = 4,static_k = 5,val = 4
|
对于block和外部变量的关系,我们有两个问题需要搞清楚:
- 1.为什么在Block里面不加__bolck不允许更改变量?
- 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
| int global_i = 1;
static int static_global_j = 2;
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int *static_k; int val; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int *static_k = __cself->static_k; int val = __cself->val;
global_i ++; static_global_j ++; (*static_k) ++; NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val); }
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, const char * argv[]) {
static int static_k = 3; int val = 4;
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));
global_i ++; static_global_j ++; static_k ++; val ++; NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_1,global_i,static_global_j,static_k,val);
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0; }
|
在__main_block_func_0
方法中,我们可以看到 对于全局变量不论是否是静态的都是直接使用!(内存中有一块区域是专门存放全局变量的这些变量不会被销毁,因此可以不用拷贝 直接使用) 对于局部变量 如果是静态变量:
int *static_k = __cself->static_k; // bound by copy
新定义了一个变量指向了这个静态变量,但是注意 这里是 int *
这就说明引用的是静态变量的地址。
我们再来看一下val这个局部变量:
int val = __cself->val; // bound by copy
同样新定义了一个变量,这个变量的值等于传入的val的值
其实这个方法的具体参数 我们可以直接通过看main函数中的__main_block_impl_0
方法就可以看出来
1
| void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));
|
区别就是静态变量传入的是&static_k
,普通局部变量传递的是val
.
通过这些,我们可以看出,block内部都是对外部非全局变量进行拷贝,因此如果外部对block内值引用的变量进行了修改不会影响block内变量的值。但是如果是静态变量 那么是会影响的。
– |
静态变量 |
静态全局变量 |
全局变量 |
自动变量 |
block内的传递 |
地址传递 |
地址传递 |
地址传递 |
值传递 |
到此为止,上面提出的第二个问题就解开答案了。
自动变量是以值传递方式传递到Block的构造函数里面去的。Block只捕获Block中会用到的变量。由于只捕获了自动变量的值,并非内存地址,所以Block内部不能改变自动变量的值(block外面变量值的改变也不会影响block内部)。Block捕获的外部变量可以改变值的是静态变量,静态全局变量,全局变量。上面例子也都证明过了。
至此,我们了解 如果要在block内部修改外部的变量,可以使用两种方法:
- 1、传入这个外部变量的内存地址而不是值传递
- 2、使用__block 修饰
Block中外部变量值的修改
传入内存地址
先看下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) { NSMutableString * str = [[NSMutableString alloc]initWithString:@"Hello,"]; void (^myBlock)(void) = ^{ [str appendString:@"World!"]; NSLog(@"Block中 str = %@",str); }; NSLog(@"Block外 str = %@",str); myBlock(); return 0; }
|
输出结果:
1 2
| Block 外 str = Hello, Block 中 str = Hello,World!
|
源码解析
下面我们利用clang来看一下上面这段代码的具体实现
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; NSMutableString *str; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableString *_str, int flags=0) : str(_str) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSMutableString *str = __cself->str;
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)str, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_1); NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_2,str); } 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, 3);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 3);}
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[]) { NSMutableString * str = ((NSMutableString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)((NSMutableString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("alloc")), sel_registerName("initWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_0);
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str, 570425344));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_33ff12_mi_3,str);
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0; }
|
从 static void __main_block_func_0(struct __main_block_impl_0 *__cself)
可以看出 传入的参数是指针 NSMutableString *str = __cself->str; // bound by copy
新建了一个变量指向的是之前的变量的地址。因此block内部改变的仍然是之前的数据。
使用__block修饰
为什么使用了__block
的修饰之后就可以在block内部修改外部变量了呢?这里肯定是系统根据__block
检测,做了一些处理。
这里我们分成基本数据类型和对象类型。
基本数据类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) { __block int i = 0; void (^myBlock)(void) = ^{ i ++; NSLog(@"%d",i); }; myBlock(); 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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| struct __Block_byref_i_0 { void *__isa; __Block_byref_i_0 *__forwarding; int __flags; int __size; int i; };
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_i_0 *i; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; (i->__forwarding->i) ++; NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_3b0837_mi_0,(i->__forwarding->i)); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8);}
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[]) {
__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0; }
|
上面的代码 与我们最初没有使用__block修饰的代码的区别在于:
- 1、外部变量的类型变为了一个
__Block_byref_i_0
结构体
- 2、自加操作对应的是
(i->__forwarding->i) ++;
默认__forwarding指向自己
对于是否可以修改外部变量,我们可以主要集中于这个自加的操作,如果__forwarding
永远指向自身那么直接通过i取到i对应的值就可以了为什么中间加一个__forwarding
呢?
我们知道, ARC环境下,一旦Block赋值就会触发copy,__block修饰的变量也就会copy到堆上,Block的类型也就变成了__NSMallocBlock。
堆上的Block会持有对象。我们把Block通过copy到了堆上,堆上也会重新复制一份Block,并且该Block也会继续持有该__block修饰的对象。当Block释放的时候,__block修饰的对象因为没有被任何对象引用,也会被释放销毁
__forwarding
指针这里的作用就是针对堆的Block,把原来__forwarding
指针指向自己,换成指向_NSConcreteMallocBlock
上复制之后的__block自己。然后堆上的变量的__forwarding
再指向自己。这样不管__block怎么复制到堆上,还是在栈上,都可以通过(i->__forwarding->i)
来访问到变量值。
根据上面的解释,我们可以得出系统使用__block修饰一个外部变量之后为什么就可以在block内部修改外部变量原因:
block创建的时候是在栈上的,对block进行赋值操作之后会将block拷贝到堆上。同时也会将block中使用的对象拷贝到堆上。然后将栈上的__block修饰对象的__forwarding指针指向堆上的拷贝之后的对象。这样我们在block内部修改的时候虽然是修改堆上的对象的值,但是因为栈上的对象的__forwarding指针将堆和栈的对象链接起来。因此达到了修改的目的。
对象类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) { __block id block_obj = [[NSObject alloc]init]; id obj = [[NSObject alloc]init];
NSLog(@"block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj); void (^myBlock)(void) = ^{ NSLog(@"***Block中****block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj); }; myBlock(); return 0; }
|
打印结果:
1 2 3 4
| block_obj = [<NSObject: 0x100b027d0> , 0x7fff5fbff7e8] , obj = [<NSObject: 0x100b03b50> , 0x7fff5fbff7b8]
Block****中********block_obj = [<NSObject: 0x100b027d0> , 0x100f000a8] , obj = [<NSObject: 0x100b03b50> , 0x100f00070]
|
从打印结果我们可以看出 block内部与外部:
- 对于使用__block修饰的变量 对象的地址没有发生改变,但是指向这个对象的指针的地址发生了变化(copy操作的影响)。
- 对于没有使用__block修饰的变量 对象的地址也没有发生变化,指向这个对象的指针地址也发生了变化
但从打印结果 我们看不出不同 下面我们利用clang在进行源码的分析。
源码分析
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
| struct __Block_byref_block_obj_0 { void *__isa; __Block_byref_block_obj_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); id block_obj; };
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; id obj; __Block_byref_block_obj_0 *block_obj; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _obj, __Block_byref_block_obj_0 *_block_obj, int flags=0) : obj(_obj), block_obj(_block_obj->__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_block_obj_0 *block_obj = __cself->block_obj; id obj = __cself->obj;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_e64910_mi_1,(block_obj->__forwarding->block_obj) , &(block_obj->__forwarding->block_obj) , obj , &obj); } static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_obj, (void*)src->block_obj, 8);_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_obj, 8);_Block_object_dispose((void*)src->obj, 3);}
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[]) {
__attribute__((__blocks__(byref))) __Block_byref_block_obj_0 block_obj = {(void*)0,(__Block_byref_block_obj_0 *)&block_obj, 33554432, sizeof(__Block_byref_block_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")); NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_e64910_mi_0,(block_obj.__forwarding->block_obj) , &(block_obj.__forwarding->block_obj) , obj , &obj);
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, (__Block_byref_block_obj_0 *)&block_obj, 570425344));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0; }
|
根据代码中赋值语句的差异 我们可以看到
1 2
| __Block_byref_block_obj_0 *block_obj = __cself->block_obj; id obj = __cself->obj;
|
没有使用__block修饰的是copy的方式传递,使用__block修饰的是采用引用的传递方式。
这就很显然了 没有使用。
Block在MRC和ARC下的不同
__block修饰变量的位置
ARC环境下,一旦Block赋值就会触发copy,__block修饰的对象就会copy到堆上,Block的类型也变成__NSMallocBlock。ARC环境下也是存在__NSStackBlock的时候,这种情况下,__block就在栈上。
MRC环境下,只有copy,__block修饰的变量才会被复制到堆上,否则,__block修饰的变量一直都在栈上,block也只是__NSStackBlock,这个时候__forwarding指针就只指向自己了。
__block修饰变量的操作
在MRC环境下,__block根本不会对指针所指向的对象执行copy操作,而只是把指针进行的复制。
而在ARC环境下,对于声明为__block的外部对象,在block内部会进行retain,以至于在block环境内能安全的引用外部对象,所以才会产生循环引用的问题!
例子
先看下面这段代码:
如果我们想在block内部修改外部变量的值 系统会提示缺失__block的修饰符!
我们添加了__block之后 就可以在block中修改外部变量的值了。
分析
__block到底做了什么,让一个原本不可以在block内修改的变量变得可以修改了呢?
我们先来看一下,被block修饰之后的变量到底都发生了什么变化.
先看一下对象的地址:
1 2 3 4 5 6 7 8 9
| __block NSInteger a = 10; NSLog(@"定义前:%p", &a); void (^blockName)(NSInteger param) = ^(NSInteger param){ a = param; NSLog(@"block内:%p", &a); };
blockName(11); NSLog(@"定义后:%p", &a);
|
打印结果:
1 2 3
| 2018-04-25 11:51:12.120470+0800 Test[9140:975007] 定义前:0x16f4454a8 2018-04-25 11:51:12.120601+0800 Test[9140:975007] block内:0x100e07c88 2018-04-25 11:51:12.120639+0800 Test[9140:975007] 定义后:0x100e07c88
|
这里我们看到 定以后以及block内部这两个位置 a的内存地址是相同的 但是跟定义前的地址是不同的!
那么__block 是做了什么操作修改了这个变量的内存地址!
我们先对这两个地址进行分析:
将这两个16进制内存地址转换为10进制的
title |
16进制 |
10进制 |
定义前 |
0x16f4454a8 |
6161716392 |
block内 |
0x100e07c88 |
4309679240 |
定义后 |
0x100e07c88 |
4309679240 |
6161716392-4309679240 = 1852037152
这个字节差的有点多呀 什么鬼
这是一个很大的值, 因为我们可以确定局部变量a是存放在栈区的 所以 我们可以确认 a在使用__block修饰后被放到了堆区。
这也证实了:a 在定义前是栈区,但只要进入了 block 区域,就变成了堆区。这才是 __block 关键字的真正作用。
当我们使用对象类型的时候呢?
看下面这段代码
1 2 3 4 5 6 7 8 9 10 11 12
| NSMutableString *a = [NSMutableString stringWithString:@"Tom"]; NSLog(@"\n 定以前:------------------------------------\n\ a指向的堆中地址:%p;a在栈中的指针地址:%p", a, &a); void (^foo)(void) = ^{ a.string = @"Jerry"; NSLog(@"\n block内部:------------------------------------\n\ a指向的堆中地址:%p;a在栈中的指针地址:%p", a, &a); }; foo(); NSLog(@"\n 定以后:------------------------------------\n\ a指向的堆中地址:%p;a在栈中的指针地址:%p", a, &a);
|
打印结果:
1 2 3 4 5 6 7 8 9 10
| 2018-04-25 13:56:00.488632+0800 Test[9566:1019521] 定以前:------------------------------------ a指向的堆中地址:0x100c03fb0;a在栈中的指针地址:0x16f7554a8 2018-04-25 13:56:00.489045+0800 Test[9566:1019521] block内部:------------------------------------ a指向的堆中地址:0x100c03fb0;a在栈中的指针地址:0x100c74f20 2018-04-25 13:56:00.489294+0800 Test[9566:1019521] 定以后:------------------------------------ a指向的堆中地址:0x100c03fb0;a在栈中的指针地址:0x16f7554a8 error in connection_block_invoke_2: Connection interrupted
|
由打印我们可以看出 定义前和定义后还有block中 a所指向堆中的内存地址是不变的
!,但是在block中会对外部的变量做一个copy操作 将栈中的指针a拷贝到堆中!(不改变该指针指向堆中的值)。
重点
:对于对象a我们没有使用__block进行修饰但是 我们在block中仍然可以修改这个对象的某一个属性。因此 我们可以得出 block中只是不能修改栈中的指针,但是可以修改栈中指针指向堆中的对象的某些属性。
下面再来看
如果我们想修改这个对象的值(修改这个指针指向的位置而不是修改指针指向位置所代表对象的某个属性)。
系统还是会提示我们 必须要使用block修饰!!!
通过上面的这两个例子我们可以得出结论:
1 2 3 4
| Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。栈区是红灯区,堆区才是绿灯区。
__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。
|
参考文档
深入研究Block捕获外部变量和__block实现原理