iOS-重回block小白之路

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS-重回block小白之路相关的知识,希望对你有一定的参考价值。

在我刚刚接触ios开发的时候,是通过MJ老师讲的OC基础入门的,iOS圈的人应该基本都知道MJ大神吧,即便如此大神,讲解完block之后我依然感觉晕晕乎乎的,直到后来真正进公司做项目,依然感觉这是自己的一个弱项,后来通过不断接触,对它可能有了更多的了解,但是不一定够全面够深入,现在准备通过自己看过的几篇觉得还不错的文章,系统的来总结一下block的使用。不多废话,下面开始:

 

1、我在平时读他人文章的时候对block常见的描述是匿名函数,再多一些描述就是可以在方法内部使用,也可以在方法外部使用,还能做参数使用。下面看一下它的简单定义方式和使用

  1 int x = 8;
  2 - (void)blockTest10 {
  3 
  4     int (^myBlock)(int) = ^(int b){
  5         x = 5;
  6         return x + b;
  7     };
  8     int result = myBlock(3);
  9     NSLog(@"%d", result); //8
 10 }
 11 
 12 - (void)blockTest9 {
 13     static int a = 8;
 14     int (^myBlock)(int) = ^(int b){
 15         a = 5;
 16         return a + b;
 17     };
 18     int result = myBlock(3);
 19     NSLog(@"%d", result); //4
 20 }
 21 
 22 
 23 - (void)blockTest8 {
 24     static int a = 8;
 25     int (^myBlock)(int) = ^(int b){
 26         return a + b;
 27     };
 28     a = 5;
 29     int result = myBlock(3);
 30     NSLog(@"%d", result); //8
 31 }
 32 - (void)blockTest7 {
 33     __block int a = 5; //加上__block前缀,就会传址进去
 34     int (^myBlock)(int) = ^(int b){
 35 //        a = 2; //编译不再报错
 36         return a + b;
 37     };
 38     a = 7;
 39     int result = myBlock(3);
 40     //上面 a = 2 注释的情况下打印出的是10,解注释的情况下打印出的是5
 41     NSLog(@"%d", result);
 42     
 43 }
 44 
 45 - (void)blockTest6 {
 46     NSMutableArray *mutableArray = [NSMutableArray arrayWithObjects:@"one", @"two", @"three", nil];
 47     int result = ^(int a){
 48         [mutableArray removeLastObject]; //这里是传址而不是传值,因此这行代码会移除成功
 49         return a * a;
 50     }(5);
 51     
 52     NSLog(@"array :%@", mutableArray);
 53     NSLog(@"%d",result); //25
 54 }
 55 
 56 - (void)blockTest5 {
 57     //这段代码你会发现打印值依然未变,这是因为block对a的使用只是a的值的使用,而不是地址的引用,它在内部把a的值5作为常量来使用,因此在外部再改变a的值不会对block内部的a值造成任何影响,并且这时在block内部a是不能改变的,它在这里相当于一个常量。
 58     int a = 5;
 59     int (^myBlock)(int) = ^(int b){
 60 //        a = 2; //编译报错
 61         return a + b;
 62     };
 63     a = 7;
 64     int result = myBlock(3);
 65     //依然打印出8
 66     NSLog(@"%d", result);
 67     
 68 }
 69 
 70 - (void)blockTest4 {
 71     int a = 5;
 72     int (^myBlock)(int) = ^(int b){
 73         return a + b;
 74     };
 75 
 76     int result = myBlock(3);
 77     //打印出8
 78     NSLog(@"%d", result);
 79 
 80 }
 81 
 82  //square参数的类型是int(^)(int)
 83 - (void)blockTest3:(int(^)(int))square{
 84     NSLog(@"%d",square(3));
 85 }
 86 
 87 
 88 - (void)blockTest2 {
 89     //声明一个名为square的block,并且返回值和参数都是int型
 90     int (^square)(int);
 91     //为square赋实体,类似函数内部的执行模块
 92     square = ^(int a){
 93         return a * a ;
 94     };
 95     //调用block,这里跟C函数调用一模一样
 96     int result = square(5);
 97     //这里打印值为25
 98     NSLog(@"%d", result);
 99 }
