像Ruby同樣寫ObjC,用block實現鏈式方法調用

Github源碼git

引言

一切要從我加入了Codewars網站,開始與世界各地的Coder們一同刷題開始提及。在Codewars中,有許多題目是支持多種不一樣語言的,好比下面這一道題,把字符串中的全部單詞根據空格分割反轉:github

#You need to write a function that reverses the words in a given string. A word can also fit an empty string. If this is not clear enough, here are some examples:

reverse('Hello World') === 'World Hello'
reverse('Hi There.') === 'There. Hi'

#Happy coding!

當咱們在Codewars的OJ系統中經過這道題目的時候,能夠看到全部答案中,你們評分最高的答案,對應上面這個題目,ObjC的最佳答案是objective-c

#import <Foundation/Foundation.h>
NSString* reverse(NSString* text) {
    NSArray *words = [text componentsSeparatedByString:@" "];
    NSArray *reversed = [[words reverseObjectEnumerator] allObjects];
    return [reversed componentsJoinedByString:@" "];
}

這是一段中規中矩的ObjC代碼,跟個人答案是同樣的。
而後咱們來看看Ruby版本的最佳答案,雖然是一樣的解題思路,但表現出來的視覺效果卻徹底不一樣:編程

def reverse(string)
  string.split.reverse.join(' ')
end

這裏給沒有接觸過Ruby的朋友解釋一下這段代碼,首先,Ruby的方法中能夠省略return關鍵字,把方法中最後一個對象返回;其次split方法不傳參數時候默認是以空格符分割,這樣就有了這段簡介明晰的代碼。數組

固然,ObjC也是能夠一句話寫完這段代碼的嘛:ruby

NSString* reverse(NSString* text) {
    return [[[[text componentsSeparatedByString:@" "] reverseObjectEnumerator] allObjects] componentsJoinedByString:@" "];
}

但你會發現,這樣書寫的ObjC代碼可讀性大打折扣,一方面ObjC的方法名太長,引發代碼折行之後,很難一眼看清整個過程;另外一方面,ObjC的消息傳遞機制使用的中括號嵌套,嵌套多層時會增長額外的匹配括號的工做,有些時候甚是煩人。閉包

思考

上面的對比,引起了我對ObjC的種種思考,是否有可能使用ObjCRuby同樣優雅地寫鏈式函數調用,實際上Swift中就採用了相似Ruby的寫法。app

在ObjC中採用 . 調用方法

咱們知道ObjC中,點是用來獲取屬性(Property)的,例如咱們給AppDelegate聲明瞭一個datas的數組屬性框架

@interface AppDelegate ()
@property (nonatomic, strong) NSArray* datas;
@end

當前的編譯器會自動給datas生成settergetter方法,並在沒有使用@synthesize關鍵字合成的前提下,聲明瞭_datas這個內部指針。編程語言

這時咱們若是用點方法調用self.datas,等同於傳遞了[self datas]消息,實際是發送的getter消息。如此一來,咱們能夠用property或者不帶參數的方法,來模擬點方法,例如數組反轉:

@interface NSArray (Functional)
- (NSArray*)reverse;
@property (nonatomic, strong, readonly) NSArray* reverse;
@end

咱們給NSArray增長一個名爲Funcional的Category,增長reverse方法或者property均可以,二選一便可。這裏的property聲明爲readonly,從而禁止調用setter方法。

這兩種方法均可以實現self.reverse,實際消息發送都是[self reverse],實現以下:

@implementation NSArray (Functional)
- (NSArray *)reverse //reverse屬性的getter方法 和 - (NSArray*)reverse; 相同
{
    return [[self reverseObjectEnumerator] allObjects];
}
@end

使用block閉包

然而當咱們須要改寫帶參數的方法時,兩種方式實現都有些問題了。好比例子中的數組拼接字符串的方法componentsSeparatedByString,咱們這裏須要用到ObjC閉包(block)特性。

下面兩種方式也是等價的,原理同上面的reverse

@interface NSArray (Functional)
@property (nonatomic, strong, readonly) NSString* (^join)(NSString* join);
- (NSString* (^)(NSString*))join;
@end

實現代碼:

- (NSString *(^)(NSString * j))join
{
    return ^ (NSString* j) {
        return [self componentsJoinedByString:j];
    };
}

另外咱們給NSString也增長一個Category實現字符串切割成數組:

@interface NSString (Functional)
@property (nonatomic, strong, readonly) NSArray* (^split)(NSString* s);
@end

@implementation NSString (Functional)
-(NSArray* (^)(NSString *))split
{
    return ^ (NSString* s) {
        return [self componentsSeparatedByString:s];
    };
}
@end

如此,咱們就能夠經過點語法來實現鏈式調用,來實現開篇說的問題。

NSString* reverse(NSString* text) {
    return text.split(@" ").reverse.join(@" ");
}

是否是有感受在用Ruby的錯覺。

擴展,函數式編程

ObjC做爲一個比C++還要遙遠的語言,在某些方面仍是缺乏現代編程語言的特性。例如數組的MapFilterFlatten等高級函數,Cocoa框架都是沒有的。

而這些函數實在是太經常使用也太好用了,咱們徹底能夠經過前面討論的方式,爲NSArray增長這些方便的高級函數.

//定義block
typedef id (^MapBlock)(id x);
@property (nonatomic, strong, readonly) NSArray* (^map)(MapBlock);
//或者
- (NSArray *(^)(id (^)(id)))map;

實現以下:

- (instancetype)map:(id (^)(id))map
{
    NSMutableArray* array = [NSMutableArray array];
    [self enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        id x = map(obj);
        if(x) [array addObject:x];
    }];
    return [array copy];
}

- (NSArray *(^)(id (^handle)(id)))map
{
    return ^(id (^handle)(id)) {
        return [self map:handle];
    };
}

如此,咱們能夠經過兩種方式來調用map方法,這兩種方式是等價的,數組[1,2,3]經過map方法變成了[3,6,9]:

[@[@1,@2,@3] map:^id(id x) {
   return @([x integerValue] * 3);
}];
    
@[@1,@2,@3].map(^id(id x) {
   return @([x integerValue] * 3);
});

小結

有人可能認爲,這些代碼並無太多的簡化咱們的開發ObjC的方式,但請不要忽視這些微小的積累。代碼的簡化和優化,帶來的是更低的耦合、更好的可讀性和更健壯的構架,咱們用了十幾分鐘的時間來討論ObjC的鏈式函數調用方法,一定會在之後的開發中,節省大量的重複勞動時間。

相關文章
相關標籤/搜索