IOS 浅谈闭包block的使用

前言:对于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      // 假设要更新model
16      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 其他用法

 

总结: 认清一件事物需要长期不断的观察与思考,长路漫漫修远兮,时刻保持学习心,与君共勉

 

参考文献:

一篇文章看懂iOS代码块Block

block 中使用__weak 和__strong修饰符的问题

iOS中Block的用法,举例,解析与底层原理(这可能是最详细的Block解析)

iOS闭包循环引用精讲

深入解构iOS的block闭包实现原理 (想看block实现原理推荐看此文献)

浅谈iOS中的闭包

 

posted on 2018-07-23 16:46  冬季的暖风  阅读(1803)  评论(3编辑  收藏  举报

导航