100 - (void)blockTest1 {
101     //直接使用一个block实体来进行计算
102     //前面说过block某些方面跟函数非常相似,所以在这里
103     //a相当于参数,return的值相当于返回值,5相当于传入的参数
104     int result = ^(int a){
105         return a * a;
106     }(5);
107     NSLog(@"%d",result); //打印值为25
108     //当然,我们几乎不会这样去使用它
109 }

以上是十种简单的使用情景,具体的说明在注释里,我想应该足够详细了,反正在这里了解一下基本格式和简单使用就好,平时不会直接这样使用,所以不能说不重要,但不属于精髓部分,这些应该是小白都懂的东西。下面是调用:

 1 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
 2     
 3 //    [self blockTest1];
 4 //    [self blockTest2];
 5     //上面是block的一些简单定义和使用的方式,下面看一下block作为参数的简单使用
 6 //    [self blockTest3:^int(int a) { //执行这行代码,会打印出9
 7 //        return a * a;
 8 //    }];
 9     //如果上面的示例不太明白的话,可以继续往下看,后面会介绍block做参数在实际开发工作中的具体使用场景。
10     
11     //下面看一下block对外部变量的使用
12 //    [self blockTest4];
13     //可以把4和5对比来看
14 //    [self blockTest5];
15     
16     //对象引用,传址的情况
17 //    [self blockTest6];
18     
19     //如果外部变量不像上面一样是一个对象指针,那该怎么处理呢?
20 //    [self blockTest7];
21     
22     //静态变量全局只有一份,所以不管在哪里访问改变的都是a本身
23 //    [self blockTest8];
24     
25 //    [self blockTest9];
26     
27     //全局变量也一样
28     [self blockTest10];
29 }

好了,简单的东西简单说,如果你感兴趣还可以去我的github把demo弄下来自己运行看看。这是地址:https://github.com/alan12138/Classification-of-knowledge-points/tree/master/block%20iOS/1-block%E7%9A%84%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8

2、好,继续往下进行,再深入一点,下面是较常用的方式了,比较重要,用block在类之间传递信息

  前面已经说过,block某些方面很像函数(但是blockOC对象),所以定义一个block,相当于定义了一个函数,它既可以定义在方法内部也可以定义在方法外部(定义在外部的时候你可以把它看做一个全局变量),而且调用规则和函数一样,调用的时候才会执行block内部的代码;

  个人感觉从这里开始才真正进入主题,因为像之前那种做法基本没人会那么用,在同一个控制器内定义和使用block,如果是这种需求那就没有必要用block了,你用C函数或OC方法岂不是更方便?block最大的用处本人认为是在不同的类之间传递消息,类似代理,但是使用起来比代理更加灵活简便,但是对掌握不熟练地人来说也很容易出错,这也会让很多人望而生畏,之前的我也是如此,哈哈。

         好了,不BB那么多了,看到这就直接去看另外两个demo吧,代理传值和block传值,这两个已经在我之前的博客控制器之间传值的方式总结中讲过了,但是我打算在把代理和block两个demo拷过来,这样可以方便对比一下,并针对block再多加几句话。

第一步:在需要对外传值的控制器声明一个block。

1 //一般都会用这种方式声明一个block类型(返回值类型为空,参数类型为字符串)
2 typedef void (^TestBlock) (NSString *str);

第二步:声明block类型属性。

1 //一般使用copy策略,因为在ARC环境下已经不再有存储在栈中的block了,而是在堆中。声明一个TestBlock类型的变量
2 @property (nonatomic, copy) TestBlock testBlock;

第三步:在你认为需要传值出去的时机调用block,把你想要传递出去的信息传递出去。

1    //block传值
2     //自己确定需要传递信息的时机,这里是返回上一页的时候传值
3     //用if判断一下是为了安全性,只有block确实存在的时候才会调用,否则会出问题
4     if(self.testBlock) {
5         //调用block成员变量
6         self.testBlock(@"绿色");
7     }

第四步:调用block的时候会执行block实体,这时候就把消息传过来了。

1    //这里是block回传的值
2     //在这里实现block的实体,并接收调用者传递过来的参数,这就实现了控制器之间传递信息
3     nextVc.testBlock = ^(NSString *str) {
4         NSLog(@"%@",str);
5     };

代理这里就不说咯,自己可以把demo搞下来对比一下,github地址:https://github.com/alan12138/Classification-of-knowledge-points/tree/master/block%20iOS/2-block%EF%BC%88%E5%86%8D%E6%B7%B1%E5%85%A5%E4%B8%80%E7%82%B9%EF%BC%89

