Block使用场景

作者: 西木柚子 | 来源:发表于2016-09-03 16:36 被阅读648次

    引言

    最近在研究RAC的时候,发现绝大部分代码实现如下所示:

        RACSignal *completedMessageSource = [self.subscribeCommand.executionSignals flattenMap:^RACStream *(RACSignal *subscribeSignal) {
            return [[[subscribeSignal materialize] filter:^BOOL(RACEvent *event) {
                return event.eventType == RACEventTypeCompleted;
            }] map:^id(id value) {
                return NSLocalizedString(@"Thanks", nil);
            }];
        }];
    

    可以发现是block嵌套使用,这是使用block实现的函数编程范式。

    还有在使用masonry的时候,我们会见到如下代码:

    [View mas_makeConstraints:^(MASConstraintMaker *make) {
          make.top.equalTo(anotherView);
          make.left.equalTo(anotherView);
          make.width.mas_equalTo(@60);
          make.height.mas_equalTo(@60);
    }];
    
    

    这里使用的点语法连接,我们称之为链式编程范式。

    而这些实现都是依靠block,所以这篇博文主要讲解如下和block相关知识

    1. block作为参数
    2. block作为返回值
    3. block保存代码块
    4. block实现链式编程
    5. block实现函数式编程

    这篇博文需要你了解block的基础知识,如果不了解,可以阅读下面几篇博文先做了解

    IOS中 Block简介与用法(一)

    iOS深入学习(Block全面分析)

    谈Objective-C block的实现

    一篇文章看懂iOS代码块Block

    其实块就是OC中的匿名函数,无需定义函数名就可以使用,相当方便。具体看维基百科的定义(匿名函数


    block作为参数

    这应该是我们日常写代码中接触到最多的block使用场景了,我们通过AFN框架来看看。

    1、在写网络请求的时候我们经常会使用如下的代码:
            [[AFHTTPSessionManager manager]POST:@"http://www.baidu.com" parameters:params success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {
                //返回响应成功后执行的代码块1
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                //返回响应失败后执行的代码块2
            }
        ];
    
    
    2、 而AFN框架对于该函数的实现如下
    
    - (AFHTTPRequestOperation *)POST:(NSString *)URLString
                          parameters:(id)parameters
                             success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                             failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
    {
        AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"POST" URLString:URLString parameters:parameters success:success failure:failure];
    
        [self.operationQueue addOperation:operation];
    
        return operation;
    }
    
    
    3、 上述函数继续调用内部函数,把success和failure名字的block往下传递,直到如下函数,才执行这两个block:
    - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                                  failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
    {
    
            dispatch_async(http_request_operation_processing_queue(), ^{
                if (self.error) {
                    if (failure) {
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            failure(self, self.error);
                        });
                    }
                } else {
                    id responseObject = self.responseObject;
                    if (self.error) {
                        if (failure) {
                            dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                                failure(self, self.error);
                            });
                        }
                    } else {
                        if (success) {
                            dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                                success(self, responseObject);
                            });
                        }
    
        };
    }
    

    我们在步骤1的时候,就把success block内实现的代码块1和failure block内的代码块2传递到了步骤2的函数,然后该函数在内部继续调用内部方法,一层层把两个代码块传递到了步骤3,如下所示。

    
    if (failure) {
                            dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                                failure(self, self.error);
                            });
                        }
                    } else {
                        if (success) {
                            dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                                success(self, responseObject);
                            });
                        }
    
    

    然后等待网络请求的回应失败或者成功就调用相应的block,然后执行代码块1或者代码块2,如下所示

    上述代码中的如下两行代码,实现block的调用,并传入相应的函数

    failure(self, self.error);
    success(self, responseObject);
    
    
    总结:

    通过上面的例子我们看到,先在block内部实现一个代码块,因为block是一个OC对象,所以可以被当做参数传递到合适的地方,然后在合适的时候调用该block并传入参数,就可以实现对该代码块的调用,达到回调的目的。

    其实block就是一个对象,和OC中其他的对象一样,所以可以被当做参数来传递,区别是block是一个匿名函数,所以你可以调用它实现某些功能。


    block作为返回值

    定义一个函数,让block作为返回值,这样就可以返回一个代码块,然后在代码块里面执行某些操作完成一些功能。也可以返回自己,然后继续调用该函数,返回一个block,这样就可以实现masonry的链式调用效果,具体的我们下面再详细讲解。

    先来看一个例子

    #import <Foundation/Foundation.h>
    
    @interface Car : NSObject
    
    /**
     *  该函数返回一个block,该block无返回值,传入的参数为int类型
     *  void:无返回值
     *  int: 参数类型为int
     */
    -(void(^)(int))run;
    
    /**
     *  该函数返回一个block,该block有返回值为NSString类型,传入的参数为int类型
     *  NSString *:返回值为NSString类型
     *  int:       参数类型为int
     */
    -(NSString*(^)(int ))drive;
    
    @end
    
    
    
    
    #import "Car.h"
    
    @implementation Car
    
    - (void (^)(int))run
    {
        return ^(int meter){
            
            NSLog(@"car run %d meter",meter);
        };
    }
    
    -(NSString *(^)(int))drive{
        return  ^NSString *(int i){
            return [NSString stringWithFormat:@"I drive %zd meters in the car.",i];
        };
    }
    
    @end
    
    
    调用上述方法
        Car *car = [[Car alloc]init];
        car.run(10);
        NSString *str  =  car.drive(20);
        NSLog(@"%@", str);
    
    
    输出如下:
    2016-09-03 12:29:31.221 01-Block开发中使用场景[14981:995644] car run 10 meter
    2016-09-03 12:29:31.222 01-Block开发中使用场景[14981:995644] I drive 20 meters in the car.
    
    

    其实上面的run和drive函数我们完全可以用方法来实现相同的功能,但是那样我们只能使用[object methodName]的方式调用,没法使用点语法实现链式调用。

    其实上面的点语法调用函数,就是调用该函数的的getter方法。

    这里我们先了解可以使用点语法来实现和方法相同的功能,下面我们会讲到链式调用,就是使用此处的知识点。


    block保存代码块

    这个应该也是我们平时开发中用的比较多的,比如代替delegate实现回调。
    具体可以看这篇文章:
    block实现回调

    下面我们来看看block是如何实现回调的,首先搞清楚回调的概念就是,下面是通俗的解释:

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

    上面的文章提到一个使用场景:

    在tableview的cell上有一个按钮,由于采用MVC架构,cell的类和tableviewController类文件是分离的,我们想实现点击cell上面的按钮的时候可以回调tableviewController的内部方法来实现某些功能。

    1、定义回调函数

    首先我们在cell内定义一个回调函数,这里采用block来实现,具体实现为cell的一个property。

    //block名字为callBack ,传入参数NSString
    @property(copy, nonatomic) void (^callBack)(NSString *);
    
    
    
    2、调用回调函数

    接下来在tableviewController里面调用回调函数,也就是给cell的block属性赋值

    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        cell.callBack = ^(SGAttentionModel *model) {
            //do something
        };
        return cell;
    }
    
    
    3、触发回调关联事件

    然后当按钮点击的时候,就触发回调关联事件

        [self.button addTarget:self action:@selector(addFollow) forControlEvents:UIControlEventTouchUpInside];
    
    
    
    4、响应回调事件

    触发回调关联事件之后,cell就响应回调事件

    - (void)addFollow
    {
        if (self.callBack) {
            self.callBack(self.nsstring));
        }
    }
    
    
    

    当然上述实现完全可以用delegate来实现,但是使用block更加简洁

    上面的场景在步骤2给cell的block属性赋值一个代码块,然后在步骤4,cell调用该代码块实现功能。可以看到block可以实现保存、传递代码块,然后在合适的时候调用的功能。

    这里是跨类传递block给另外一个类,当然你也可以在类里面的一个地方保存一个block,然后在类的另外一个地方调用。


    block实现链式编程

    说完了上面的基础知识,我们下面就需要使用这些基础知识来实现链式编程和函数式编程。

    场景:

    实现加法计算,比如我需要计算1+2+5+14。通常做法如下:

    定义加法函数:

    -(NSInteger)addWithParam1:(NSInteger)param1 param2:(NSInteger)param2 {
        return param1 + param2;
    }
    
    

    然后调用:

        NSInteger result = [self addWithParam1:1 param2:2];
        result = [self addWithParam1:result param2:5];
        result = [self addWithParam1:result param2:14];
        NSLog(@"%zd",result);
    
    

    有多少个数字需要相加,我们就需要调用多少次这个方法,相当麻烦。

    我们想实现如下效果的调用,类似于masonry,也就是所谓的链式编程,看起来就十分优雅。

    int reslut = [NSObject makeCalculate:^(CalculateManager *mgr) {
            mgr.add(5).add(6).add(7).add(10);
        }];
    
    

    下面我们就来看看具体的实现过程吧。

    1、先定义一个NSObject的分类如下:
    
    #import <Foundation/Foundation.h>
    
    #import "CalculateManager.h"
    
    @interface NSObject (Calculate)
    
    + (int)makeCalculate:(void(^)(CalculateManager *))block;
    @end
    
    
    ==============================================================================
    
    #import "NSObject+Calculate.h"
    #import "CalculateManager.h"
    
    @implementation NSObject (Calculate)
    + (int)makeCalculate:(void (^)(CalculateManager *))block
    {
        // 创建计算管理者
        CalculateManager *mgr = [[CalculateManager alloc] init];
        
        // 执行计算
        block(mgr);
        
        return mgr.result;
    }
    @end
    
    
    2、继续定义一个类实现计算过程,比如add:
    #import <Foundation/Foundation.h>
    
    @interface CalculateManager : NSObject
    
    @property (nonatomic, assign) int result;
    
    - (CalculateManager *(^)(int))add;
    @end
    
    
    =======================================================
    
    
    #import "CalculateManager.h"
    
    @implementation CalculateManager
    
    - (CalculateManager * (^)(int))add
    {
        return ^(int value){
            _result += value;
            return self;
        };
    }
    
    @end
    
    
    
    3、然后调用:
       int reslut = [NSObject makeCalculate:^(CalculateManager *mgr) {
            mgr.add(5).add(6).add(7).add(10);
           
        }];
        
        NSLog(@"%zd",reslut);
    
    
    

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

    4、实现过程分析:
    1. 上面的步骤3,调用nsobject的分类方法makeCalculate:^(CalculateManager *mgr)block,该方法的参数是一个block,我们在这里传递一个定义好的block到该函数。block的实现是mgr.add(5).add(6).add(7).add(10)
    2. 回到步骤1,是分类方法makeCalculate:^(CalculateManager *mgr)block的具体实现,该方法内部初始化一个CalculateManager实例对象mgr,然后作为block的参数传入block,也就是步骤3的block内部的mgr参数,然后调用该block,也就是上一步实现的这句代码mgr.add(5).add(6).add(7).add(10),然后返回执行完毕后的结果,也就是mgr.result。
    3. 回到步骤2,是链式调用代码mgr.add(5).add(6).add(7).add(10)的关键,可以看到add方法返回的是一个block,该block的实现是累加传递进来的值然后赋值给属性result保存下来,然后返回值是self,也就是CalculateManager实例对象。这样又可以实现点语法继续调用add方法。

    block实现函数式编程

    不了解什么是函数编程的童鞋可以看看这篇文章,作为一个入门了解:

    函数式编程初探

    函数编程有两个好处:

    1. 去掉了中间变量
    2. 把运算过程写成一系列的函数嵌套调用,逻辑更加清楚

    还是上面的例子,不过这次我们想如下写:

        CalculateManager *mgr = [[CalculateManager alloc] init];
        [[[[mgr calculate:^(int result){
           // 存放所有的计算代码
            result += 5;
            result *= 5;
            return result;
        }]printResult:^(int result) {
            NSLog(@"第一次计算结果为:%d",result);
        }]calculate:^int(int result) {
            result -= 2;
            result /= 3;
            return result;
        }]printResult:^(int result) {
            NSLog(@"第二次计算结果为:%d",result);
        }];
    
    
    

    可以看到计算函数calculate和输出函数printResult可以一直循环嵌套调用,所有的运算过程全部聚在一起,看起来逻辑更加清楚。

    下面来看看如何实现:
    
    #import <Foundation/Foundation.h>
    @interface CalculateManager : NSObject
    @property (nonatomic, assign) int result;
    - (instancetype)calculate:(int(^)(int))calculateBlock;
    -(instancetype)printResult:(void(^)(int))printBlock;
    @end
    
    
    ===========================================================
    
    #import "CalculateManager.h"
    
    @implementation CalculateManager
    - (instancetype)calculate:(int (^)(int))calculateBlock
    {
        _result =  calculateBlock(_result);
        return self;
    }
    
    -(instancetype)printResult:(void(^)(int))printBlock{
        printBlock(_result);
        return self;
    }
    @end
    
    

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


    总结

    刚开始理解block可能有些费尽,觉得非常别扭。但是如果你把block当初普通的OC对象来理解,就可以马上理解上面列出的的block使用场景了。唯一的不同是block是函数,可以实现函数的所有功能。

    这让他即可以像对象一样被传递、保存、当做参数,也可以像函数一样实现功能。如果还是不太理解,我觉得block怪异的语法可能是理解的一大障碍,那么可以先去看看python的Lambda,同样是匿名函数,但是更好理解。

    相关文章

      网友评论

      • bigParis:有一个问题想请教下, 项目中大量使用了Masonry这种链式编程的框架, 如果以后Masonry不维护了(现在也不怎么维护了)怎么办, 或者有比Masonry更好的自动布局框架出现了. 话句话说, 这种链式编程的框架是不是有些特定的使用场景, 不知道博主有没有考虑过这样的问题?
        西木柚子:@bigParis 那是避免不了 用的时候就要做好这方面的准备
        bigParis:@西木柚子 感觉Masonry这种框架即使二次封装了也是换汤不换药,如果有更好的布局框架还是很难避免大面积修改,甚至会在旧项目直接兼容两套框架
        西木柚子:@bigParis 这种入侵比较大的框架,你在使用的时候,这些问题就无法避免,有精力的话可以自己做一次通用的二次封装,这样底层框架换了,也不用到处改代码
      • 瑞廷:厉害:+1:🏻

      本文标题:Block使用场景

      本文链接:https://www.haomeiwen.com/subject/ydokettx.html