前言: 对于 ios 初学者, block 通常用于逆向传值, 遍历等, 会使用, 但是可能心虚, 会感觉 block 很神秘, 那么下面就一起来揭开它的面纱吧.
ps: 下面重点讲叙了闭包的概念, 常用的语法, 以及访问变量, 循环引用问题, 至于底层的运行, 堆栈 block 的区别, 还有其他用法这里就不介绍了, 目前也处于迷糊中, 等到真正理解了再来补充 - -.
一. 概念
1. 什么是闭包?
闭包就是能够读取其他函数内部变量的函数, 可以理解成 "定义在一个函数 https://baike.baidu.com/item/函数/301912 内部的函数".
在本质上, 闭包是将函数内部和函数外部连接起来的桥梁.
闭包在很多语言中都有应用, C,JAVA,OC 等
2. OC 中的 Block
在 OC 中, Block 是在 iOS4 开始引入, 是对 C 语言的扩展, 被用来实现匿名函数的特性
Block 是一种特殊的数据类型, 可以正常定义变量, 作为参数, 作为返回值
特殊地, Block 还可以声明赋值去保存一段代码, 在需要调用的地方去调用
目前 Block 已经广泛应用于各类回调传值, 排序遍历, GCD, 动画等
二. 基本语法
1. block 做自由变量 - 声明, 赋值以及调用 (个人感觉理解语法就好)
- // 声明一个名字为 TestBlockTest 的无返回值含有参数的变量
- void (^TestBlockTest)(NSString *);
- // 只声明变量, 需要赋值
- TestBlockTest = ^(NSString *parameter){
- NSLog(@"测试");
- };
- // 调用
- TestBlockTest(@"");
- // 声明 TestTwoBlock 变量同时赋值
- int (^TestTwoBlock)(int) = ^(int num){
- return num*8;
- };
- // 已经声明了 blcok 并赋值了 , 可以直接调用
- int num = TestTwoBlock(8);
- NSLog(@"%d",num);
注意:
^ 这个叫做 脱字符, 其中, 返回值类型, 参数列表可以省略简写, 这个用多了就知道了, 开始推荐写全, 基础要扎实
第一个输出值 测试, 没有用到参数 parameter, 强迫症大神请见谅哈! 第二个输出值 64
Block 的声明与赋值只是保存了一段代码段, 必须 调用 才能执行内部代码
2. 使用 typedef 定义 Block 类型
可做属性, 可做参数, 返回值类型等 (这个用法务必掌握)
示例代码背景: A 页面点击按钮 跳转 B 页面, B 页面返回 A 页面时候, 传值 @"测试", 用于修改 A 页面按钮名字
2.1 .h 中定义
- #import <UIKit/UIKit.h>
- // typedef 定义无返回值, 有一个参数, 名字为 TestBlock 的 block 类型
- typedef void(^TestBlock)(NSString *);
- @interface ViewController : UIViewController
- // 做属性
- @property (nonatomic, copy) TestBlock testBolck;
- // 做方法参数
- - (void)returnText:(TestBlock)block;
- @end
2.2 .m 中用法, 实现 (下面整合了做属性, 做参数的代码)
- // B 页面
- // 做属性
- TestBlock blockVar = ^(NSString *parameterTwo){
- NSLog(@"hello world %@",parameterTwo);
- };
- /*
- * 我们可能需要重复地声明多个相同返回值相同参数列表的 Block 变量
- * 如果总是重复地编写一长串代码来声明变量会非常繁琐
- * 所以我们可以使用 typedef 来定义 Block 类型
- * 然后像 OC 中声明变量一样使用 Block 类型 TestBlock 来声明变量
- **/
- blockVar(@"UZI");
- blockVar(@"");
- // 实现定义的方法
- - (void)returnText:(TestBlock)block{
- self.testBolck = block;
- }
- // 这里选择返回传参数, 用法很多, 看个人喜好
- - (void)viewWillDisappear:(BOOL)animated{
- self.testBolck(@"测试");
- }
- // A 页面 在页面跳转部分
- // 做属性回调
- __weak __typeof(self) weakSelf = self;
- vc.testBolck = ^(NSString *parameter) {
- [weakSelf setBtnTitle:parameter];
- };
- // 做参数回调
- [vc returnText:^(NSString *parameter) {
- [self setBtnTitle:parameter];
- }];
- // 做参数另一种写法
- __weak __typeof(self) weakSelf = self;
- [vc returnText:^(NSString *parameter) {
- __strong __typeof(weakSelf) strongSelf = weakSelf;
- // 这里用 strong 保证 self 不被释放, 详见下文 循环引用 weak,strong 修饰问题
- [strongSelf setBtnTitle:parameter];
- }];
注意:
上面选择在 B 页面返回 A 页面时候传递参数, 传递给 A,A 页面分别用了 block 做属性, 做参数是怎么完成逆向传值的, 方式很多, 凭自己喜好
三. block 访问变量问题
这里不一一代码举例, 个人感觉看总结, 主要有以下四点, 记住就好
1. Block 拥有捕获外部变量的功能, 在 Block 中访问一个外部的局部变量, Block 会持用它的临时状态, 自动捕获变量值, 外部局部变量的变化不会影响它的的状态, 可以理解为瞬间性捕获.
2. 在 block 中, 可以访问局部变量 (自由变量), 但是不能修改局部变量, 因为: block 捕获的是自动变量的 const 值, 名字一样, 不能修改
3. 可以访问静态变量, 并修改变量的值, 静态变量属于类的, 不是某一个变量, 因此 block 不用调用 self 指针, 所以 block 可以修改值
4. 使用__block 修饰符的局部变量, 可以修改局部变量的值. 包括可变类型的参数, 也可以修改, 这个可以用 clang 命令将 OC 转为 C++ 代码来查看一下 Block 底层实现
四: 循环引用 __weak __strong 修饰问题
很多初学者对这块都是模糊的, 只知道加上 __weak __typeof(self) weakSelf = self 这句, 弱引用, 可以防止循环引用.
那什么是循环引用?
简单理解为 相互持有强引用, 造成 block 内所持有的对象无法释放, 引起内存泄漏
当然, 造成循环引用不唯一, 好比对象内部有一个 Block 属性, 而在 Block 内部又访问了该对象, 那么也会造成循环引用
下面分三点来谈论:
1. 如果相互持有强引用, 即对象内部有一个 Block 属性, 而在 Block 内部又访问了该对象, 那么会造成循环引用.
解决办法是使用一个弱引用的指针指向该对象, 然后在 Block 内部使用该弱引用指针来进行操作, 这样引用计数不加 1, 避免了 Block 对对象进行强引用.
通常是这样: __weak __typeof(self) weakSelf = self
注意: 以上是在 ARC 情况下, 如果 MRC 中, 可以在 会引起相互持有的对象 前面, 使用 __block 修饰, 原理是可以禁止 block 对对象进行 retain 操作, 引用计数不会加 1, 从而解决循环引用问题.
这种循环引用示例 (部分代码, 提供思路):
- // 定义一个 block
- typedef void(^HYBFeedbackBlock)(id model);
- // 声明一个对象
- @property (nonatomic, strong) HYBAView *aView;
- // block 做方法参数
- - (instancetype)initWithBlock:(HYBFeedbackBlock)block;
- // 构造方法
- - (instancetype)initWithBlock:(HYBFeedbackBlock)block {
- if (self = [super init]) {
- self.block = block;
- return self;
- }
- // 调用
- self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
- // 假设要更新 model
- self.currentModel = model;
- }];
上面代码很容易看出所形成的环:
- vc->aView->block->vc(self)
- vc->aView->block->vc.currentModel
2. 对于上面的那种情况, 为消除循环引用, 而用弱引用
虽说使用__weak, 但是此处会有一个隐患, 你不知道 block 内的 self 什么时候会被释放,
为了保证在 block 内不会被释放, 我们添加__strong, 更多的时候需要配合 strongSelf 使用
- // 上面讲到做方法参数时候的一种写法
- __weak __typeof(self) weakSelf = self;
- [vc returnText:^(NSString *parameter) {
- __strong __typeof(weakSelf) strongSelf = weakSelf;
- [strongSelf setBtnTitle:parameter];
- }];
可能有人问, 用 strong, 那什么时候才释放呢?
用修饰符 strong 时, 当外部把变量 / 对象释放掉, 但 block 如果没有执行结束, 那么系统就会等待 block 执行完成后再释放,
对该变量 / 对象在 block 中的使用起到了保护作用, 当 block 执行结束后会自动释放掉 (ARC).
不过若无强烈需求, 不建议在 Block 里加 strong, 容易占用内存, 造成内存消耗
3. 是不是所有的 block 都要用弱引用呢?
不是, 如果没有相互直接引用, 可以放心大胆的不用__weak
并不是 block 就一定会造成循环引用, 如果不是相互持有, 可以不用__weak 去弱引用
最经典的示例: Masonry 代码布局
- [self.headView mas_makeConstraints:^(MASConstraintMaker *make) {
- make.centerY.equalTo(self.otherView.mas_centerY);
- }];
* block 里用到了 self,block 会保持一个对 self 的引用, 但是 self 并没有直接或者间接持有 block, 所以不会造成循环引用
形成的持有链:
self ->self.headView ... MASConstraintMaker 构造 block->self
五. 感兴趣的可以继续了解下
* block 与内存管理, 堆栈 block 等
* block 底层实现
* block 其他用法
总结: 认清一件事物需要长期不断的观察与思考, 长路漫漫修远兮, 时刻保持学习心, 与君共勉
来源: https://www.cnblogs.com/zhouDongdong/p/9354236.html