3、下面进行到第三阶段了,这里着重分析一下block的实现原理,虽然之前我在面试题blog中也简单分析过,但是这里打算放一些更详细一点的,我就直接把唐巧大大的博客链接放到这里了,因为过于底层的东西平时开发是很少会接触到的,但是对于爱刨根问底的我们-程序猿来说,还是很有诱惑力的,所以有兴趣的童鞋可以去看看,没兴趣的就继续往下吧。我在这里只摘抄一些我认为大家都应该知道的一些东西。

1)、闭包

闭包是一个函数(或指向函数的指针),再加上该函数执行的外部的上下文变量(有时候也称作自由变量)。block 实际上就是 Objective-C 语言对于闭包的实现。

2)、在 Objective-C 语言中,一共有 3 种类型的 block:

  1. _NSConcreteGlobalBlock 全局的静态 block,不会访问任何外部变量。
  2. _NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。
  3. _NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0 时会被销毁

3)、NSConcreteMallocBlock 类型的 block 通常不会在源码中直接出现,因为默认它是当一个 block 被 copy 的时候,才会将这个 block 复制到堆中,目标的 block 类型被修改为 _NSConcreteMallocBlock。

4)、在 ARC 开启的情况下,将只会有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的 block。

原本的 NSConcreteStackBlock 的 block 会被 NSConcreteMallocBlock 类型的 block 替代。证明方式是以下代码在 XCode 中,会输出<__NSMallocBlock__: 0x100109960>。在苹果的 官方文档 中也提到,当把栈中的 block 返回时,不需要调用 copy 方法了。

引用文章地址:http://blog.devtang.com/2013/07/28/a-look-inside-blocks/

 

上面这四条都来自于唐巧的博客,但是后面我还打算补充一点更通俗一点的东西。

 

5)Block在MRC下的内存管理

block在mrc的情况下,默认是存储在栈中的,因此不需要程序员自己对它做内存管理,即使引用了外部的对象,也不会对该对象的引用计数产生任何影响。

但是,一旦对block进行了copy操作,它便会被移到堆中,这时候便需要对它做内存管理,包括释放以及对它引用对象的释放,因为如果它存在堆中的时候,那么被它引用的对象引用计数会+1,所以这个对象需要释放两次。

 1 void(^myBlock)() = ^{
 2     NSLog(@"------");
 3 };
 4 myBlock();
 5 
 6 Block_copy(myBlock);
 7 
 8 // do something ...
 9 
10 Block_release(myBlock);

那么如何避免当block存在于堆,又对其他对象做了强引用,并且这个对象又对block产生了强引用的情况(比如block内部使用了self)。

 1 void(^myBlock)() = ^{
 2      NSLog(@"------%@",self.view);
 3  };
 4  myBlock();
 5  
 6  Block_copy(myBlock);
 7  
 8  // do something ...
 9  
10  Block_release(myBlock);

由于对block进行了copy操作,这时候self对block是强引用的,那么block内部又对self做了强引用,就会造成强引用循环,造成内存泄漏。解决办法便是:

 1 __block typeof(self) weakSelf = self;
 2 void(^myBlock)() = ^{
 3       NSLog(@"------%@",weakSelf.view);
 4   };
 5   myBlock();
 6   
 7   Block_copy(myBlock);
 8   
 9   // do something ...
10   
11   Block_release(myBlock);

加入了第一行之后,block便不会再对self做强引用了。

 

6)Block在ARC下的内存管理

  在ARC默认情况下,Block的内存存储在堆中,ARC会自动进行内存管理,程序员只需要避免循环引用即可.

 

 1 __weak typeof(self) weakSelf = self;
 2 void(^myBlock)() = ^{
 3       NSLog(@"------%@",weakSelf.view);
 4   };
 5   myBlock();
 6   
 7   Block_copy(myBlock);
 8   
 9   // do something ...
10   
11   Block_release(myBlock);

ARC环境下,只需要把__block换成__weak就可以了,还有个什么长长的前缀也可以,但是我忘记了,__weak够了。

为什么在ARC环境下__block不行了呢?因为__block在ARC中并不能禁止block对所引用的对象进行强引用,解决办法可以是在Block中将这个强引用对象置空,但是不推荐这么做。

但是在需要修改外部变量的时候,还是需要使用__block对变量进行修饰才能对变量进行修改的。

