前言:对于ios初学者,block通常用于逆向传值,遍历等,会使用,但是可能心虚,会感觉block很神秘,那么下面就一起来揭开它的面纱吧。
ps: 下面重点讲叙了闭包的概念,常用的语法,以及访问变量,循环引用问题,至于底层的运行,堆栈block的区别,还有其他用法这里就不介绍了,目前也处于迷糊中,等到真正理解了再来补充 - -。
一. 概念
1. 什么是闭包?
闭包就是能够读取其他函数内部变量的函数,可以理解成“定义在一个函数内部的函数“。
在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
闭包在很多语言中都有应用,C,JAVA,OC等
2. OC中的Block
在OC中,Block是在iOS4开始引入,是对C语言的扩展,被用来实现匿名函数的特性
Block是一种特殊的数据类型,可以正常定义变量、作为参数、作为返回值
特殊地,Block还可以声明赋值去保存一段代码,在需要调用的地方去调用
目前Block已经广泛应用于各类回调传值、排序遍历、GCD、动画等
二. 基本语法
1. block做自由变量 - 声明、赋值以及调用 (个人感觉理解语法就好)
1 // 声明一个名字为 TestBlockTest 的无返回值含有参数的变量 2 void (^TestBlockTest)(NSString *); 3 // 只声明变量,需要赋值 4 TestBlockTest = ^(NSString *parameter){ 5 NSLog(@"测试"); 6 }; 7 // 调用 8 TestBlockTest(@""); 9 10 // 声明TestTwoBlock变量同时赋值11 int (^TestTwoBlock)(int) = ^(int num){12 return num*8;13 };14 // 已经声明了blcok并赋值了 ,可以直接调用15 int num = TestTwoBlock(8);16 NSLog(@"%d",num);
注意:
^ 这个叫做 脱字符,其中,返回值类型,参数列表可以省略简写,这个用多了就知道了,开始推荐写全,基础要扎实
第一个输出值 测试,没有用到参数parameter,强迫症大神请见谅哈!第二个输出值 64
Block的声明与赋值只是保存了一段代码段,必须 调用 才能执行内部代码
2. 使用typedef定义Block类型
可做属性,可做参数、返回值类型等 (这个用法务必掌握)
示例代码背景:A页面点击按钮 跳转 B页面,B页面返回A页面时候,传值@“测试”,用于修改A页面按钮名字
2.1 .h中定义
1 #import <UIKit/UIKit.h> 2 3 // typedef 定义无返回值,有一个参数,名字为TestBlock的block类型 4 typedef void(^TestBlock)(NSString *); 5 6 @interface ViewController : UIViewController 7 8 // 做属性 9 @property (nonatomic, copy) TestBlock testBolck;10 11 // 做方法参数12 - (void)returnText:(TestBlock)block;13 14 @end
2.2 .m中用法,实现(下面整合了做属性,做参数的代码)
1 // B页面 2 3 // 做属性 4 TestBlock blockVar = ^(NSString *parameterTwo){ 5 NSLog(@"hello world %@",parameterTwo); 6 }; 7 /* 8 * 我们可能需要重复地声明多个相同返回值相同参数列表的Block变量 9 * 如果总是重复地编写一长串代码来声明变量会非常繁琐10 * 所以我们可以使用typedef来定义Block类型11 * 然后像OC中声明变量一样使用Block类型 TestBlock 来声明变量12 **/13 blockVar(@"UZI");14 blockVar(@""); 15 // 实现定义的方法16 - (void)returnText:(TestBlock)block{17 self.testBolck = block;18 } 19 // 这里选择返回传参数,用法很多,看个人喜好20 - (void)viewWillDisappear:(BOOL)animated{21 self.testBolck(@"测试");22 } 23 // A页面 在页面跳转部分24 // 做属性回调25 __weak __typeof(self) weakSelf = self;26 vc.testBolck = ^(NSString *parameter) {27 [weakSelf setBtnTitle:parameter];28 };29 30 // 做参数回调31 [vc returnText:^(NSString *parameter) {32 [self setBtnTitle:parameter];33 }]; 34 // 做参数另一种写法35 __weak __typeof(self) weakSelf = self;36 [vc returnText:^(NSString *parameter) {37 __strong __typeof(weakSelf) strongSelf = weakSelf;38 // 这里用strong保证self不被释放,详见下文 循环引用weak,strong修饰问题39 [strongSelf setBtnTitle:parameter];40 }];
注意:
上面选择在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,从而解决循环引用问题。
这种循环引用示例(部分代码,提供思路):
1 // 定义一个block 2 typedef void(^HYBFeedbackBlock)(id model); 3 // 声明一个对象 4 @property (nonatomic, strong) HYBAView *aView; 5 // block做方法参数 6 - (instancetype)initWithBlock:(HYBFeedbackBlock)block; 7 // 构造方法 8 - (instancetype)initWithBlock:(HYBFeedbackBlock)block { 9 if (self = [super init]) {10 self.block = block;11 return self;12 }13 // 调用14 self.aView = [[HYBAView alloc] initWithBlock:^(id model) {15 // 假设要更新model16 self.currentModel = model;17 }];
上面代码很容易看出所形成的环:
vc->aView->block->vc(self)
vc->aView->block->vc.currentModel
2. 对于上面的那种情况,为消除循环引用,而用弱引用
虽说使用__weak,但是此处会有一个隐患,你不知道 block内的 self 什么时候会被释放,
为了保证在block内不会被释放,我们添加__strong,更多的时候需要配合strongSelf使用
1 // 上面讲到做方法参数时候的一种写法2 __weak __typeof(self) weakSelf = self;3 [vc returnText:^(NSString *parameter) {4 __strong __typeof(weakSelf) strongSelf = weakSelf;5 [strongSelf setBtnTitle:parameter];6 }];
可能有人问,用strong,那什么时候才释放呢?
用修饰符strong时,当外部把变量/对象释放掉,但block如果没有执行结束,那么系统就会等待block执行完成后再释放,
对该变量/对象在block中的使用起到了保护作用,当block执行结束后会自动释放掉(ARC)。
不过若无强烈需求,不建议在Block里加strong,容易占用内存,造成内存消耗
3. 是不是所有的block都要用弱引用呢?
不是,如果没有相互直接引用,可以放心大胆的不用__weak
并不是block就一定会造成循环引用,如果不是相互持有,可以不用__weak 去弱引用
最经典的示例: Masonry代码布局
1 [self.headView mas_makeConstraints:^(MASConstraintMaker *make) {2 3 make.centerY.equalTo(self.otherView.mas_centerY);4 }];
* block里用到了self,block会保持一个对self的引用,但是self并没有直接或者间接持有block,所以不会造成循环引用
形成的持有链:
self ->self.headView ··· MASConstraintMaker构造block->self
五. 感兴趣的可以继续了解下
* block 与内存管理,堆栈block等
* block 底层实现
* block 其他用法
总结: 认清一件事物需要长期不断的观察与思考,长路漫漫修远兮,时刻保持学习心,与君共勉
参考文献:
block 中使用__weak 和__strong修饰符的问题
iOS中Block的用法,举例,解析与底层原理(这可能是最详细的Block解析)
深入解构iOS的block闭包实现原理 (想看block实现原理推荐看此文献)
原文出处:http://www.cnblogs.com/zhouDongdong/p/9354236.html
共同学习,写下你的评论
评论加载中...
作者其他优质文章