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 之前需要先赋值然后调用,这样就能解决这个问题
2.循环引用
user有一个属性是一个 block,并且在内部又调用了 user,造成了循环引用。在编写代码的时候,编译器就给出了上面的黄色警告。
怎么知道发生了循环引用
xcode 9 的话,我们可以打断点看,我们在 autoreleasepool 结束的时候打断点
点击 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的内部使用弱引用,这样的话,就能打破这个环。内存图就像下面这个样子
参考内容
一篇文章看懂iOS代码块Block