同时,在block内部定义的变量,会在作用域结束时自动释放,block对其并没有强引用关系,且在ARC中只需要避免循环引用即可,如果只是block单方面地对外部变量进行强引用,并不会造成内存泄漏。

 第三阶段就写这么多,即使写到这里我还是觉得远远没到自己感兴趣的那个程度,只掌握到这个程度的话在实际项目应用方面还是比较菜的,所以这里不多废话了,还有两个小demo,没事的话可以clone下来看一下,或许会有点帮助:https://github.com/alan12138/Classification-of-knowledge-points/tree/master/block%20iOS/3-%E7%A8%8D%E7%A8%8D%E6%80%BB%E7%BB%93%E4%B8%80%E4%B8%8B

 

4、实用篇,写一点实际应用场景。

1、之前提到过,也是最常见的,block做参数。

比如AFN中

1 [[AFHTTPSessionManager manager] POST:@"" parameters:params progress:^(NSProgress * _Nonnull uploadProgress) {
2         } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
3     //do something
4         } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
5 
6         }];
7     

Masonry中:(请忽略我见不得人的命名)

1 [self.nameLaebl makeConstraints:^(MASConstraintMaker *make) {
2         make.top.equalTo(self.productBtn.top);
3         make.left.equalTo(self.productBtn.right).offset(nameLabelLeftMargin);
4         make.right.equalTo(self.right).offset(nameLabelRightMargin);
5     }];

拿AFN来说吧,AFN对上面方法的实现是这样的:

 1 - (AFHTTPRequestOperation *)POST:(NSString *)URLString
 2                       parameters:(id)parameters
 3                          success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
 4                          failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
 5 {
 6     AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"POST" URLString:URLString parameters:parameters success:success failure:failure];
 7 
 8     [self.operationQueue addOperation:operation];
 9 
10     return operation;
11 }

继续向下调用:

 1 - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
 2                               failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
 3 {
 4 
 5         dispatch_async(http_request_operation_processing_queue(), ^{
 6             if (self.error) {
 7                 if (failure) {
 8                     dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
 9                         failure(self, self.error);
10                     });
11                 }
12             } else {
13                 id responseObject = self.responseObject;
14                 if (self.error) {
15                     if (failure) {
16                         dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
17                             failure(self, self.error);
18                         });
19                     }
20                 } else {
21                     if (success) {
22                         dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
23                             success(self, responseObject);
24                         });
25                     }
26 
27     };
28 }

这里负责把方法中block参数中的参数传递出去,而在我们使用这个方法的时候,便在block中接收它传递出来的结果,最常用的是responseObject,AFN返回的网络数据就存在这个值中。

而在我们使用POST方法的时候,就把success block内实现的代码块和failure block内的代码块层层传递到了AFN内部函数,然后等待网络请求的回应失败或者成功就调用相应的block,最后把获取的结果通过AFN方法中的block参数中的参数传给我们。

总结一下:先在block内部实现一个代码块,然后在合适的时候调用该block并传入参数,就可以实现对该代码块的调用,达到回调的目的。(不过AFN属于提供给别人用的,所以我理解的是这个顺序:你调用它的接口方法的时候,便实现了它block接口的实体,这样它在内部调用这个block的时候便有了实体,便成功调用参数block把block中的参数传递出来,所以block做参数的时候,一般block自己的参数便是传递信息的核心媒介)block就是一个对象,和OC中其他的对象一样,所以可以被当做参数来传递,区别是block是一个匿名函数,所以你可以调用它实现某些功能。

 

2、block做返回值

这里我写了个demo,直接看demo的代码吧:

先新建一个Person类:

 1 #import <Foundation/Foundation.h>
 2 
 3 @interface Person : NSObject
 4 
 5 - (NSString * (^)(NSUInteger))speak;
 6 
 7 - (void (^)(NSUInteger))eat;
 8 @end
 9 
10 
11 @implementation Person
12 - (NSString *(^)(NSUInteger))speak {
13     return ^ NSString * (NSUInteger a) {
14         return [NSString stringWithFormat:@"my age is %ld", a];
15     };
16 }
17 
18 - (void (^)(NSUInteger))eat {
19     return ^(NSUInteger a) {
20         NSLog(@"eat 了 %ld 个鸭梨", a);
21     };
22 }
23 @end

定义两个公共方法供外部调用,返回值都是block类型。

