IOS 基础知识--闭包

1. 闭包简介

  • 什么是闭包

闭包就是能够读取其他函数内部变量的函数,可以理解成“定义在一个函数内部的函数“。
在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
闭包在很多语言中都有应用,C,JAVA,OC等

  • OC中的Block

在OC中,Block是在iOS4开始引入,是对C语言的扩展,被用来实现匿名函数的特性
Block是一种特殊的数据类型,可以正常定义变量、作为参数、作为返回值
特殊地,Block还可以声明赋值去保存一段代码,在需要调用的地方去调用
目前Block已经广泛应用于各类回调传值、排序遍历、GCD、动画等

2. 闭包语法

  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);
//输出值 64

^ 这个叫做 脱字符,其中,返回值类型,参数列表可以省略简写,
Block的声明与赋值只是保存了一段代码段,必须 调用 才能执行内部代码

  1. 使用typedef定义Block类型

下面通过一个实例代码讲解:
实例1:

1. 头文件

#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. 实现文件
// 做属性
    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];
     }];

  1. block访问变量问题
  • Block拥有捕获外部变量的功能,在Block中访问一个外部的局部变量,Block会持用它的临时状态,自动捕获变量值,外部局部变量的变化不会影响它的的状态,可以理解为瞬间性捕获。

  • 在block中,可以访问局部变量(自由变量),但是不能修改局部变量,因为:block捕获的是自动变量的const值,名字一样,不能修改

  • 可以访问静态变量,并修改变量的值,静态变量属于类的,不是某一个变量,因此block不用调用self指针,所以block可以修改值

  • 使用__block修饰符的局部变量,可以修改局部变量的值。包括可变类型的参数,也可以修改,这个可以用clang命令将OC转为C++代码来查看一下Block底层实现

3. 闭包用法

3.1 Block声明及定义语法,及其变形

  1. 标准声明与定义
return_type (^blockName)(var_type) = ^return_type (var_type varName) { // ... };
blockName(var);
  1. 当返回类型为void
void (^blockName)(var_type) = ^void (var_type varName) { // ... };
blockName(var);

可省略写成

void (^blockName)(var_type) = ^(var_type varName) { // ... };
blockName(var);
  1. 当参数类型为void
return_type (^blockName)(void) = ^return_type (void) { // ... };
blockName();
可省略写成
return_type (^blockName)(void) = ^return_type { // ... };
blockName();
  1. 当返回类型和参数类型都为void
void (^blockName)(void) = ^void (void) { // ... };
blockName();
可省略写成
void (^blockName)(void) = ^{ // ... };
blockName();
  1. 匿名Block

Block实现时,等号右边就是一个匿名Block,它没有blockName,称之为匿名Block:

^return_type (var_type varName)
{ //... };

3.2 typedef简化Block的声明

  1. 利用typedef简化Block的声明:
typedef return_type (^BlockTypeName)(var_type);
  1. 作属性
//声明 typedef void(^ClickBlock)(NSInteger index); 
//block属性 
@property (nonatomic, copy) ClickBlock imageClickBlock;
  1. 作方法参数
//声明 typedef void (^handleBlock)(); 
//block作参数 
- (void)requestForRefuseOrAccept:(MessageBtnType)msgBtnType messageModel:(MessageModel *)msgModel handle:(handleBlock)handle{
  ...

3.3 Block的常见用法

  1. 局部位置声明一个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);
  1. @interface位置声明一个Block型的属性
@property(nonatomic, copy)return_type (^blockName) (var_type);

//按钮点击Block
 @property (nonatomic, copy) void (^btnClickedBlock)(UIButton *sender);
  1. 在定义方法时,声明Block型的形参
- (void)yourMethod:(return_type (^)(var_type))blockName;
- (void)addClickedBlock:(void(^)(id obj))clickedAction;
  1. 在调用如上方法时,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其他用法

  1. Block的内联用法
^return_type (var_type varName)
{ //... }(var);
  1. 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);
  1. 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. 从生命周期看两者区别:
  • 非逃逸闭包的生命周期与函数相同:

1,把闭包作为参数传给函数;
2,函数中调用闭包;
3,退出函数。结束

  • 逃逸闭包的生命周期:

1,闭包作为参数传递给函数;
2,退出函数;
3,闭包被调用,闭包生命周期结束

即逃逸闭包的生命周期长于函数,函数退出的时候,逃逸闭包的引用仍被其他对象持有,不会在函数结束时释放

  1. 经常使用逃逸闭包的2个场景:
  • 异步调用: 如果需要调度队列中异步调用闭包,比如网络请求成功的回调和失败的回调,这个队列会持有闭包的引用,至于什么时候调用闭包,或闭包什么时候运行结束都是不确定,上边的例子。
  • 存储: 需要存储闭包作为属性,全局变量或其他类型。
  1. 通过一个实例代码来讲解逃逸闭包和非逃逸闭包的区别

实例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 常见的几种循环引用

  1. 如果相互持有强引用,即对象内部有一个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
  1. 对于上面的那种情况,为消除循环引用,而用弱引用

    虽说使用__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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值