@[TOC](Swift (一)--闭包)
1. 闭包简介
- 什么是闭包
闭包就是能够读取其他函数内部变量的函数,可以理解成“定义在一个函数内部的函数“。
在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
闭包在很多语言中都有应用,C,JAVA,OC等
- OC中的Block
在OC中,Block是在iOS4开始引入,是对C语言的扩展,被用来实现匿名函数的特性
Block是一种特殊的数据类型,可以正常定义变量、作为参数、作为返回值
特殊地,Block还可以声明赋值去保存一段代码,在需要调用的地方去调用
目前Block已经广泛应用于各类回调传值、排序遍历、GCD、动画等
2. 闭包语法
- 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);
//输出值 64
^ 这个叫做 脱字符,其中,返回值类型,参数列表可以省略简写,
Block的声明与赋值只是保存了一段代码段,必须 调用 才能执行内部代码
- 使用typedef定义Block类型
下面通过一个实例代码讲解:
实例1:
1. 头文件
#import
// typedef 定义无返回值,有一个参数,名字为TestBlock的block类型
typedef void(^TestBlock)(NSString *);
@interface ViewController : UIViewController
// 做属性
@property (nonatomic, copy) TestBlock testBolck;
// 做方法参数
- (void)returnText:(TestBlock)block;
@end
2. 实现文件
// 做属性
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];
}];
- block访问变量问题
Block拥有捕获外部变量的功能,在Block中访问一个外部的局部变量,Block会持用它的临时状态,自动捕获变量值,外部局部变量的变化不会影响它的的状态,可以理解为瞬间性捕获。
在block中,可以访问局部变量(自由变量),但是不能修改局部变量,因为:block捕获的是自动变量的const值,名字一样,不能修改
可以访问静态变量,并修改变量的值,静态变量属于类的,不是某一个变量,因此block不用调用self指针,所以block可以修改值
使用__block修饰符的局部变量,可以修改局部变量的值。包括可变类型的参数,也可以修改,这个可以用clang命令将OC转为C++代码来查看一下Block底层实现
3. 闭包用法
3.1 Block声明及定义语法,及其变形
- 标准声明与定义
return_type (^blockName)(var_type) = ^return_type (var_type varName) { // ... };
blockName(var);
- 当返回类型为void
void (^blockName)(var_type) = ^void (var_type varName) { // ... };
blockName(var);
可省略写成
void (^blockName)(var_type) = ^(var_type varName) { // ... };
blockName(var);
- 当参数类型为void
return_type (^blockName)(void) = ^return_type (void) { // ... };
blockName();
可省略写成
return_type (^blockName)(void) = ^return_type { // ... };
blockName();
- 当返回类型和参数类型都为void
void (^blockName)(void) = ^void (void) { // ... };
blockName();
可省略写成
void (^blockName)(void) = ^{ // ... };
blockName();
- 匿名Block
Block实现时,等号右边就是一个匿名Block,它没有blockName,称之为匿名Block:
^return_type (var_type varName)
{ //... };
3.2 typedef简化Block的声明
- 利用typedef简化Block的声明:
typedef return_type (^BlockTypeName)(var_type);
- 作属性
//声明 typedef void(^ClickBlock)(NSInteger index);
//block属性
@property (nonatomic, copy) ClickBlock imageClickBlock;
- 作方法参数
//声明 typedef void (^handleBlock)();
//block作参数
- (void)requestForRefuseOrAccept:(MessageBtnType)msgBtnType messageModel:(MessageModel *)msgModel handle:(handleBlock)handle{
...
3.3 Block的常见用法
- 局部位置声明一个Block型的变量
return_type (^blockName)(var_type) = ^return_type (var_type varName) { // ... };
blockName(var);
void (^globalBlockInMemory)(int number) = ^(int number){ printf("%d \n",number);
};
globalBlockInMemory(90);
- @interface位置声明一个Block型的属性
@property(nonatomic, copy)return_type (^blockName) (var_type);
//按钮点击Block
@property (nonatomic, copy) void (^btnClickedBlock)(UIButton *sender);
- 在定义方法时,声明Block型的形参
- (void)yourMethod:(return_type (^)(var_type))blockName;
- (void)addClickedBlock:(void(^)(id obj))clickedAction;
- 在调用如上方法时,Block作实参
- (void)addClickedBlock:(void(^)(id obj))clickedAction{ self.clickedAction = clickedAction;
// :先判断当前是否有交互事件,如果没有的话。。。所有gesture的交互事件都会被添加进gestureRecognizers中
if (![self gestureRecognizers]) { self.userInteractionEnabled = YES;
// :添加单击事件
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap)];
[self addGestureRecognizer:tap];
}
}
- (void)tap{ if (self.clickedAction) { self.clickedAction(self);
}
}
3.4 Block其他用法
- Block的内联用法
^return_type (var_type varName)
{ //... }(var);
- Block的递归调用
//Block内部调用自身,递归调用是很多算法基础,特别是在无法提前预知循环终止条件的情况下。注意:由于Block内部引用了自身,这里必须使用__block避免循环引用问题。
__block return_type (^blockName)(var_type) = [^return_type (var_type varName)
{ if (returnCondition)
{
blockName = nil; return;
} // ... // 【递归调用】 blockName(varName);
} copy];
blockName(varValue);
- Block作为返回值
方法的返回值是一个Block,可用于一些“工厂模式”的方法中:
- (return_type(^)(var_type))methodName
{ return ^return_type(var_type param) { // ... };
}
3.5 Block应用
3.6 Block类型
3.6.1 逃逸闭包和非逃逸闭包
闭包只有在函数中做参数时才会区分逃逸闭包和非逃逸闭包。
Swift 3.0之后,传递闭包到函数中的时候,系统会默认为非逃逸闭包类型(NonescapingClosures)@noescaping,逃逸闭包在闭包前要添加@escaping关键字。
- 从生命周期看两者区别:
- 非逃逸闭包的生命周期与函数相同:
1,把闭包作为参数传给函数;
2,函数中调用闭包;
3,退出函数。结束
- 逃逸闭包的生命周期:
1,闭包作为参数传递给函数;
2,退出函数;
3,闭包被调用,闭包生命周期结束
即逃逸闭包的生命周期长于函数,函数退出的时候,逃逸闭包的引用仍被其他对象持有,不会在函数结束时释放
- 经常使用逃逸闭包的2个场景:
- 异步调用: 如果需要调度队列中异步调用闭包,比如网络请求成功的回调和失败的回调,这个队列会持有闭包的引用,至于什么时候调用闭包,或闭包什么时候运行结束都是不确定,上边的例子。
- 存储: 需要存储闭包作为属性,全局变量或其他类型。
- 通过一个实例代码来讲解逃逸闭包和非逃逸闭包的区别
实例30:
class HttpTool: NSObject {
func loadData(callBack:(String)->()) { ///(1)
callBack("非逃逸闭包") ///(2)
} ///(3)
}
代码执行顺序(1),(2),(3)
实例31:
class HttpTool: NSObject {
func loadData(callBack:@escaping((String)->())) { ///(1)
DispatchQueue.global().async {
DispatchQueue.main.async {
callBack("非逃逸闭包") ///(2)
}
}
} ///(3)
}
代码执行顺序:(1),(3),(2)
逃逸闭包前面添加@escaping关键字,这里闭包的生命周期不可预知。
4. 闭包循环引用问题
4.1 什么是循环引用
简单理解为 相互持有强引用,造成block内所持有的对象无法释放,引起内存泄漏
当然,造成循环引用不唯一,好比对象内部有一个Block属性,而在Block内部又访问了该对象,那么也会造成循环引用
4.2 常见的几种循环引用
- 如果相互持有强引用,即对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用。
解决办法是: 使用一个弱引用的指针指向该对象,然后在Block内部使用该弱引用指针来进行操作,这样引用计数不加1,避免了Block对对象进行强引用。
通常是这样: __weak __typeof(self) weakSelf = self
注意: 以上是在ARC情况下,如果MRC中,可以在 会引起相互持有的对象 前面,使用 __block 修饰,原理是可以禁止block对对象进行retain操作,引用计数不会加1,从而解决循环引用问题。
实例代码2:
// 定义一个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
-
对于上面的那种情况,为消除循环引用,而用弱引用
虽说使用__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,容易占用内存,造成内存消耗
- 是不是所有的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