1,变量截获
前几天朋友给我出了个block的题目
-(void)method
int multiplier = 6;
int(^Block)(int) = ^int(int num){
return num * multiplier;
};
multiplier = 4;
NSLog(@"result is %d",Block(2));
}
相信有一定经验的同学都知道打印结果是12
,为了深入了解其中的缘由,就开始了下面的一系列的操作了,利用命令clang -rewrite-objc -fobjc-arc MyBlock.m
生成一个MyBlock.cpp
,打开文件找到相应的代码
struct __MyBlock__method_block_impl_0 {
//保存block信息的结构体
struct __block_impl impl;
//关于block描述
struct __MyBlock__method_block_desc_0* Desc;
//参数
int multiplier;
__MyBlock__method_block_impl_0(void *fp, struct __MyBlock__method_block_desc_0 *desc, int _multiplier, int flags=0) : multiplier(_multiplier) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
从上面代码中可以看出来,block创建的时候,变量multiplier
被直接传入block的构造函数中,所以下面修改multiplier
的值的时候根本不会修改构造函数里面的参数了。这时候我不禁在想,如果传入的是其他类型的数据呢?于是乎,写了另一个方法
-(void)method
__unsafe_unretained id unsafe_obj = nil;
__strong id strong_obj = nil;
static int static_var = 3;
int my_var = 3;
self.str = @"hello";
NSString * s = @"123";
void(^Block)(void)=^{
NSLog(@"局部变量__unsafe_unretained id数据类型:%@",unsafe_obj);
NSLog(@"局部变量__strong id数据类型:%@",strong_obj);
NSLog(@"静态变量 static int数据类型:%d",static_var);
NSLog(@"基本数据类型:%d",my_var);
NSLog(@"全局变量字符串值:%@",self.str);
NSLog(@"局部变量字符串的值:%@",s);
};
strong_obj = @"123";
self.str = @"world";
s=@"hello";
Block();
}
打印结果为
2018-08-10 13:42:38.488653+0800 Block底层调用[1278:88416] 局部变量__unsafe_unretained id数据类型:(null)
2018-08-10 13:42:38.488771+0800 Block底层调用[1278:88416] 局部变量__strong id数据类型:(null)
2018-08-10 13:42:38.488846+0800 Block底层调用[1278:88416] 静态变量 static int数据类型:3
2018-08-10 13:42:38.488917+0800 Block底层调用[1278:88416] 基本数据类型:3
2018-08-10 13:42:38.489053+0800 Block底层调用[1278:88416] 全局变量字符串值:world
2018-08-10 13:42:38.489302+0800 Block底层调用[1278:88416] 局部变量字符串的值:123
利用命令clang -rewrite-objc -fobjc-arc MyBlock.m
生成一个MyBlock.cpp
,打开文件找到相应的代码
struct __MyBlock__method_block_impl_0 {
struct __block_impl impl;
struct __MyBlock__method_block_desc_0* Desc;
__unsafe_unretained id unsafe_obj;
__strong id strong_obj;
int *static_var;
int my_var;
MyBlock *const __strong self;
NSString *__strong s;
__MyBlock__method_block_impl_0(void *fp, struct __MyBlock__method_block_desc_0 *desc, __unsafe_unretained id _unsafe_obj, __strong id _strong_obj, int *_static_var, int _my_var, MyBlock *const __strong _self, NSString *__strong _s, int flags=0) : unsafe_obj(_unsafe_obj), strong_obj(_strong_obj), static_var(_static_var), my_var(_my_var), self(_self), s(_s) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __MyBlock__method_block_func_0(struct __MyBlock__method_block_impl_0 *__cself) {
__unsafe_unretained id unsafe_obj = __cself->unsafe_obj; // bound by copy
__strong id strong_obj = __cself->strong_obj; // bound by copy
int *static_var = __cself->static_var; // bound by copy
int my_var = __cself->my_var; // bound by copy
MyBlock *const __strong self = __cself->self; // bound by copy
NSString *__strong s = __cself->s; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kq_1y5vtnrx03d7m9x_0610rbdm0000gn_T_MyBlock_e16049_mi_2,unsafe_obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kq_1y5vtnrx03d7m9x_0610rbdm0000gn_T_MyBlock_e16049_mi_3,strong_obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kq_1y5vtnrx03d7m9x_0610rbdm0000gn_T_MyBlock_e16049_mi_4,(*static_var));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kq_1y5vtnrx03d7m9x_0610rbdm0000gn_T_MyBlock_e16049_mi_5,my_var);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kq_1y5vtnrx03d7m9x_0610rbdm0000gn_T_MyBlock_e16049_mi_6,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("str")));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kq_1y5vtnrx03d7m9x_0610rbdm0000gn_T_MyBlock_e16049_mi_7,s);
}
从上面代码发现,block构造函数对局部变量的所有权修饰符一起截获了,但是没有截获全局变量。因此我们也就知道,在block创建之后修改哪些值会影响到block初始化后的值了。
2,__block
-(void)method{
int multiplier = 6;
int(^Block)(int) = ^int(int num){
multiplier += num;
return multiplier;
};
NSLog(@"result is %d",Block(2));
}
关于这段代码,有一定开发经验的人都知道编译器会报这个错误
Variable is not assignable (missing __block type specifier)
是的,如果用编译器,所有人都能够一眼看出来需要将代码改成
-(void)method{
__block int multiplier = 6;
int(^Block)(int) = ^int(int num){
multiplier += num;
return multiplier;
};
NSLog(@"result is %d",Block(2));
}
那么添加了__block
,到底发生了什么变化呢?
由变量截获
可以知道静态全局变量
,全局变量
,静态全局变量
不需要使用__block
,而在block修改局部变量就需要用__block
;利用命令clang -rewrite-objc -fobjc-arc MyBlock.m
生成一个MyBlock.cpp
,找到相应代码块如下
struct __MyBlock__method_block_impl_0 {
struct __block_impl impl;
struct __MyBlock__method_block_desc_0* Desc;
__Block_byref_multiplier_0 *multiplier; // by ref
__MyBlock__method_block_impl_0(void *fp, struct __MyBlock__method_block_desc_0 *desc, __Block_byref_multiplier_0 *_multiplier, int flags=0) : multiplier(_multiplier->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __Block_byref_multiplier_0 {
void *__isa;
__Block_byref_multiplier_0 *__forwarding;
int __flags;
int __size;
int multiplier;
};
由上面代码可以看出,用__block
修饰过的局部变量multiplier
其实已经变成了一个结构体__Block_byref_multiplier_0
,由于该结构体有一个isa
指针,所以实际上multiplier
变成了一个对象;相当于代码变成了multiplier=4;
=> multiplier.__forwarding->multiplier;
。所以当block内部修改multiplier
的值,相当于通过multiplier
对象的__forwarding指针
修改其对应的值,由于此时__forwarding
指向的是变量自己,因此修改的值就是栈上面multiplier
的值
block内存管理
block包括以下三种类型
- _NSConcreteGlobalBlock //全局block 处于已初始化数据区
- _NSConcreteStackBlock //栈上block 处于栈上
- _NSConcreteMallocBlock //堆上block 处于堆上
block的copy操作产生的结果如下
block类别 | 源 | copy结果 |
---|---|---|
_NSConcreteMallocBlock | 堆 | 增加引用计数 |
_NSConcreteStackBlock | 栈 | 堆 |
_NSConcreteGlobalBlock | 数据区 | 什么也不做 |
当对栈上面的block进行copy操作时候,实际上是复制了一份block和__block变量放在堆里面,这也是为什么在MRC中,如果对栈上面的block进行copy之后不手动释放就会产生内存泄露。
看如下代码:
-(void)method{
__block int multiplier = 6;
self.Block = ^int(int num){
multiplier += num;
return multiplier;
};
multiplier = 4;
[self executeBlock];
}
-(void)executeBlock{
NSLog(@"%d",self.Block(4));
}
打印结果为:
2018-08-10 15:04:40.695608+0800 Block底层调用[2238:218584] 8
当copy完成之后,栈上面__block变量__forwarding指向堆上面的__block变量,而堆上面_block变量__forwarding指向自身。当进行copy完成之后,再出修改multiplier
,实际是修改了堆上面_block变量的值。
生成指定架构的c++源文件命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m -o xxx-arm64.cpp
生成ARC,指定运行时的源文件命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 xxx.m -o xxx.cpp
当然循环引用也是比较常见的,但是大部分同学都知道了,此处就不在赘述了。
参考资料Block技巧与底层解析