前期回顧
分析
先來(lái)看下Masonry的完整調(diào)用:
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.top.mas_equalTo(0.f);
make.height.mas_equalTo(44.f);
}];
再來(lái)看下之前例子中的調(diào)用:
SQLTool *tool = [[SQLTool alloc] init];
NSString *sql = tool.select(nil).from(@"table").sql;
從中可以看出,Masonry中實(shí)現(xiàn)自動(dòng)布局的關(guān)鍵類MASConstraintMaker,他的實(shí)例并不是調(diào)用者自己創(chuàng)建的,而是通過(guò)調(diào)用方法mas_makeConstraints:(這里是用了Category),其參數(shù)block中的參數(shù)傳遞過(guò)來(lái)的。
也就是說(shuō),通過(guò)mas_makeConstraints:這個(gè)方法,我們不需要知道MASConstraintMaker的實(shí)例是怎么創(chuàng)建的,也不需要知道具體是怎么實(shí)現(xiàn)了給View添加了自動(dòng)布局,唯一需要的是實(shí)現(xiàn)(傳遞)一個(gè)block,在block里按照Masonry的方式指定需要添加的約束就可以了。
相信這些應(yīng)該都能理解,而這種實(shí)現(xiàn)方式就是本篇要說(shuō)的<b>函數(shù)式編程</b>。
函數(shù)式編程
什么是函數(shù)式編程?同前一篇一樣,這種理論性的東西請(qǐng)自行百度,這里不做解釋。函數(shù)式編程在iOS中是借由block實(shí)現(xiàn)的,通過(guò)聲明一個(gè)block,類似于定義了一個(gè)“函數(shù)”,再將這個(gè)“函數(shù)”傳遞給調(diào)用的方法,以此來(lái)實(shí)現(xiàn)對(duì)調(diào)用該方法時(shí)中間一些過(guò)程或者對(duì)結(jié)果處理的“自定義”,而其內(nèi)部的其他環(huán)節(jié)完全不需要暴露給調(diào)用者。實(shí)際上,調(diào)用者也根本不需要知道。
拿NSArray的一個(gè)排序方法sortedArrayUsingComparator:來(lái)說(shuō),我們可以通過(guò)一實(shí)現(xiàn)個(gè)名為NSComparator的block,來(lái)實(shí)現(xiàn)我們自己想要的排序邏輯。
同樣地,相信大部分人都用過(guò)AFNetworking。我們可以在發(fā)起請(qǐng)求前聲明success和failure的block作為所謂的回調(diào)。這其實(shí)本質(zhì)是一樣的,相當(dāng)于在這個(gè)地方我們?nèi)プ约憾x了在請(qǐng)求結(jié)束后的動(dòng)作。
在iOS的SDK中還有很多這樣的實(shí)現(xiàn),這種實(shí)現(xiàn)的背后也是設(shè)計(jì)模式的一種體現(xiàn),大家可以自行去體會(huì)。
#這里稍微吐個(gè)槽,之前有看到帖子說(shuō)下面的方式就是函數(shù)式編程:
make.left.right.bottom.mas_equalTo(0.f);
話不多說(shuō),這里也是大家自行體會(huì)。
將例子函數(shù)式化
回顧下前一篇中的例子,我在.h中聲明了一個(gè)名為sql,類型是NSString的屬性,目的是為了能夠得到拼接后的完整的SQL。但實(shí)際上,不管出于什么目的,一個(gè)單純的工具類不應(yīng)該暴露過(guò)多的屬性在外面。因?yàn)檫@些屬性有可能是在其調(diào)用某些方法時(shí)中間臨時(shí)保存一些數(shù)據(jù)所需的,如果暴露給調(diào)用者,要是在不恰當(dāng)?shù)臅r(shí)候被調(diào)用,可能會(huì)得到一個(gè)非預(yù)期的結(jié)果。
所以,我們要將這個(gè)屬性放到.m文件中去。然后再添加一個(gè)方法來(lái)讓外界調(diào)用者來(lái)保證能在正確的時(shí)機(jī)獲取其正確的值。在這個(gè)方法的內(nèi)部,做拼接SQL語(yǔ)句的操作,最后返回拼接的值給調(diào)用者。所以,改進(jìn)后的SQLTool是這樣的:
.h文件
@interface SQLTool : NSObject
//這里不需要了,放到.m中去
//@property (nonatomic, strong, readonly) NSString *sql;
//添加這個(gè)方法,參數(shù)是一個(gè)block,傳遞一個(gè)SQLTool的實(shí)例
+ (NSString *)makeSQL:(void(^)(SQLTool *tool))block;
//定義select的block
typedef SQLTool *(^Select)(NSArray<NSString *> *columns);
@property (nonatomic, strong, readonly) Select select;
...
@end
.m文件
@interface SQLTool ()
//用于保存拼接后的SQL
@property (nonatomic, strong) NSString *sql;
@end
@implementation SQLTool
+ (NSString *)makeSQL:(void(^)(SQLTool *tool))block {
if (block) {
SQLTool *tool = [[SQLTool alloc] init];
block(tool);
return tool.sql;
}
return nil;
}
- (Select)select {
return ^(NSArray<NSString *> *columns) {
if (columns.count > 0) {
self.sql = [NSString stringWithFormat:@"SELECT %@", [columns componentsJoinedByString:@","]];
} else {
self.sql = @"SELECT *";
}
//這里將自己返回出去
return self;
}
}
...
@end
然后調(diào)用時(shí)是這樣的:
NSString *sql = [SQLTool makeSQL:^(SQLTool * tool) {
tool.select(nil).from(@"table").where(@"columnA = 1");
}];
去除了SQLTool的初始化,和最后的對(duì)屬性sql的調(diào)用,整段代碼是不是變清爽多了,結(jié)構(gòu)和條理上也更清晰。調(diào)用者無(wú)需關(guān)注這個(gè)工具類的實(shí)例是怎么創(chuàng)建的,只要調(diào)用給定的方法,就可以構(gòu)筑自己想要的SQL語(yǔ)句。這個(gè)“過(guò)程”完全由調(diào)用者指定。
一些小結(jié)
本篇的內(nèi)容相對(duì)比較簡(jiǎn)單,因?yàn)閭鬟fblock的調(diào)用形式在iOS應(yīng)該算是挺常見(jiàn)的,比如NSArray的排序、快速枚舉等等。在平常調(diào)用這些方法的時(shí)候,如果仔細(xì)想想也不難想出其背后所代表的思想。另外,在回調(diào)方面,block和delegate(代理)起到的作用可以說(shuō)一樣的,所以有興趣的朋友可以試著把你自己的一些類中原本使用delegate方式回調(diào)的,改成block的形式。當(dāng)然,不管是block還是delegate都要注意避免循環(huán)引用的問(wèn)題。
最后
本篇算是解決了前一篇中提到的第一個(gè)問(wèn)題,有了跟Masonry一樣的體驗(yàn)。還剩下第二個(gè)問(wèn)題:如何可以做到使得調(diào)用者只能按照指定的順序、而不是隨意的調(diào)用。這個(gè)問(wèn)題留到下一篇再來(lái)探討。
謝謝閱讀!