Block 与局部变量
int global = 100; void(^myBlock)(void) = ^{ NSLog(@"global = %d",global); }; global = 101; myBlock();
block 可以捕获局部变量,在你声明 myBlock 后,因为需要在block 内使用了 global 变量,所以 block 为你捕获了这个变量。
局部变量的使用
1.如果使用 global 变量
在使用 clang -rewrite-objc main.m 后查看.cpp 文件可以看到
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int global; //block 捕获了这个变量 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _global, int flags=0) : global(_global) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
2.如果不使用 global 变量
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };//没有捕获 global
局部变量的修改
值类型的变量
直接修改报错,提示缺少了__block 的修饰符,那么加了__block 修饰符后有什么不一样呢。同样的我们还是查看.cpp 文件来比较
使用__block | 不使用 |
---|---|
__Block_byref_global_0 *global; // by ref | int global; |
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_global_0 *_global, int flags=0) : global(_global->__forwarding) | __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _global, int flags=0) : global(_global) |
可以看到 global 由一个值类型变成了一个指针类型的变量,所以使用了__block 后可以修改 global 的值。
局部变量的修改结果
1.使用__block 修饰
__block int global = 100; void(^myBlock)(void) = ^{ NSLog(@"global = %d",global); }; global = 101; myBlock(); //运行结果 global = 101
2.不使用__block 修饰
int global = 100; void(^myBlock)(void) = ^{ NSLog(@"global = %d",global); }; global = 101; myBlock(); //运行结果 global = 100
修改结果总结
如果使用__block 修饰,并且在调用 block 之前修改 global ,那么block 内部的 global 也会跟着变。
如果不使用__block的话,并且在调用 block 之前修改 global,那么 block 内部的 global 不会发生改变。
原因
使用__block 修饰
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_global_0 *global = __cself->global; // bound by ref NSLog((NSString *)&__NSConstantStringImpl__var_folders_bq_bv7y3wj94sg1_5ldvtywqy7c0000gn_T_main_b116b4_mi_0,(global->__forwarding->global)); }
2.不使用__block修饰
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int global = __cself->global; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_bq_bv7y3wj94sg1_5ldvtywqy7c0000gn_T_main_62e250_mi_0,global); }
使用__block 修饰:
__Block_byref_global_0 *global = __cself->global; // bound by ref
不使用__block 修饰:
int global = __cself->global; // bound by copy
划重点,因为一个是bound by ref
一个是bound by copy
。一个是传递了指针,一个是对当前捕获的值的拷贝
Block 与 全局变量
在外层定义一个全部变量,执行下面的代码
void(^myBlock)(void) = ^{ NSLog(@"global = %d",global); }; global = 101; myBlock(); //运行结果 global = 101
为什么不使用__block 也可以修改,我们打开.cpp 进行查看
int global = 100;//先定义了一个全局的变量struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
在block 结构体的上方,已经定义了一个全局的变量,所以直接使用就可以了。不需要像局部变量一样,而且它并没有被 block 所捕获。
Block 与 静态变量
定义一个静态变量,执行下面的代码
static int global = 100; void(^myBlock)(void) = ^{ NSLog(@"global = %d",global); }; global = 101; myBlock(); //运行结果 global = 101
为什么不使用__block 也可以修改,我们打开.cpp 进行查看
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; int *global; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_global, int flags=0) : global(_global) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
在 global 进行赋值的时候是这样的
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int *global = __cself->global; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_bq_bv7y3wj94sg1_5ldvtywqy7c0000gn_T_main_d41b2e_mi_0,(*global)); }
它和局部变量不使用__block 修饰看起来很像,所以做了下对比
静态全局变量:
int *global = __cself->global; // bound by copy
不使用__block 修饰的局部变量:
int global = __cself->global; // bound by copy
划重点,虽然都是 copy,但是静态全局变量 copy 了指针,而不使用__block修改的局部变量进行了值的 copy。
ARC 下的 Block
1.EXC_BAD_ACCESS (code=1, address=0x10)
我有一个 User类
user.h
typedef void(^nameBlock)(void);@interface User : NSObject@property (nonatomic,strong)NSString *name;@property (nonatomic,copy)nameBlock block; -(instancetype)initWithName:(NSString *)name;@end
user.m
import "User.h"
@implementation User-(instancetype)initWithName:(NSString *)name{ self = [super init]; if (self) { _name = name; } return self; }@end
如果不赋值直接调用user.block();那么直接就会报错EXC_BAD_ACCESS (code=1, address=0x10),在使用时要注意
不对 block 赋值,直接调用
所以在调用 block 之前需要先赋值然后调用,这样就能解决这个问题
2.循环引用
循环引用
user有一个属性是一个 block,并且在内部又调用了 user,造成了循环引用。在编写代码的时候,编译器就给出了上面的黄色警告。
怎么知道发生了循环引用
xcode 9 的话,我们可以打断点看,我们在 autoreleasepool 结束的时候打断点
debug memory
点击 Debug Memory 那个选项
内存的图
本来这个 pool 结束的时候,user 应该被释放掉,但是因为在 block 内部强引用了 user,造成了循环引用。
如果我们去掉 block 内部的那些代码
没有形成循环引用
如果没有了 block 内部对 user 的强引用,那么便不会发生这种情况
如果我们一定要在 block 内部调用 user
修改我们的代码
User *user = [[User alloc]initWithName:@"Likee"]; __weak typeof(user)weakUser = user; user.block = ^{ weakUser.name = @"cat"; NSLog(@"name is %@",weakUser.name); }; NSLog(@"name is %@",user.name); user.block();
在 block的内部使用弱引用,这样的话,就能打破这个环。内存图就像下面这个样子
解决循环引用
作者:LikeeCat
链接:https://www.jianshu.com/p/a8f7697228a5
共同学习,写下你的评论
评论加载中...
作者其他优质文章