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

相關文章
相關標籤/搜索