在OC里面實(shí)現(xiàn)鏈?zhǔn)骄幊汤赐ィ梢允褂梅祷卣{(diào)用者自身來(lái)實(shí)現(xiàn)。但是類有很多穿挨,每個(gè)類也有很多方法月弛,假如要實(shí)現(xiàn)鏈?zhǔn)骄幊蹋瑒t需要每一個(gè)方法進(jìn)行命令與實(shí)現(xiàn)科盛,工作量之大可想而知帽衙。
事實(shí)是雖然工作量巨大,但是卻充滿了吸引力贞绵。
之前見(jiàn)過(guò)有人將UIKit
和Foundation
框架中的大部分類一個(gè)個(gè)的都通過(guò)手工的方式添加上了鏈?zhǔn)骄幊痰墓δ芾髀堋Q笱鬄⒌膶懥撕芏啵坏貌慌宸@種愚公精神榨崩,然而仔細(xì)看代碼的話會(huì)發(fā)現(xiàn)其實(shí)有很多一樣的地方谴垫,而這些一樣的地方是可以提煉出來(lái)的。
懶惰是一個(gè)程序員的美德母蛛。
今天要來(lái)介紹一個(gè)非常有意思的框架翩剪,這個(gè)框架會(huì)幫助我們自動(dòng)的生成那些鏈?zhǔn)轿募恍枰覀冇卸嗲趭^彩郊,挨個(gè)的去寫實(shí)現(xiàn)前弯,只需要我們傳入我們想要進(jìn)行轉(zhuǎn)換的類名數(shù)組就可以了。
首先老規(guī)矩秫逝,框架地址博杖。
關(guān)于怎么使用可以具體看作者的readme,里面講的比較詳細(xì)了筷登,然而就像作者其他的作品一樣剃根,代碼里面注釋依然是少得可憐。筆者也是斷斷續(xù)續(xù)的看了兩個(gè)星期前方,才對(duì)這個(gè)代碼的大致流程梳理的比較清晰了狈醉。
下面是筆者整理的程序流程圖,水平有限惠险,不準(zhǔn)確之處還望指出苗傅。
流程圖中可以看出,當(dāng)我們輸入我們的類數(shù)組后班巩,首先會(huì)進(jìn)到一個(gè)循環(huán)里去渣慕,如果這個(gè)類是OC類的話那么就會(huì)被加入到新的數(shù)組中嘶炭,并且也會(huì)將它的父類加入到新數(shù)組中,總之第一步就是對(duì)要處理的類進(jìn)行處理逊桦。
然后輸出一個(gè)NSMutableSet眨猎,我們要生成的鏈?zhǔn)筋悾褪沁@里面的强经。
緊接著就是開(kāi)始根據(jù)類生成鏈?zhǔn)轿募怂悖懙搅薽ac的桌面上。并且生成的文件主要是兩類匿情,一類是MLChainObject類型的兰迫,另一類是Object+MLChain類型的。這兩類的作用不同炬称,但又是互相緊密聯(lián)系的汁果。
由于代碼量巨大,邏輯復(fù)雜玲躯,所以在一章篇幅里是很難將它解釋的明白的据德。(也可以解釋,但可能會(huì)丟掉好多有營(yíng)養(yǎng)的東西)府蔗。今天我們主要看看在mac桌面生成普通鏈?zhǔn)轿募拇笾逻^(guò)程晋控。
還是先看下筆者畫的流程圖汞窗。(強(qiáng)烈建議原作者做一點(diǎn)代碼的解釋工作姓赤。。仲吏。)
可以看到我們將處理后的類名數(shù)組傳進(jìn)去不铆,就會(huì)以類名作為條件生成對(duì)應(yīng)的頭文件(.h)、實(shí)現(xiàn)文件(.m)裹唆、橋梁類名誓斥、以及父類名。然后根據(jù)這些條件創(chuàng)建我們專門用來(lái)生成代碼文件的模型CodeModel许帐,最后通過(guò)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];
下面來(lái)看一下普通鏈?zhǔn)筋^文件(
.h文件
)是如何生成的成畦,還是看流程圖:
首先判斷該類是不是NSObject
基類距芬,然后對(duì)應(yīng)生成不同的屬性,屬性的目的是為了獲取當(dāng)前鏈?zhǔn)綄?duì)象所綁定的的原生對(duì)象循帐。同時(shí)運(yùn)用runtime獲取傳入類的所有方法框仔,并對(duì)方法進(jìn)行過(guò)濾,主要就是去掉私有方法拄养、轉(zhuǎn)換set离斩、get方法、過(guò)濾鏈?zhǔn)椒椒ǖ龋缓箝_(kāi)始循環(huán)遍歷方法數(shù)組中的每一個(gè)方法跛梗,運(yùn)用適配器將方法都轉(zhuǎn)換成鏈?zhǔn)椒椒ㄑ傲螅鋵?shí)主要就是加了一個(gè)前綴,用以區(qū)別茄袖。其中比較難理解的就是生成鏈?zhǔn)胶甓x(ChainMacroDefines
)的過(guò)程操软,生成了鏈?zhǔn)降暮甓x,然后再將該宏定義與諸如(MLChainClass*(^)())chainSelName
的鏈?zhǔn)椒椒ㄟM(jìn)行拼接宪祥。比如這樣:
#ifndef numberOfLines
#define numberOfLines(...) numberOfLines(@"setNumberOfLines:", (long long)metamacro_at(0, __VA_ARGS__))
#endif
/** ClassName-> UILabel
SEL: setNumberOfLines: 'q'
*/
- (MLChain4UILabel *(^)())numberOfLines;
按照此種方式對(duì)該類所有的方法都進(jìn)行處理聂薪,并加入到methodAndMacro
數(shù)組中,等遍歷完該類中的所有的方法時(shí)蝗羊,就將該數(shù)組中的所有元素進(jìn)行拼接藏澳,返回該類所有符合條件方法的宏定義與對(duì)應(yīng)的鏈?zhǔn)椒椒钠唇幼址?/p>
到這里.h文件的主要內(nèi)容基本就完成了。然后就是再讓它拼接屬性字符串耀找,以構(gòu)造完整的content翔悠。
總之.h文件里生成了該類及其父類方法所對(duì)應(yīng)的鏈?zhǔn)椒椒?并定義了用來(lái)接收可變參數(shù)的宏定義。宏定義的名字跟鏈?zhǔn)椒椒粯右懊ⅲ@樣我們?cè)谑褂命c(diǎn)語(yǔ)法調(diào)用鏈?zhǔn)椒椒ǖ臅r(shí)候蓄愁,實(shí)際上就會(huì)進(jìn)入到宏中,而在這個(gè)宏中會(huì)接收一個(gè)可變的參數(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;
當(dāng)調(diào)用obj. mlc_make.addTarget_action_forControlEvents(self,SEL action, UIControlEventTouchUpInside)
的時(shí)候撮抓,其實(shí)最終會(huì)進(jìn)入NSObject+ChainInvocation.h
分類中的方法- (instancetype (^)(NSString *selName, ...))mlc_rootChainMethod
,在這里我們會(huì)獲取到鏈?zhǔn)綄?duì)象所綁定的原生對(duì)象與傳入的可變參數(shù)摇锋,最后轉(zhuǎn)換為原生對(duì)象調(diào)用原生的方法丹拯。(更加詳細(xì)的解析會(huì)在下一篇文章中)
如何生成文件,那就是事先你需要將該文件的具體內(nèi)容荸恕,按照規(guī)則乖酬,拼接成一個(gè)具體的字符串,然后在通過(guò)NSFileManager寫到桌面就可以了融求。
既然鏈?zhǔn)筋愔姓{(diào)用所有的方法最終都會(huì)轉(zhuǎn)換為原生類調(diào)用原生的方法咬像,所以在鏈?zhǔn)筋惖膶?shí)現(xiàn)文件中,就沒(méi)有必要寫具體的實(shí)現(xiàn)了生宛。這就是這個(gè)框架設(shè)計(jì)的巧妙之處县昂。
看了
普通鏈?zhǔn)筋^文件
中的內(nèi)容,下面簡(jiǎn)單的看一下普通鏈?zhǔn)綄?shí)現(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"];
}
實(shí)現(xiàn)文件的代碼比較簡(jiǎn)單茅糜,簡(jiǎn)單講就是添加了兩個(gè)方法七芭,一個(gè)是+ (void)load方法,另一個(gè)是獲取該鏈?zhǔn)筋愃壎ǖ脑惖膅et方法蔑赘。
load方法會(huì)在main函數(shù)前就調(diào)用狸驳,給鏈?zhǔn)筋愔械乃蟹椒ㄗ鲆粋€(gè)動(dòng)態(tài)添加方法的操作预明,并且以- (instancetype (^)(NSString *selName, ...))mlc_rootChainMethod
作為它們的方法實(shí)現(xiàn),也就是說(shuō)當(dāng)調(diào)用鏈?zhǔn)椒椒ǖ臅r(shí)候耙箍,就會(huì)進(jìn)到- (instancetype (^)(NSString *selName, ...))mlc_rootChainMethod
中撰糠,而在這個(gè)方法里,我們會(huì)讓鏈?zhǔn)綄?duì)象所綁定的原生對(duì)象調(diào)用鏈?zhǔn)椒椒ㄋ鶎?duì)應(yīng)的原生方法辩昆。
關(guān)于OBJ+MLChain分類文件的生成
分類里面的代碼是比較少的阅酪,也是比較容易理解的。主要就是為了當(dāng)調(diào)用obj.mlc_make
的時(shí)候能夠返回一個(gè)橋梁對(duì)象汁针,同時(shí)也是為了模仿Masonry的設(shè)置方式术辐,作者在這里添加了類方法與實(shí)例方法。(這里就不做過(guò)多的解釋了施无。)
頭文件代碼如下:
+ (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;
}
以上所講的就是普通鏈?zhǔn)轿募c分類文件的大致生成過(guò)程辉词,并簡(jiǎn)要的講了下代碼是如何進(jìn)行調(diào)用的。下篇文章會(huì)對(duì)這個(gè)框架的幾個(gè)難點(diǎn)進(jìn)行分析猾骡,一個(gè)是方法宏定義的生成瑞躺、另一個(gè)是鏈?zhǔn)椒椒ǖ恼{(diào)用過(guò)程,其中關(guān)于調(diào)用過(guò)程里作者用到了YYKit中的一個(gè)重要的方法兴想,下篇文章也會(huì)進(jìn)行闡述幢哨。
最后建議大伙自己去demo中查看,可以說(shuō)這是一個(gè)設(shè)計(jì)非常巧妙的架構(gòu)嫂便,可以學(xué)到很多的東西(喜歡的記得給作者star哈)捞镰。筆者也會(huì)持續(xù)的對(duì)它進(jìn)行分析并繪制相關(guān)的程序流程圖,希望能幫助小伙伴理解顽悼,當(dāng)然有錯(cuò)誤的話還望指正出來(lái)曼振。
另外一篇關(guān)于這個(gè)框架的原理分析已經(jīng)寫好了几迄,這里是傳送門蔚龙。