引言
一切要從我加入了Codewars
網(wǎng)站跪呈,開始與世界各地的Coder們一同刷題開始說起。在Codewars
中取逾,有許多題目是支持多種不同語言的耗绿,比如下面這一道題,把字符串中的所有單詞根據(jù)空格分割反轉(zhuǎn):
#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!
當(dāng)我們在Codewars
的OJ系統(tǒng)中通過這道題目的時(shí)候砾隅,可以看到所有答案中误阻,大家評分最高的答案,對應(yīng)上面這個(gè)題目晴埂,ObjC
的最佳答案是
#import <Foundation/Foundation.h>
NSString* reverse(NSString* text) {
NSArray *words = [text componentsSeparatedByString:@" "];
NSArray *reversed = [[words reverseObjectEnumerator] allObjects];
return [reversed componentsJoinedByString:@" "];
}
這是一段中規(guī)中矩的ObjC
代碼究反,跟我的答案是一樣的。
然后我們來看看Ruby
版本的最佳答案儒洛,雖然是同樣的解題思路精耐,但表現(xiàn)出來的視覺效果卻完全不同:
def reverse(string)
string.split.reverse.join(' ')
end
這里給沒有接觸過Ruby
的朋友解釋一下這段代碼,首先琅锻,Ruby的方法中可以省略return
關(guān)鍵字卦停,把方法中最后一個(gè)對象返回;其次split
方法不傳參數(shù)時(shí)候默認(rèn)是以空格符分割恼蓬,這樣就有了這段簡介明晰的代碼惊完。
當(dāng)然,ObjC
也是可以一句話寫完這段代碼的嘛:
NSString* reverse(NSString* text) {
return [[[[text componentsSeparatedByString:@" "] reverseObjectEnumerator] allObjects] componentsJoinedByString:@" "];
}
但你會發(fā)現(xiàn)处硬,這樣書寫的ObjC
代碼可讀性大打折扣小槐,一方面ObjC
的方法名太長,引起代碼折行以后郁油,很難一眼看清整個(gè)過程本股;另一方面,ObjC
的消息傳遞機(jī)制使用的中括號嵌套桐腌,嵌套多層時(shí)會增加額外的匹配括號的工作拄显,有些時(shí)候甚是煩人。
思考
上面的對比案站,引發(fā)了我對ObjC
的種種思考躬审,是否有可能使用ObjC
像Ruby
一樣優(yōu)雅地寫鏈?zhǔn)胶瘮?shù)調(diào)用,實(shí)際上Swift
中就采用了類似Ruby
的寫法蟆盐。
在ObjC中采用 . 調(diào)用方法
我們知道ObjC
中承边,點(diǎn)是用來獲取屬性(Property)
的,例如我們給AppDelegate
聲明了一個(gè)datas
的數(shù)組屬性
@interface AppDelegate ()
@property (nonatomic, strong) NSArray* datas;
@end
當(dāng)前的編譯器會自動(dòng)給datas
生成setter
和getter
方法石挂,并在沒有使用@synthesize
關(guān)鍵字合成的前提下博助,聲明了_datas
這個(gè)內(nèi)部指針。
這時(shí)我們?nèi)绻命c(diǎn)方法調(diào)用self.datas
痹愚,等同于傳遞了[self datas]
消息富岳,實(shí)際是發(fā)送的getter
消息蛔糯。如此一來,我們可以用property
或者不帶參數(shù)的方法窖式,來模擬點(diǎn)方法蚁飒,例如數(shù)組反轉(zhuǎn):
@interface NSArray (Functional)
- (NSArray*)reverse;
@property (nonatomic, strong, readonly) NSArray* reverse;
@end
我們給NSArray
增加一個(gè)名為Funcional的Category
,增加reverse
方法或者property
都可以萝喘,二選一即可淮逻。這里的property
聲明為readonly
,從而禁止調(diào)用setter
方法阁簸。
這兩種方法都可以實(shí)現(xiàn)self.reverse
爬早,實(shí)際消息發(fā)送都是[self reverse]
,實(shí)現(xiàn)如下:
@implementation NSArray (Functional)
- (NSArray *)reverse //reverse屬性的getter方法 和 - (NSArray*)reverse; 相同
{
return [[self reverseObjectEnumerator] allObjects];
}
@end
使用block閉包
然而當(dāng)我們需要改寫帶參數(shù)的方法時(shí)强窖,兩種方式實(shí)現(xiàn)都有些問題了凸椿。比如例子中的數(shù)組拼接字符串的方法componentsSeparatedByString
,我們這里需要用到ObjC
的閉包(block)
特性翅溺。
下面兩種方式也是等價(jià)的脑漫,原理同上面的reverse
:
@interface NSArray (Functional)
@property (nonatomic, strong, readonly) NSString* (^join)(NSString* join);
- (NSString* (^)(NSString*))join;
@end
實(shí)現(xiàn)代碼:
- (NSString *(^)(NSString * j))join
{
return ^ (NSString* j) {
return [self componentsJoinedByString:j];
};
}
另外我們給NSString
也增加一個(gè)Category
實(shí)現(xiàn)字符串切割成數(shù)組:
@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
如此,我們就可以通過點(diǎn)語法來實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用咙崎,來實(shí)現(xiàn)開篇說的問題优幸。
NSString* reverse(NSString* text) {
return text.split(@" ").reverse.join(@" ");
}
是不是有感覺在用Ruby
的錯(cuò)覺。
擴(kuò)展褪猛,函數(shù)式編程
ObjC
作為一個(gè)比C++
還要遙遠(yuǎn)的語言网杆,在某些方面還是缺少現(xiàn)代編程語言的特性。例如數(shù)組的Map
伊滋、Filter
碳却、Flatten
等高級函數(shù),Cocoa
框架都是沒有的笑旺。
而這些函數(shù)實(shí)在是太常用也太好用了昼浦,我們完全可以通過前面討論的方式,為NSArray
增加這些方便的高級函數(shù).
//定義block
typedef id (^MapBlock)(id x);
@property (nonatomic, strong, readonly) NSArray* (^map)(MapBlock);
//或者
- (NSArray *(^)(id (^)(id)))map;
實(shí)現(xiàn)如下:
- (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];
};
}
如此筒主,我們可以通過兩種方式來調(diào)用map
方法关噪,這兩種方式是等價(jià)的,數(shù)組[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);
});
小結(jié)
有人可能認(rèn)為乌妙,這些代碼并沒有太多的簡化我們的開發(fā)ObjC
的方式使兔,但請不要忽視這些微小的積累。代碼的簡化和優(yōu)化藤韵,帶來的是更低的耦合虐沥、更好的可讀性和更健壯的構(gòu)架,我們用了十幾分鐘的時(shí)間來討論ObjC
的鏈?zhǔn)胶瘮?shù)調(diào)用方法泽艘,必定會在以后的開發(fā)中置蜀,節(jié)省大量的重復(fù)勞動(dòng)時(shí)間奈搜。