最近在研究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相關知識網絡
- block做爲參數
- block做爲返回值
- block保存代碼塊
- block實現鏈式編程
- block實現函數式編程
這篇博文須要你瞭解block的基礎知識,若是不瞭解,能夠閱讀下面幾篇博文先作了解架構
iOS深刻學習(Block全面分析)async
一篇文章看懂iOS代碼塊Block函數式編程
其實塊就是OC中的匿名函數,無需定義函數名就可使用,至關方便。具體看維基百科的定義(匿名函數)
這應該是咱們平常寫代碼中接觸到最多的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
}
];複製代碼
- (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;
}複製代碼
- (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,這樣就能夠實現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方法。
這裏咱們先了解可使用點語法來實現和方法相同的功能,下面咱們會講到鏈式調用,就是使用此處的知識點。
這個應該也是咱們平時開發中用的比較多的,好比代替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,而後在類的另一個地方調用。
說完了上面的基礎知識,咱們下面就須要使用這些基礎知識來實現鏈式編程和函數式編程。
實現加法計算,好比我須要計算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);
}];複製代碼
下面咱們就來看看具體的實現過程吧。
#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 複製代碼
#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的功勞。
makeCalculate:^(CalculateManager *mgr)block
,該方法的參數是一個block,咱們在這裏傳遞一個定義好的block到該函數。block的實現是mgr.add(5).add(6).add(7).add(10)
makeCalculate:^(CalculateManager *mgr)block
的具體實現,該方法內部初始化一個CalculateManager實例對象mgr,而後做爲block的參數傳入block,也就是步驟3的block內部的mgr參數,而後調用該block,也就是上一步實現的這句代碼mgr.add(5).add(6).add(7).add(10)
,而後返回執行完畢後的結果,也就是mgr.result。mgr.add(5).add(6).add(7).add(10)
的關鍵,能夠看到add方法返回的是一個block,該block的實現是累加傳遞進來的值而後賦值給屬性result保存下來,而後返回值是self,也就是CalculateManager實例對象。這樣又能夠實現點語法繼續調用add方法。不瞭解什麼是函數編程的童鞋能夠看看這篇文章,做爲一個入門瞭解:
函數編程有兩個好處:
- 去掉了中間變量
- 把運算過程寫成一系列的函數嵌套調用,邏輯更加清楚
仍是上面的例子,不過此次咱們想以下寫:
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