关于OC中的block介绍
摘要:OC中的block和swift中的闭包在iOS开发中很常见,使用场景也非常多,例如块动画.GCD的回调.迭代器以及逆向传值等.下面将从相关原理. 逆向传值. 内存管理和循环引用几个方面详细介绍OC中的block.
一.Block的内存管理
1.不引用任何外部变量的block,保存在全局区(__NSGlobalBlock__),代码的执行效率高,但这种情况在实际开发中几乎是不存在的.eg:
- (void)blockDemo1 {
void(^myBlock)() = ^ {
NSLog(@"hello world");
};
NSLog(@"%@", myBlock);
}
2.引用外部变量的block.在MRC环境下,block默认存储在栈区,出了作用域就会弹栈.要想作为能够被全局访问的属性,就要用copy修饰,因为在对全局属性的block实例化的时候,走setter方法,用copy修饰系统会将block从栈区拷贝到堆区,实现全局共享.目前开发基本都在ARC环境下,block本来就在堆区,做为全局属性既可以用strong修饰,但apple建议使用copy修饰.
3.在MRC和ARC下,block做为属性和成员变量的区别:
MRC下,block默认存储在栈区.做为属性赋值时,系统会将其拷贝到堆区,引用计数器也会+1.而对成员变量赋值时,仅仅是计数器+1,不会进行copy操作.
ARC下,block无论作为属性还是成员变量都是强引用.赋值时都会copy.
二.Block和控制器的循环引用
1.控制器没有引用block,而block引用了控制器,示例代码:
- (void)viewDidLoad {
[super viewDidLoad];
// 定义block
void (^block)() = ^ {
NSLog(@"%@",self.view);
};
// 调用block
block();
}
在控制器的dealloc方法中打印输出,验证控制器的销毁:
- (void)dealloc {
NSLog(@"%s", __FUNCTION__);
}
运行结果:控制器的dealloc方法被触发,说明没有循环引用.因为block执行完毕后会释放,从而释放对self的引用.
2.将block定义为控制器的属性,block内部又引用了self(ViewController),示例代码:
*定义属性
@property (nonatomic,copy) void (^demoBlock)();
*在 viewDidLoad 中记录 block
- (void)viewDidLoad {
[super viewDidLoad];
void (^block)() = ^ {
NSLog(@"%@",self.view);
};
// 记录 block
self.demoBlock= block;
}
同样,在控制器的dealloc方法中打印输出,验证控制器的销毁情况.
运行结果:dealloc方法没有被触发,说明发生了循环引用.因为控制器引用了block,block内部又引用了self.相互强引用,造成了循环引用.
解决方案:使用__weak修饰符,定义一个弱引用的对象,示例代码:
- (void)viewDidLoad {
[super viewDidLoad];
// 把self临时定义成弱引用
__weak typeof(self) weakSelf = self;
void (^block)() = ^ {
NSLog(@"%@",weakSelf.view);
};
// 记录 block
self.demoBlock= block;
}
特别要注意的时,在使用block时,如果block内部引用了self,同时使用了属性记录了block,要格外注意是否出现循环引用.
另外,不是所有的self,都会出现循环引用,像UIView的动画代码块在执行完就销毁.
再就是ARC下,成员变量也是被控制器对象强引用,如果在block内部引用了成员变量,也相当于间接强引用了控制器,那么要注意这个时候,控制器有没有也对block形成强引用.示例代码:
@implementation ViewController {
NSMutableArray* _arrayM;
}
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
void(^block)() = ^ {
// 循环引用点在 `_arrayM`
NSLog(@"%@ %@", weakSelf.view, _arrayM);
};
// 记录 block
self.demoBlock= block;
}
三.block的回调
block的回调功能是GCD诞生的基础.显而易见,它在反向传值中的使用非常广泛.在iOS开发中,block和代理都能实现反向传值的功能,那么它们有什么区别呢?
block用法分为四步:
被调用方:
定义block属性 -> 调用block
调用方:
初始化block -> 在需要的时候执行block(前提是block已经初始化,否则运行时会crash).
代理的实现步骤有七步:
被调用方:
定义协议 -> 声明代理方法 ->定义代理属性 ->需要的时候判断代理对象是否能响应代理方法,能的情况下进行调用
调用方:
遵守协议 -> 成为代理对象 ->实现代理方法
另外,block的所有的代码写在一起,可读性更好,使用代理的话,代码相对分散.但是block的一个回调对应一个属性,属性定义在头文件中,容易和其他属性混在一起.而代理设计模式通过协议预先定好代理方法,更加严谨.尤其是在协议方法很多的时候,使用协议更加直观清晰.
四.关于block使用的补充
1.当block引用外部的变量时,会对外部变量进行一次拷贝,对外部变量的真实值不会造成影响;
2.如果要在block内部修改外部的局部变量,需要使用__block修饰这个局部变量.在block内部使用了这个变量后,这个变量的地址在后续的使用中都在堆区.
可以这样理解,block内部不能修改外部局部变量的原因是,block一般作为回调使用,经常性的需要传递到别的类中,局部变量出了block的作用域就会被销毁,为了让局部的变量不销毁,就用__block来进行标记.