此文章由熱心網(wǎng)友 ccSundayChina授權(quán)轉(zhuǎn)載
在OC里面實現(xiàn)鏈式編程,可以使用返回調(diào)用者自身來實現(xiàn)配椭。但是類有很多虫溜,每個類也有很多方法,假如要實現(xiàn)鏈式編程股缸,則需要每一個方法進行命令與實現(xiàn)衡楞,工作量之大可想而知。
事實是雖然工作量巨大,但是卻充滿了吸引力瘾境。
之前見過有人將UIKit
和Foundation
框架中的大部分類一個個的都通過手工的方式添加上了鏈式編程的功能歧杏。洋洋灑灑的寫了很多,不得不佩服這種愚公精神迷守,然而仔細看代碼的話會發(fā)現(xiàn)其實有很多一樣的地方犬绒,而這些一樣的地方是可以提煉出來的。
懶惰是一個程序員的美德兑凿。
今天要來介紹一個非常有意思的框架凯力,這個框架會幫助我們自動的生成那些鏈式文件,不需要我們有多勤奮礼华,挨個的去寫實現(xiàn)咐鹤,只需要我們傳入我們想要進行轉(zhuǎn)換的類名數(shù)組就可以了。
首先老規(guī)矩圣絮,框架地址祈惶。
關(guān)于怎么使用可以具體看作者的readme,里面講的比較詳細了扮匠,然而就像作者其他的作品一樣捧请,代碼里面注釋依然是少得可憐。筆者也是斷斷續(xù)續(xù)的看了兩個星期棒搜,才對這個代碼的大致流程梳理的比較清晰了疹蛉。
下面是筆者整理的程序流程圖,水平有限帮非,不準確之處還望指出氧吐。
流程圖中可以看出,當我們輸入我們的類數(shù)組后末盔,首先會進到一個循環(huán)里去,如果這個類是OC類的話那么就會被加入到新的數(shù)組中座慰,并且也會將它的父類加入到新數(shù)組中陨舱,總之第一步就是對要處理的類進行處理。
然后輸出一個NSMutableSet版仔,我們要生成的鏈式類游盲,就是這里面的。
緊接著就是開始根據(jù)類生成鏈式文件了蛮粮,并寫到了mac的桌面上益缎。并且生成的文件主要是兩類,一類是MLChainObject類型的然想,另一類是Object+MLChain類型的莺奔。這兩類的作用不同,但又是互相緊密聯(lián)系的变泄。
由于代碼量巨大令哟,邏輯復雜恼琼,所以在一章篇幅里是很難將它解釋的明白的。(也可以解釋屏富,但可能會丟掉好多有營養(yǎng)的東西)晴竞。今天我們主要看看在mac桌面生成普通鏈式文件的大致過程。
還是先看下筆者畫的流程圖狠半。(強烈建議原作者做一點代碼的解釋工作噩死。。神年。)
可以看到我們將處理后的類名數(shù)組傳進去已维,就會以類名作為條件生成對應的頭文件(.h)、實現(xiàn)文件(.m)瘤袖、橋梁類名衣摩、以及父類名。然后根據(jù)這些條件創(chuàng)建我們專門用來生成代碼文件的模型CodeModel捂敌,最后通過NSFileManager文件管理工具艾扮,將文件的代碼字符串寫到mac桌面文件中。
[[NSFileManager defaultManager] writefileString:model.hFileResultString ToFileWithDiretory:XcodeCreateCodeDirectory fileName:chainClassName fileType:kML_CreateCodeFileType_h moveToTrashWhenFileExists:YES];
[[NSFileManager defaultManager] writefileString:model.mFileResultString ToFileWithDiretory:XcodeCreateCodeDirectory fileName:chainClassName fileType:kML_CreateCodeFileType_m moveToTrashWhenFileExists:YES];
下面來看一下普通鏈式頭文件(
.h文件
)是如何生成的占婉,還是看流程圖:
首先判斷該類是不是NSObject
基類泡嘴,然后對應生成不同的屬性,屬性的目的是為了獲取當前鏈式對象所綁定的的原生對象逆济。同時運用runtime獲取傳入類的所有方法酌予,并對方法進行過濾,主要就是去掉私有方法奖慌、轉(zhuǎn)換set抛虫、get方法、過濾鏈式方法等简僧,然后開始循環(huán)遍歷方法數(shù)組中的每一個方法建椰,運用適配器將方法都轉(zhuǎn)換成鏈式方法,其實主要就是加了一個前綴岛马,用以區(qū)別棉姐。其中比較難理解的就是生成鏈式宏定義(ChainMacroDefines
)的過程,生成了鏈式的宏定義啦逆,然后再將該宏定義與諸如(MLChainClass*(^)())chainSelName
的鏈式方法進行拼接伞矩。比如這樣:
#ifndef numberOfLines
#define numberOfLines(...) numberOfLines(@"setNumberOfLines:", (long long)metamacro_at(0, __VA_ARGS__))
#endif
/** ClassName-> UILabel
SEL: setNumberOfLines: 'q'
*/
- (MLChain4UILabel *(^)())numberOfLines;
按照此種方式對該類所有的方法都進行處理,并加入到methodAndMacro
數(shù)組中夏志,等遍歷完該類中的所有的方法時乃坤,就將該數(shù)組中的所有元素進行拼接,返回該類所有符合條件方法的宏定義與對應的鏈式方法名的拼接字符串。
到這里.h文件的主要內(nèi)容基本就完成了侥袜。然后就是再讓它拼接屬性字符串蝌诡,以構(gòu)造完整的content。
總之.h文件里生成了該類及其父類方法所對應的鏈式方法,并定義了用來接收可變參數(shù)的宏定義枫吧。宏定義的名字跟鏈式方法名一樣浦旱,這樣我們在使用點語法調(diào)用鏈式方法的時候,實際上就會進入到宏中九杂,而在這個宏中會接收一個可變的參數(shù)列表颁湖。如下面代碼所指示的:
#ifndef addTarget_action_forControlEvents
#define addTarget_action_forControlEvents(...) addTarget_action_forControlEvents(@"addTarget:action:forControlEvents:", metamacro_at(0, __VA_ARGS__), metamacro_at(1, __VA_ARGS__), (long long)metamacro_at(2, __VA_ARGS__))
#endif
/** ClassName-> UIButton
SEL: addTarget: '@'
action: ':'
forControlEvents: 'Q'
*/
- (MLChain4UIButton *(^)())addTarget_action_forControlEvents;
當調(diào)用obj. mlc_make.addTarget_action_forControlEvents(self,SEL action, UIControlEventTouchUpInside)
的時候,其實最終會進入NSObject+ChainInvocation.h
分類中的方法- (instancetype (^)(NSString *selName, ...))mlc_rootChainMethod
例隆,在這里我們會獲取到鏈式對象所綁定的原生對象與傳入的可變參數(shù)甥捺,最后轉(zhuǎn)換為原生對象調(diào)用原生的方法。(更加詳細的解析會在下一篇文章中)
如何生成文件镀层,那就是事先你需要將該文件的具體內(nèi)容镰禾,按照規(guī)則,拼接成一個具體的字符串唱逢,然后在通過NSFileManager寫到桌面就可以了吴侦。
既然鏈式類中調(diào)用所有的方法最終都會轉(zhuǎn)換為原生類調(diào)用原生的方法,所以在鏈式類的實現(xiàn)文件中坞古,就沒有必要寫具體的實現(xiàn)了备韧。這就是這個框架設計的巧妙之處。
看了
普通鏈式頭文件
中的內(nèi)容痪枫,下面簡單的看一下普通鏈式實現(xiàn)文件
中的代碼:
/**
m文件內(nèi)容
@param className <#className description#>
@return <#return value description#>
*/
+ (NSString *)mlc_mFileContentStrWithClassName:(NSString *)className{
NSMutableArray *resultStrs = [[NSMutableArray alloc] init];
if ([className isEqualToString:@"NSObject"]) {
NSString * mfileContentString = @"+ (void)load{\n\
\n [self mlc_setUpMethodDynamically];\
\n}";
[resultStrs addObject:mfileContentString];
}else{
NSString * mfileContentString = @"+ (void)load{\n\
\n [self mlc_setUpMethodDynamically];\
\n}";
NSString *chainObjectMethod =
[NSString stringWithFormat:
@"- (%@ *)chainObject{\
\n return (id)[super chainObject];\
\n}", className];
[resultStrs addObject:mfileContentString];
[resultStrs addObject:chainObjectMethod];
}
return [resultStrs componentsJoinedByString:@"\n"];
}
實現(xiàn)文件的代碼比較簡單织堂,簡單講就是添加了兩個方法,一個是+ (void)load方法奶陈,另一個是獲取該鏈式類所綁定的原生類的get方法易阳。
load方法會在main函數(shù)前就調(diào)用,給鏈式類中的所有方法做一個動態(tài)添加方法的操作吃粒,并且以- (instancetype (^)(NSString *selName, ...))mlc_rootChainMethod
作為它們的方法實現(xiàn)闽烙,也就是說當調(diào)用鏈式方法的時候,就會進到- (instancetype (^)(NSString *selName, ...))mlc_rootChainMethod
中声搁,而在這個方法里,我們會讓鏈式對象所綁定的原生對象調(diào)用鏈式方法所對應的原生方法捕发。
關(guān)于OBJ+MLChain分類文件的生成
分類里面的代碼是比較少的疏旨,也是比較容易理解的。主要就是為了當調(diào)用obj.mlc_make
的時候能夠返回一個橋梁對象扎酷,同時也是為了模仿Masonry的設置方式檐涝,作者在這里添加了類方法與實例方法。(這里就不做過多的解釋了。)
頭文件代碼如下:
+ (NSString *)mlc_methodStringInCategory
{
NSMutableString *resultString = [[NSMutableString alloc] init];
NSString *methodString1 = [NSString stringWithFormat:@"+ (MLChain4%@ *)mlc_make;\n\n", NSStringFromClass(self)];
NSString *methodString2 = [NSString stringWithFormat:@"- (MLChain4%@ *)mlc_make;\n\n", NSStringFromClass(self)];
NSString *methodString3 = [NSString stringWithFormat:@"+ (MLChain4%@ *)mlc_makeConfigs:(void(^)(MLChain4%@ *maker))block;\n\n", NSStringFromClass(self), NSStringFromClass(self)];
NSString *methodString4 = [NSString stringWithFormat:@"- (MLChain4%@ *)mlc_makeConfigs:(void(^)(MLChain4%@ *maker))block;\n\n", NSStringFromClass(self), NSStringFromClass(self)];
[resultString appendString:methodString1];
[resultString appendString:methodString2];
[resultString appendString:methodString3];
[resultString appendString:methodString4];
return resultString;
}
以上所講的就是普通鏈式文件與分類文件的大致生成過程谁榜,并簡要的講了下代碼是如何進行調(diào)用的幅聘。下篇文章會對這個框架的幾個難點進行分析,一個是方法宏定義的生成窃植、另一個是鏈式方法的調(diào)用過程帝蒿,其中關(guān)于調(diào)用過程里作者用到了YYKit中的一個重要的方法,下篇文章也會進行闡述巷怜。
最后建議大伙自己去demo中查看葛超。可以說這是一個設計非常巧妙的架構(gòu)延塑,可以學到很多的東西绣张,筆者也會持續(xù)的對它進行分析并繪制相關(guān)的程序流程圖,希望能幫助小伙伴理解关带,當然有錯誤的話還望指正出來侥涵。