Block使用场景

引言

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

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实现的函数编程范式。python

还有在使用masonry的时候,咱们会见到以下代码:ios

[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全面分析)async

谈Objective-C block的实现ide

一篇文章看懂iOS代码块Block函数式编程

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


block做为参数

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

一、在写网络请求的时候咱们常常会使用以下的代码:
[[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
        }
    ];复制代码
二、 而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;
}复制代码
三、 上述函数继续调用内部函数,把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 
  
  
  

 
  
  @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的内部方法来实现某些功能。

一、定义回调函数

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

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

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

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

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

[self.button addTarget:self action:@selector(addFollow) forControlEvents:UIControlEventTouchUpInside];复制代码
四、响应回调事件

触发回调关联事件以后,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);
    }];复制代码

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

一、先定义一个NSObject的分类以下:
#import 
  
  
  

 
  
  #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 

 复制代码
二、继续定义一个类实现计算过程,好比add:
#import 
  
  
  

 
  
  @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 

 复制代码
三、而后调用:
int reslut = [NSObject makeCalculate:^(CalculateManager *mgr) {
        mgr.add(5).add(6).add(7).add(10);

    }];

    NSLog(@"%zd",reslut);复制代码

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

四、实现过程分析:
  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 
  
  
  

 
  
  @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,一样是匿名函数,可是更好理解。

更多文章请访问个人我的博客:blog.ximu.site

相关文章
相关标签/搜索