接上篇文章传蹈,里面主要講述了普通鏈?zhǔn)轿募c分類鏈?zhǔn)轿募纳蛇^程距境。今天講一下里面的幾個(gè)技術(shù)難點(diǎn)央拖。
一:為什么要構(gòu)造鏈?zhǔn)椒椒ê甓x以及如何構(gòu)造祭阀。
思考一個(gè)問題鹉戚,當(dāng)我們給類添加了鏈?zhǔn)椒椒ê螅热?code>- (MLChain4UIButton *(^)())addTarget_action_forControlEvents;其實(shí)我們就已經(jīng)可以進(jìn)行調(diào)用了专控,比如這樣
UIButton.mlc_make. addTarget_action_forEvents(self, @selector(buttonClicked:), UIControlEventTouchUpInside)
這樣寫是不會報(bào)錯的抹凳,但是因?yàn)槲覀儧]有給這個(gè)鏈?zhǔn)椒椒ㄟM(jìn)行相應(yīng)的實(shí)現(xiàn),所以它是不會產(chǎn)生任何效果的伦腐。而如果親自動手給每一個(gè)鏈?zhǔn)椒椒ǘ继砑訉?shí)現(xiàn)的話赢底,就偏離了我們的初衷。
仔細(xì)想一想柏蘑,其實(shí)它的實(shí)現(xiàn)跟原生類里面的方法addTarget:(nullable id) action:(nonnull SEL) forControlEvents:(UIControlEvents)
的實(shí)現(xiàn)是一模一樣的幸冻,所以如果我們在調(diào)用鏈?zhǔn)椒椒ǖ臅r(shí)候能夠讓這個(gè)鏈?zhǔn)綄ο笏鶎?yīng)的原生對象去響應(yīng)它的原生方法的話,那么就能實(shí)現(xiàn)我們想要的效果了咳焚,而這時(shí)候我們也不需要寫任何的實(shí)現(xiàn)代碼洽损。
那么怎么實(shí)現(xiàn)這個(gè)功能呢粥脚,首先我們要知道的是班套,鏈?zhǔn)椒椒c原生方法名是可以按照固定的規(guī)則進(jìn)行相互的轉(zhuǎn)換的讹剔,而鏈?zhǔn)綄ο笈c原生對象也是可以按照固定規(guī)則進(jìn)行相互的轉(zhuǎn)換握侧。也就是說知道了一個(gè)就可以推算出另一個(gè)泊窘。這也是代碼中NSObject+ChainInfoAdaptor分類所做的事情叶骨,它像是一個(gè)適配器一樣匹层,可以根據(jù)你的需要將鏈?zhǔn)椒椒D(zhuǎn)換成原生方法名等极谊,反之亦然赏胚。而如果我們知道了原生的類访娶,原生的方法,以及方法的參數(shù)
的話觉阅,那么我們就可以根據(jù)NSInvocation逐一根據(jù)數(shù)據(jù)類型設(shè)置崖疤,最后使用[Invocation invoke]來手動的觸發(fā)這個(gè)方法。
所以我們在調(diào)用鏈?zhǔn)椒椒ǖ臅r(shí)候典勇,希望可以將這個(gè)方法所對應(yīng)的原生類劫哼、原生方法、以及它的各個(gè)參數(shù)都傳遞出去割笙,讓另一個(gè)專門的類(NSObject+ChainInvocation
)對這些進(jìn)行統(tǒng)一處理权烧,最后實(shí)現(xiàn)原生對象調(diào)用原生方法的效果。
這時(shí)候僅僅通過一個(gè)鏈?zhǔn)椒椒ㄊ菬o法實(shí)現(xiàn)的伤溉,因此需要一個(gè)更巧妙的設(shè)計(jì)般码。
我們知道如果定義一個(gè)形如addTarget_action_forControlEvents(...)
跟鏈?zhǔn)椒椒ㄍ暮甓x的話,那么我們再寫上面的UIButton.mlc_make. addTarget_action_forEvents(self, @selector(buttonClicked:), UIControlEventTouchUpInside)
的時(shí)候乱顾,程序就會進(jìn)到那個(gè)宏里面去板祝,這是我們必須清楚地一件事情。
知道了這個(gè)前提走净,我們就可以在這上面的宏中做文章了券时。思考一下如何使用這個(gè)宏傳出去原生方法孤里、方法的參數(shù)(個(gè)數(shù)不確定)和原生的類(這個(gè)可以在外面再獲取)橘洞,由于原生方法在生成鏈?zhǔn)椒椒ǖ臅r(shí)候是已經(jīng)知道了的捌袜,因?yàn)槲覀兪峭ㄟ^原生方法映射出鏈?zhǔn)椒椒ǖ模晕覀兛梢栽谧詣由晌募臅r(shí)候就將原生方法名作為宏中已知的默認(rèn)第一項(xiàng)炸枣,而外界輸入的可變參數(shù)作為第二個(gè)大項(xiàng)(可以通過RAC中的宏定義metamacro_at(N, ...)
來依次獲取參數(shù)的值)對外進(jìn)行傳遞虏等。所以我們需要的宏定義如下:
#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__))
因此我們在自動生成宏定義的時(shí)候圍繞上面的原則進(jìn)行生成就可以了,需要在注意一下參數(shù)的類型适肠。關(guān)鍵代碼如下:
macroDefineString = [NSString stringWithFormat: @"#ifndef %@\
\n#define %@(...) %@(@\"%@\", %@)\
\n#endif", chainSelName, chainSelName, chainSelName, selName, [mulArgStrs componentsJoinedByString:@", "]];
//chainSelName:鏈?zhǔn)椒椒┢洌瑂elName:原生方法名,mulArgStrs:該方法的參數(shù)類型數(shù)組迂猴。
二:如何在調(diào)用鏈?zhǔn)椒椒ǖ臅r(shí)候,轉(zhuǎn)為讓原生的對象去響應(yīng)原生的方法背伴。
定義好了統(tǒng)一的鏈?zhǔn)胶甓x后沸毁,我們希望能夠?qū)⒑曛械哪切l件都傳到一個(gè)固定的方法中進(jìn)行處理。也就是說在調(diào)用鏈?zhǔn)椒椒ǖ臅r(shí)候傻寂,無論是什么息尺,它的實(shí)現(xiàn)都能夠進(jìn)到另一個(gè)固定的方法中。
為此我們在每一個(gè)類中的+(void) load;
方法(這個(gè)方法的執(zhí)行在main函數(shù)前)中給每一個(gè)鏈?zhǔn)椒椒ǘ歼M(jìn)行了動態(tài)的綁定疾掰,并將它們方法的實(shí)現(xiàn)轉(zhuǎn)到一個(gè)固定的方法中搂誉。
+ (void)mlc_setUpMethodDynamically{
Class originalClass = MLCOriginalClass(self);//獲取鏈?zhǔn)筋愃鶎?yīng)的原生類
Class chainBridgeClass = self; //當(dāng)前鏈?zhǔn)筋? Method desMethod = class_getInstanceMethod([originalClass class], @selector(mlc_rootChainMethod)); //mlc_rootChainMethod是我們用來統(tǒng)一處理所有鏈?zhǔn)椒椒ǖ模墟準(zhǔn)椒椒ǘ紩哌@個(gè)方法静檬。
IMP imp = method_getImplementation(desMethod);//獲取mlc_rootChainMethod的實(shí)現(xiàn)
for (NSString *methodName in [originalClass mlc_noReturnValueSelNames]) {
NSString *chainMethodName = [originalClass mlc_chainSelNameWithOringalSelName:methodName];//將原生方法名轉(zhuǎn)換為鏈?zhǔn)椒椒? class_addMethod([chainBridgeClass class], sel_registerName(chainMethodName.UTF8String), imp, method_getTypeEncoding(desMethod));//給鏈?zhǔn)筋悇討B(tài)添加鏈?zhǔn)椒椒ㄌ堪茫⑶覍lc_rootChainMethod方法的實(shí)現(xiàn)作為它們的統(tǒng)一實(shí)現(xiàn)。
}
}
下面看一下所有鏈?zhǔn)椒椒ǖ淖罱K實(shí)現(xiàn):mlc_rootChainMethod
方法拂檩。這是在NSObject+ChainInvocation分類中定義的方法侮腹。
- (instancetype (^)(NSString *selName, ...))mlc_rootChainMethod{
return ^ id (NSString *selName, ...){
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
id chainObject = [self performSelector:@selector(chainObject)];
#pragma clang diagnostic pop
NSMethodSignature *sig = [chainObject methodSignatureForSelector:sel_registerName(selName.UTF8String)];
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:sig];
[inv setTarget:chainObject];
[inv setSelector:sel_registerName(selName.UTF8String)];
va_list args;
va_start(args, selName);
[NSObject mlc_setInv:inv withSig:sig andArgs:args];
va_end(args);
[inv invoke];
return self;
};
}
在這里這個(gè)方法的block參數(shù)第一項(xiàng)就是原生類的原始方法,而后面接收一個(gè)可變參數(shù)的列表稻励,也就是我們從宏定義中獲取到的參數(shù)父阻。
根據(jù)這些條件,我們就可以讓原生對象調(diào)用原生的方法了望抽。
三:再梳理一下程序的流程
1加矛、在生成文件的時(shí)候,讓鏈?zhǔn)椒椒c鏈?zhǔn)胶甓x的名字一樣煤篙。并在宏定義中將原生方法名作為第一項(xiàng)默認(rèn)參數(shù)斟览,輸入的方法變量作為可變參數(shù)列表這個(gè)第二個(gè)大項(xiàng)。鏈?zhǔn)胶昙胺椒ㄈ缦拢?/p>
#define chainSelName(...) chainSelName(@"originalSelName", [mulArgStrs componentsJoinedByString:@", "]])
//chainSelName:鏈?zhǔn)椒椒Ⅲ riginalSelName:原生方法趣惠、[mulArgStrs componentsJoinedByString:@", "]]方法參數(shù)類型宏定義狸棍。
- (MLChain4Object *(^)()) chainSelName;
2、程序啟動時(shí)味悄,給所有的鏈?zhǔn)筋惖逆準(zhǔn)椒椒ㄗ鲆粋€(gè)動態(tài)的綁定草戈,并且將NSObject+ChainInvocation
分類中的mlc_rootChainMethod
方法作為他們的統(tǒng)一實(shí)現(xiàn)。
這樣在書寫形如UIButton.mlc_make. addTarget_action_forEvents(self, @selector(buttonClicked:), UIControlEventTouchUpInside)
的時(shí)候侍瑟,就會進(jìn)到- (instancetype (^)(NSString *selName, ...))mlc_rootChainMethod
方法中唐片,在這里我們會獲取到原生類、原生方法涨颜、參數(shù)列表费韭,獲取到這些我們就可以手動的觸發(fā)方法了。主要就是使用NSInvocation逐一根據(jù)數(shù)據(jù)類型進(jìn)行設(shè)置庭瑰,設(shè)置的方法setInv:withSig:andArgs:
從YYKit中得來星持。篇幅有限,這里就先不做過多的對這個(gè)方法的闡述了弹灭,之后會加入的督暂。