接下来看一下怎么在控制器中使用:

 1 Person *p = [[Person alloc] init];
 2     
 3 //    NSLog(@"%@",[p speak](5));
 4 //    
 5 //    [p eat](6);
 6     
 7     //其实上面这种做法是没有必要的,因为如果你真的想实现这种功能的话不需要多走一层block,但是这里是为了使用点语法实现链式调用,所以应该是下面这么用的
 8     NSLog(@"%@",p.speak(5));
 9     
10     p.eat(6);
11     
12     //是不是感觉有点像Masonry中的链式语法,点语法调用函数,就是调用该函数的的getter方法

OK,下面会讲到链式语法。上面代码的demo:https://github.com/alan12138/Classification-of-knowledge-points/tree/master/block%20iOS/4-%E5%AE%9E%E7%94%A8%E7%AF%87/block%E5%81%9A%E8%BF%94%E5%9B%9E%E5%80%BC

3、block链式语法

先看一个场景:

 1 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
 2     NSUInteger a = 1,b = 2,c = 3,d = 4,e = 5;
 3     NSUInteger result = 0;
 4     result = [self add:a b:b];
 5     result = [self add:result b:c];
 6     result = [self add:result b:d];
 7     result = [self add:result b:e];
 8     NSLog(@"%ld",result);
 9 }
10 
11 - (NSUInteger)add:(NSUInteger)a b:(NSUInteger)b {
12     return a + b;
13 }

假设定义一个简单的计算两个整数相加之和的方法,当我们需要计算多个数之和的时候,便需要不断的调用,但是链式语法便要简单的多:(这里的举例可能不太合适,因为谁都知道计算两数之和没必要单独写个方法,这里只是为了说明链式语法的优点)

1 NSUInteger result = [NSObject makeCalculate:^(CalculateManager *mgr) {
2         mgr.add(a).add(b).add(c).add(d).add(e);
3     }];

好了,下面就来看一下具体的实现过程是怎么样的:

先新建一个计算管理类:

 1 @interface ATCalcManager : NSObject
 2 @property (nonatomic, assign) NSUInteger result;
 3 
 4 - (ATCalcManager *(^)(NSUInteger s))add;
 5 
 6 
 7 @implementation ATCalcManager
 8 - (ATCalcManager *(^)(NSUInteger s))add {
 9     return ^ATCalcManager *(NSUInteger x) {
10         self.result += x;
11         return self;
12     };
13 }
14 @end

再新建一个NSObject的分类:

 1 @class ATCalcManager;
 2 @interface NSObject (ATCalc)
 3 + (NSUInteger)makeCalc:(void(^)(ATCalcManager *mgr))block;
 4 @end
 5 
 6 
 7 
 8 @implementation NSObject (ATCalc)
 9 + (NSUInteger)makeCalc:(void(^)(ATCalcManager *mgr))block {
10     ATCalcManager *mgr = [[ATCalcManager alloc] init];
11     block(mgr); //计算
12     return mgr.result;
13 }

在控制器中使用:

1  NSUInteger a = 1,b = 2,c = 3,d = 4,e = 5;
2  NSUInteger result = 0;
3 
4 result = [NSObject makeCalc:^(ATCalcManager *mgr) {
5         mgr.add(a).add(b).add(c).add(d).add(e);
6     }];
7  NSLog(@"%ld",result); //15

 

总结:1、先看控制器中的这行调用

1 result = [NSObject makeCalc:^(ATCalcManager *mgr) {
2         mgr.add(a).add(b).add(c).add(d).add(e);
3     }];

这个方法的参数是一个block,我们在这里定义这个block的参数实体,也就是我们想实现的链式语法。

 

2、Command + 鼠标左键进去看这个函数的具体实现,该方法内部初始化一个ATCalcManager实例对象mgr,然后作为block的参数传入block,然后调用该block,回调了我们上一步实现的block实体。

3、接下来执行的链式调用代码mgr.add(a).add(b).add(c).add(d).add(e);,可以看到add方法返回的是一个block,该block的实现是累加传递进来的值然后赋值给属性result保存下来,然后返回值是self,也就是ATCalcManager实例对象。这样又可以实现点语法继续调用add方法,最后return mgr.result;返回计算结果。

最后,实现链式调用的一个关键点:就是每次调用add方法必须返回自身,然后才可以继续调用,如此一致循环下去,实现这一切都是block的功劳。

