静态变量和全局变量为什么不用加__block都可以修改值
1、静态变量和全局变量在加和不加__block都会直接引用变量地址。也就意味着可以修改变量的值。在没有加__block关键字的情况下。
2、常量变量(NSString *a=@"hello"; a为变量,@“hello”为常量。)
不加__block类型,block会直接取常量值(浅拷贝)。
加__block类型,block会去引用变量的地址。(如:a变量,a = @"abc".可以任意修改a 指向的内容。)
如果不加__block 直接在block内部修改变量 ,会编译报错。block内部改变量是只读的。
Block的分类
- block主要分为三类:
① 全局block:_NSConcreteGlobalBlock
;存储在全局内存中,相当于单例。
② 栈block:_NSConcreteStackBlock
;存储在栈内存中,超出其作用域则马上被销毁。
③ 堆block:_NSConcreteMallocBlock
;存储在堆内存中,是一个带引用计数的对象,需要
自行管理其内存。
auto变量:
栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配是有alloc函数进行分配的,但是栈的动态分配和堆是不同的,他的动态分配由编译器进行释放,无需我们手工实现。
即:
block的类型 | 环境 |
---|---|
__NSGlobalBlock__ |
没有访问 auto 变量 |
__NSStackBlock__ |
访问了 auto 变量 |
__NSMallocBlock__ |
NSStackBlock 调用了 copy |
这三种block各自的存储区域如下图:
简而言之,存储在栈中的block
就是栈块,存储在堆区的就是堆块,既不在栈区也不在堆区的就是全局块
- 当我们遇到一个
block
,怎么去判定这个block的存储位置呢?
外部变量:
(1)block
不访问外部变量(包括栈和堆中的变量)
此时block
既不在栈中,也不在堆中,在代码段中。ARC
和MRC
下都是如此。
此时为全局block:_NSConcreteGlobalBlock
void(^block)(void) = ^{
};
NSLog(@"%@", block);
/*输出结果为*/
<__NSGlobalBlock__: 0x100004030>
(2)block访问外部变量
MRC
环境下:访问外部变量的block
默认是存储在栈
中的。
ARC
环境下:访问外部变量的block
默认是存储在堆
中的(实际是放在栈
区,然后ARC
情况下又自动拷贝到堆
区),自动释放。
- MRC 环境:
int a = 10;
void(^block)(void) = ^{
NSLog(@"%d", a);
};
NSLog(@"%@", block);
/*输出结果为*/
<__NSStackBlock__: 0x7ffeefbff3e8>
- ARC 环境下
int a = 10;
void(^block)(void) = ^{
NSLog(@"%d", a);
};
NSLog(@"%@", block);
/*输出结果为*/
<__NSMallocBlock__: 0x1040508b0>
- 在ARC环境下我们怎么获取
栈block
呢?
我们可以这样做:
int a = 10;
void(^ __weak block)(void) = ^{
NSLog(@"%d", a);
};
NSLog(@"%@", block);
/*输出结果为*/
<__NSStackBlock__: 0x7ffeefbff3e8>
此时我们通过__weak
不进行强持有,block
就还是栈区的block。
- ARC环境下,访问外部变量的
block
为什么要自动从栈区拷贝到堆区呢?
因为:栈上的block
,如果其所属的变量作用域结束,该block
就会被废弃,如同一般的自动变量。当然,block
中的__block
变量也同时会被废弃。
为了解决栈块在其变量作用域结束之后被废弃(释放)的问题,我们需要把block
复制到堆中,延长其生命周期。开启ARC
时,大多数情况下编译器会恰当的进行判断是否有必要将block
从栈复制到堆,如果有,自动生成将block
从栈复制到堆的代码。block
的复制操作执行的是Copy
实例方法。block
只要调用了Copy
方法,栈块就会变成堆块。
eg:
typedef int(^myblock)(int);
myblock func(int a) {
return ^(int b) {
return a * b;
};
}
上面的代码中,函数返回的block
是配置在栈上的,所以返回返回函数调用方法时,block
变量作用域就被释放了,block
也会被释放。但是,在ARC
环境下是有效的,这种情况编译器会自动完成复制。
在非ARC
情况下则需要开发者调用Copy
方法手动复制。
block的类型 | 副本源的配置存储区域 | 复制效果 |
---|---|---|
_NSConcreteGlobalBlock |
程序的数据区域 | 什么也不做 |
_NSConcreteStackBlock |
栈区 | 从栈区复制到堆区 |
_NSConcreteMallocBlock |
堆区 | 引用计数增加 |
根据表格我们知道,block
在堆区Copy会造成引用计数增加,这与其它OC对象是一样的。虽然block
在栈中也是以对象的身份存在,但是栈区没有引用计数,因为不需要,我们都知道栈区的内存由编译器自动分配释放。
三、block 底层分析
int a = 10;
void(^ block)(void) = ^{
NSLog(@"%d", a);
};
NSLog(@"%@", block);
使用 clang
将OC
代码转换成C++
文件,查看block
的方法。
- 在命令行输入下面的指令(XXX.m就是要编译的文件,需在当前文件夹下面执行)
clang -rewrite-objc XXX.m
执行网上面的指令之后,当前文件夹中会多一个
XXX.cpp
的文件。此时在命令行输入open XXX.cpp
或者 直接打开文件打开
XXX.cpp
文件,在文件底部我们可以看到main
函数被编译之后的样式:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10;
void(* block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__pbdfr2t16xx3pkwg63c2y5m80000gn_T_main_c75271_mi_1, block);
}
return 0;
}
我们从main
函数中提取一下block
void(* block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
/*简化一下,去除强制转换*/
void(* block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a); ///构造函数
可以看到构造函数名为__main_block_impl_0
下面我们再寻找一下__main_block_impl_0
:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 可以看到
__main_block_impl_0
是一个结构体。同时我们也可以说block
是一个__main_block_impl_0
类型的对象,这也是为什么block
能够%@
打印的原因
1、block自动捕获外部变量
- block自动捕获的外部变量,在block的函数体内是不允许被修改的。
① 通过上面的代码我们可以看到__main_block_impl_0
函数的定义:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; ///编译器自动生成的名字相同的变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _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) {
int a = __cself->a; // bound by copy 值拷贝
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__pbdfr2t16xx3pkwg63c2y5m80000gn_T_main_ab7cb4_mi_0, a);
}
可以看到,编译器会自动生成一个同名的变量。
__main_block_func_0
中a
是值拷贝。
因此,在block
内存会生成一个,内容一样的同名变量,此时如果在函数体内进行a++
的操作,则编译器就不清楚该去修改哪个变量。所以block
自动捕获的变量,在函数体内部是不允许修改的。
- 那么我们要修改外部变量要怎么办呢?
1、__block
修饰外部变量。
2、将变量定义成全局变量
3、将变量用参数的形式,传入block
里面。
第2种和第3种方式,想必大家都非常的熟悉,在这里就不再赘述。下面我们来看一下第1种方式,底层究竟做了些什么。
__block 原理
- 现在我们对
a
进行__block
编译,之后我们就可以在block
内部对a
进行修改。
__block int a = 10;
void(^ block)(void) = ^{
a++;
NSLog(@"%d", a);
};
block();
下面我们再通过clang
来观察一下,底层代码有了什么变化。
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int 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 指针拷贝
/// 等同于外界的 a++
(a->__forwarding->a)++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__pbdfr2t16xx3pkwg63c2y5m80000gn_T_main_b21337_mi_0, (a->__forwarding->a));
}
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, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void(* block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
- 首先我们看到,在
main
函数里面,此时a
变成了成了一个__Block_byref_a_0
类型的对象。 - 同时在
__main_block_func_0
函数中,由之前的值拷贝(bound by copy
) 变成了现在的指针拷贝(bound by ref
)
*在main
函数中传入的a是一个对象,同时在__main_block_func_0
函数内部,对a
进行指针拷贝;则此时创建的对象a
和传入的对象a
指向同一片内存空间。
总结:
__block
修饰外界变量的时候:
1、外界变量会生成__Block_byref_a_0
结构体
2、结构体用来保存原始变量的指针
和值
(可以在上面编译后的代码中找到)
3、将变量生成的结构体对象的指针地址
传递给block
,然后在block
内部就可以对外界变量
进行修改了。
接下来,在给大家看一个东西:
- 在上面的C++代码中,
__main_block_func_0
函数中,大家会注意到执行a++
的是这段代码(a->__forwarding->a)++
;,那么这个__forwarding
又是什么呢?
接下来我们先看一下__Block_byref_a_0
结构体长什么样子:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
可以看到,__forwarding
是一个指向自己本身的指针(自身为结构体)。
那么,在Copy
操作之后,既然__block
变量也被Copy
到堆区上了,那么访问该变量是访问栈上的还是堆上的呢?这个时候我们就要来看一下,在Copy
过程中__forwarding
的变化了:
可以看到,通过__forwarding
,无论是在block
中,还是block
外访问__block
变量,也不管该变量是在栈上或是在堆上,都能顺利的访问同一个__block
变量。
注意:这里与上面的结论并不矛盾。大家要主要到局部变量a
被__block
修饰之后,会变成__Block_byref_a_0
结构体对象。所以无论是在栈区还是在堆区,只要__forwarding
指向的地址一样,那么就可以在block
内部修改外界变量。这里大家要仔细观察一下__Block_byref_a_0
结构体
Block对局部变量的修改特殊情况如:局部变量为NSMutableArray类型,在block初始化了以后,对NSMutableArray这些add或者remove操作,都会同步到Block里边的
-(void )test3
{
NSString *_person2=@"person2";
NSMutableArray *_listTest=[[NSMutableArray alloc]init];
//初始值
NSLog(@"init _person2:%@,%p",_person2,_person2);
NSLog(@"init _listTest:%@,%p",_listTest,_listTest);
void (^myBlock)(int) = ^(int num) {
//block内赋值
// _weakPerson2=@"person22";
NSLog(@"excuteing _person2:%@,%p",_person2,_person2);
NSLog(@"excuteing _listTest:%@,%p",_listTest,_listTest);
};
//修改前赋值
_person2=@"person22";
[_listTest addObject:@"1212"];
NSLog(@"excutebefore _person2:%@,%p",_person2,_person2);
NSLog(@"excutebefore _listTest:%@,%p",_listTest,_listTest);
myBlock(1);
//block执行后
NSLog(@"excuteafter _person2:%@,%p",_person2,_person2);
NSLog(@"excuteafter _listTest:%@,%p",_listTest,_listTest);
}
输出结果
2014-07-29 11:05:29.460 Test[2540:60b] init _person2:person2,0xb18ec
2014-07-29 11:05:29.463 Test[2540:60b] init _listTest:(
),0x17d98560
2014-07-29 11:05:29.464 Test[2540:60b] excutebefore _person2:person22,0xb193c
2014-07-29 11:05:29.465 Test[2540:60b] excutebefore _listTest:(
1212
),0x17d98560
2014-07-29 11:05:29.467 Test[2540:60b] excuteing _person2:person2,0xb18ec
2014-07-29 11:05:29.468 Test[2540:60b] excuteing _listTest:(
1212
),0x17d98560
2014-07-29 11:05:29.470 Test[2540:60b] excuteafter _person2:person22,0xb193c
2014-07-29 11:05:29.471 Test[2540:60b] excuteafter _listTest:(
1212
),0x17d98560
从日志可以看出:block内部对于可变、不可变的变量都无法修改,而且
1.在block初始化后对于NSString 变量 _person2 的修改,并没有同步到block内部,因为这时block外部的变量_person2指针重新指向另外一块内存
2.在block初始化后对于NSMutableArray变量 _listTest 的修改,同步到block内部,因为这时block外部的变量 _listTest 指针指向的内存地址没有变,只是对这块内存的值进行了操作。
我们可以借助 clang -rewrite-objc 转换.c文件得到.cpp文件,也可以转换.m也可以得到cpp文件(可能会有些报错)
以下是部分转换后的代码
//这里就是block对象的结构
//imp:函数指针对象,FuncPtr指向具体block实现的函数
//_person2:截获的变量
//isa、flags、funcptr、desc后面会说道。
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __KDBlockTest__test3_block_impl_0 {
struct __block_impl impl;
struct __KDBlockTest__test3_block_desc_0* Desc;
NSString *_person2;
NSMutableArray *_listTest;
__KDBlockTest__test3_block_impl_0(void *fp, struct __KDBlockTest__test3_block_desc_0 *desc, NSString *__person2, NSMutableArray *__listTest, int flags=0) : _person2(__person2), _listTest(__listTest) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//block实现的函数
static void __KDBlockTest__test3_block_func_0(struct __KDBlockTest__test3_block_impl_0 *__cself, int num) {
NSString *_person2 = __cself->_person2; // bound by copy
NSMutableArray *_listTest = __cself->_listTest; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_4,_person2,_person2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_5,_listTest,_listTest);
}
//block对象的描述信息(大小等等)
static struct __main1_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main1_block_desc_0_DATA = { 0, sizeof(struct __main1_block_impl_0)};
//这是objc测试函数test
static void _I_KDBlockTest_test3(KDBlockTest * self, SEL _cmd) {
NSString *_person2=(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_1;
NSMutableArray *_listTest=((id (*)(id, SEL))(void *)objc_msgSend)((id)((id (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_2,_person2,_person2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_3,_listTest,_listTest);
void (*myBlock)(int) = (void (*)(int))&__KDBlockTest__test3_block_impl_0((void *)__KDBlockTest__test3_block_func_0, &__KDBlockTest__test3_block_desc_0_DATA, _person2, _listTest, 570425344);
_person2=(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_6;
((void (*)(id, SEL, id))(void *)objc_msgSend)((id)_listTest, sel_registerName("addObject:"), (id)(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_7);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_8,_person2,_person2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_9,_listTest,_listTest);
((void (*)(__block_impl *, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 1);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_10,_person2,_person2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_11,_listTest,_listTest);
}
简单分析block截获变量:
1).block初始化
void (*myBlock)(int) = (void (*)(int))&__KDBlockTest__test3_block_impl_0((void *)__KDBlockTest__test3_block_func_0, &__KDBlockTest__test3_block_desc_0_DATA, _person2, _listTest, 570425344);
传入了参数:函数指针、block描述、外部变量 _person2 和 _listTest,这时候在block内部对 _person2、_listTest 进行了引用
: _person2(__person2), _listTest(__listTest)
1.在block初始化后,我们对 _person2 做了修改,重新指向了 0xb193c 这块内存,但是不会影响block结构体成员_person2,因为成员 _person2 指向的是 0xb18ec。
2.向 _listTest 数组中添加了一个元素,并没有改变它的内存地址,依然还是 0x17d98560
_person2=(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_6;
((void (*)(id, SEL, id))(void *)objc_msgSend)((id)_listTest, sel_registerName("addObject:"), (id)(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_7);
2).执行block
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
其实还是调用了block对象里的函数对象(_block_imp1)的函数指针(FuncPtr) 所指向的函数__main1_block_func_0,并把block自己作为参数传递进去。
static void __KDBlockTest__test3_block_func_0(struct __KDBlockTest__test3_block_impl_0 *__cself, int num) {
NSString *_person2 = __cself->_person2; // bound by copy
NSMutableArray *_listTest = __cself->_listTest; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_4,_person2,_person2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_5,_listTest,_listTest);
}
总结:对于局部变量,变量不加__block修饰符,在block内部是无法修改变量的值。而且
- 对值类型的修改,如果block初始化后,无法同步到block内部
- 对于引用类型的修改,如果block初始化后,修改指针指向,即指向另外一块内存,这样也是无法同步到block内部
- 对于引用类型的修改,如果block初始化后,对指针指向的内存进行修改,即NSMutableArray add 、remove操作,这样是可以用同步到block内部,但block内部同样无法修改。
Block对成员变量
结论:
对于成员变量,结果却不一样,加了__block和不加__block修饰符效果都是一样的,而且不用区分是引用类型和值类型,block初始化后,对于block内部引用的变量的修改,也能同步到block内部,并且在block内部可以修改成员变量的值。
Demo:
声明两个变量:_person2、_person3
@interface KDBlockTest()
{
NSString *_person2;
__block NSString *_person3;
}
添加测试方法,输出变量的值、地址、指针地址
-(void )test3
{
_person2=@"person2";
_person3=@"person3";
//初始值
NSLog(@"init _person2:%@,%p",_person2,_person2);
NSLog(@"init _person3:%@,%p",_person3,_person3);
void (^myBlock)(int) = ^(int num) {
//block内赋值
_person3=@"person33";
NSLog(@"excuteing _person2:%@,%p",_person2,_person2);
NSLog(@"excuteing _person3:%@,%p",_person3,_person3);
};
//修改前赋值
_person2=@"person22";
NSLog(@"excutebefore _person2:%@,%p",_person2,_person2);
NSLog(@"excutebefore _person3:%@,%p",_person3,_person3);
myBlock(1);
//block执行后
NSLog(@"excuteafter _person2:%@,%p",_person2,_person2);
NSLog(@"excuteafter _person3:%@,%p",_person3,_person3);
}
执行结果如下:
2014-07-29 12:06:11.526 Test[2575:60b] init _person2:person2,0x10790c
2014-07-29 12:06:11.529 Test[2575:60b] init _person3:person3,0x10791c
2014-07-29 12:06:11.530 Test[2575:60b] excutebefore _person2:person22,0x10797c
2014-07-29 12:06:11.531 Test[2575:60b] excutebefore _person3:person3,0x10791c
2014-07-29 12:06:11.532 Test[2575:60b] excuteing _person2:person22,0x10797c
2014-07-29 12:06:11.534 Test[2575:60b] excuteing _person3:person33,0x10794c
2014-07-29 12:06:11.535 Test[2575:60b] excuteafter _person2:person22,0x10797c
2014-07-29 12:06:11.536 Test[2575:60b] excuteafter _person3:person33,0x10794c
从日志可以看出,
- block内部修改了成员变量_person3(
没有用__block修饰符
),并且同步到block外部,修改前和修改后地址是一样的。 - block初始化后,执行前,修改成员变量_person2的值,可以同步到block内部(
没有用__block修饰符
),修改前和修改后地址是一样的。
我们来看一下clang转换后的代码就会知道原因了
struct __KDBlockTest__test3_block_impl_0 {
struct __block_impl impl;
struct __KDBlockTest__test3_block_desc_0* Desc;
KDBlockTest *self;
__KDBlockTest__test3_block_impl_0(void *fp, struct __KDBlockTest__test3_block_desc_0 *desc, KDBlockTest *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
对于局部变量,block结构体里对应一个变量,都会有一个成员。
对于成员变量,block结构体里只会有一个成员变量,即 KDBlockTest *self,不管你是否用__block修饰了,此时对self产生了强引用
void (*myBlock)(int) = (void (*)(int))&__KDBlockTest__test3_block_impl_0((void *)__KDBlockTest__test3_block_func_0, &__KDBlockTest__test3_block_desc_0_DATA, self, 570425344);
在初始化的时候,把self传到block结构体构造函数里,block对象对self产生了引用,此时我们对成员变量进行修改
_person2=@"person22";
_person3=@"person33";
转换后代码
(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person2))=(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_3beba7_mi_8;
这段代码大致是修改self的objc变量。下面开始执行block,即调用对应的函数指针。
((void (*)(__block_impl *, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 1);
static void __KDBlockTest__test3_block_func_0(struct __KDBlockTest__test3_block_impl_0 *__cself, int num) {
KDBlockTest *self = __cself->self; // bound by copy
(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person3))=(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_3beba7_mi_5;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_3beba7_mi_6,(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person2)),(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person2)));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_3beba7_mi_7,(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person3)),(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person3)));
}
函数实现里通过引用block结构体的成员self,再引用到对应的objc变量_person2和_person3。
小结:
- 对于一个、多个成员变量,不管是否用__block修饰(
用不用都没任何影响
),block结构体会生成一个成员 :self,并且会引用成员变量所属的对象实例 self。 - 对于成员变量的修改都是通过对象self指针引用来实现的。
- block内部对于成员变量的访问也是通过block结构体对象的成员self 指针引用来实现的。