demo:https://github.com/alan12138/Classification-of-knowledge-points/tree/master/block%20iOS/4-%E5%AE%9E%E7%94%A8%E7%AF%87/block%E9%93%BE%E5%BC%8F%E8%AF%AD%E6%B3%95

 4、block实现函数式编程

先看个示例:

 1 [[[[mgr calculate:^NSUInteger(NSUInteger result) {
 2         result += 1;
 3         return result;
 4     }] printResult:^(NSUInteger result) {
 5         NSLog(@"第一次计算结果为:%ld",result);
 6     }] calculate:^NSUInteger(NSUInteger result) {
 7         result -= 2;
 8         return result;
 9     }] printResult:^(NSUInteger result) {
10         NSLog(@"第二次计算结果为:%ld",result);
11     }];

计算和打印循环套用,逻辑过程清晰的连在一起,而且不需要中间变量。

下面看如何实现:

新建一个ATCalcManager类

 1 @interface ATCalcManager : NSObject
 2 @property (nonatomic, assign) NSUInteger result;
 3 - (instancetype)calculate:(NSUInteger(^)(NSUInteger result))calculateBlock;
 4 -(instancetype)printResult:(void(^)(NSUInteger result))printBlock;
 5 @end
 6 
 7 
 8 @implementation ATCalcManager
 9 - (instancetype)calculate:(NSUInteger (^)(NSUInteger result))calculateBlock
10 {
11     _result =  calculateBlock(_result);
12     return self;
13 }
14 
15 -(instancetype)printResult:(void(^)(NSUInteger result))printBlock{
16     printBlock(_result);
17     return self;
18 }
19 @end

和链式编程一样,上面两个函数的关键点仍然在于每次都必须返回self,这样才可以继续嵌套调用其他函数。函数的内部实现是做一些内部处理,然后传入参数来调用block。

demo:https://github.com/alan12138/Classification-of-knowledge-points/tree/master/block%20iOS/4-%E5%AE%9E%E7%94%A8%E7%AF%87/block%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B

 5、block保存代码块

首先说一下回调的概念,直接从大神那里拿过来了,简单易懂的例子:你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。

然后看一下具体场景中的应用,一个很常见的需求:

在tableview的每行cell上都有一个按钮,你需要在这个按钮被点击的时候处理这个动作,但是这个动作显然不适合在view中解决,这时就需要借助block回调来传递事件。

1、首先在cell视图中定义一个block属性

1 @property(copy, nonatomic) void (^callBack)(ATModel *);

2、在cell的实现文件中监听按钮点击

1 [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
2 
3 //...
4 
5 - (void)btnClick:(UIButton *)btn {
6     if (self.callBack) {
7         self.callBack(self.model);
8     }
9 }

3、在cellForRowAtIndexPath方法中为block赋实体

1 cell.callBack = ^(ATModel *model) {
2         NSLog(@"%ld---%@",indexPath.row,model.name);
3     };

这样你想要的点击rowIndex和对应的model都很容易的获取到了。

如果还不明白,直接运行一下demo,看一下输出就明白了。

 demo:https://github.com/alan12138/Classification-of-knowledge-points/tree/master/block%20iOS/4-%E5%AE%9E%E7%94%A8%E7%AF%87/block%E4%BF%9D%E5%AD%98%E4%BB%A3%E7%A0%81%E5%9D%97

 

总结:

做个总结吧,block有时候理解起来确实不那么顺,老感觉有点别扭有点绕,但是它也不过是个普通的OC对象而已,用习惯了就好了。最后推荐一个计算并缓存cellHeigth的小分类工具代码,很惭愧非原创,是我从大牛哪里学来的,做了一点微不足道的改动自己就用上了,哈哈,不过感觉里面的block使用的很值得看看,毕竟是实实在在在实际项目中应用的,而不是简单的demo简单介绍一下的就能比拟效果。代码在这里,有兴趣可以看看:https://github.com/alan12138/Tools/tree/master/rowHeightTest

以上是关于iOS-重回block小白之路的主要内容,如果未能解决你的问题,请参考以下文章

小白的python之路10/31&11/1文件操作系统

小白学开发(iOS)OC_ block数据类型(2015-08-08)

iOS学习之代码块(Block)

python 小白(无编程基础,无计算机基础)的开发之路 辅助知识1 with...as

离开互联网上岸1年后,我后悔了。重回大厂内卷,开启Python之路

ios block和delegate的区别