上回書說道,你和伍麗娟已經(jīng)不可能了!我們也同時了解,雖然你的硬需求不能擴(kuò)展,但是你可以努力奮斗,用你殘缺的體魄通過不斷累積方法走上人生巔峰,這... ...,就是我們今天的主題,但... ...,你還是個單身狗!

我們之前說過過于Method的一些方法,并且充分說明了SEL,Method,IMP之間是何種關(guān)系,今天我們先來重新把有關(guān)于它常用的方法做一次梳理:
// 添加方法BOOLclass_addMethod( Class cls, SEL name, IMP imp,constchar*types );// 獲取實例方法Methodclass_getInstanceMethod( Class cls, SEL name );// 獲取類方法Methodclass_getClassMethod( Class cls, SEL name );// 獲取所有方法的ListMethod *class_copyMethodList( Class cls,unsignedint*outCount );// 替代方法的實現(xiàn)IMPclass_replaceMethod( Class cls, SEL name, IMP imp,constchar*types );// 返回方法的具體實現(xiàn)IMPclass_getMethodImplementation( Class cls, SEL name );IMPclass_getMethodImplementation_stret( Class cls, SEL name );// 類實例是否響應(yīng)指定的selectorBOOLclass_respondsToSelector( Class cls, SEL sel );??:當(dāng)判斷一個實例方法是否實現(xiàn)時,第一個參數(shù)要用類對象,也就是[Personclass]。? ? 當(dāng)判斷一個類方法是否實現(xiàn)時,第一個參數(shù)要傳元類,也就是object_getClass([Personclass])。
其他函數(shù)根據(jù)注釋,參數(shù)以及返回值應(yīng)該都能明白,說下class_addMethod這個方法的實現(xiàn)會覆蓋父類的方法實現(xiàn),但不會取代本類中已存在的實現(xiàn),如果本類中包含一個同名的實現(xiàn),則函數(shù)會返回NO。如果要修改已存在實現(xiàn),可以使用class_replaceMethod或者更深一步使用method_setImplementation,如果你想把一個函數(shù)替換為一個并未實現(xiàn)的函數(shù),原對應(yīng)函數(shù)實現(xiàn)保持不變(淡定,不會crash)。大概代碼如下:
在ViewController中實現(xiàn):-(void)swizzTest{NSLog(@"swizzTest_swizz");}在Person類中實現(xiàn):-(void)eat{NSLog(@"eat_person");}//在viewDidLoad中class_replaceMethod([selfclass],NSSelectorFromString(@"swizzTest"), method_getImplementation(class_getInstanceMethod([Personclass],NSSelectorFromString(@"eat"))),"v@:");或者:method_setImplementation(class_getInstanceMethod([selfclass],NSSelectorFromString(@"swizzTest")), method_getImplementation(class_getInstanceMethod([Personclass],NSSelectorFromString(@"eat"))));然后調(diào)用:[selfswizzTest];
打印結(jié)果:
RuntimeSkill[3701:729966]eat_person

??:把ViewController中的方法替換為Person的方法了?之前寫的幾篇總有人局限于類和對象的概念里出不來,會覺得只有對本類內(nèi)進(jìn)行操作才是可行的,在runtime的概念里,就是一堆C的結(jié)構(gòu)體和函數(shù)這些個玩意,對于方法,只要取到函數(shù)的指針,還不是你想干嘛就干嘛,為所欲為,勇往無前,不撞南墻不回頭!
參數(shù)分析
對于這些C函數(shù),我們來剖析一下它的參數(shù):
Class cls:類對象(??:我們可以看到獲取方法的函數(shù)有兩個class_getInstanceMethod,class_getClassMethod,分別獲取實例方法和類方法,但是如果我們要添加獲或者替換方法就需要注意你操作的是實例方法還是類方法,如果是類方法這個參數(shù)一定要傳本類的元類)
SEL name:方法的selector
IMP imp:函數(shù)對應(yīng)實現(xiàn)
const char *types:代表函數(shù)類型,比如無參數(shù)無返回值->”v@:”,int類型返回值,一個參數(shù)傳入->”i@:@”,如果你知道了對應(yīng)的Method,你可以直接通過method_getTypeEncoding函數(shù)獲取。
消息轉(zhuǎn)發(fā)
我們都知道調(diào)用一個沒有實現(xiàn)的方法時,會crash,我們來微笑著,一步步的看它是如何crash的,也許你還能插一手。同時想要深入靈活的了解關(guān)于函數(shù)方法的東西,我們也需要明白消息轉(zhuǎn)發(fā)的機(jī)制:
消息轉(zhuǎn)發(fā)第一步:+(BOOL)resolveInstanceMethod:(SEL)sel,+(BOOL)resolveClassMethod:(SEL)sel->討薪
當(dāng)向調(diào)用一個方法,但沒有實現(xiàn)時,消息會通過上面兩個方法尋找是否能找到實現(xiàn)?如果沒有則返回NO,進(jìn)入下一步。(雖然伍麗娟和你不可能了,但是她給你介紹了個工作,去浙江溫州伍氏皮革廠工作。但是老板到了開工資的日期,卻沒有發(fā)工資,你抓緊去問問到底還能不能發(fā)工資)。
- (id)forwardingTargetForSelector:(SEL)aSelector-> 尋找溫州皮革廠老板(能給錢的人)
第一步如果返回NO會通過- (id)forwardingTargetForSelector:(SEL)aSelector方法再次尋找,不過這次找的是一個能響應(yīng)該方法的對象。如果返回一個能響應(yīng)該消息的對象,那么消息會轉(zhuǎn)發(fā)到該對象那里, 如果返回nil則進(jìn)行下一步,如果返回的對象不能相應(yīng)此消息,直接返回異常。(你發(fā)現(xiàn)老板根本就不給發(fā)工資,老板帶著資金跑路了!于是你開始找老板,找到老板就能拿回工資,找不到老板你就只能出此下策進(jìn)行下一步了)。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector->私家偵探找線索
當(dāng)forwardingTargetForSelector :返回nil時,會進(jìn)行這一步,生成方法簽名,如果方法簽名為nil直接調(diào)用doesNotRecognizeSelector:返回異常,如果正常生成方法簽名,則進(jìn)行最后一步。(找私家偵探幫你找線索,如果沒找到直接上口號老板帶著小姨子跑了,我們沒有辦法,只能拿著錢包抵工資,原價二百多,三百多的錢包統(tǒng)統(tǒng)二十元... ...!如果偵探有線索你將踏上尋找老板并說服他發(fā)工資的漫漫長路。)
- (void)forwardInvocation:(NSInvocation *)anInvocation->私家偵探有線索,你踏上討薪的長征路!
到這這一步,其實我們還可以通過NSInvocation來力挽狂瀾(我們在前面說過這個東西,也是很神奇的存在,不過有點(diǎn)麻煩),如果在這一步也不處理,只要你實現(xiàn)forwardInvocation :方法就不會拋出異常,消息被過濾掉,也就是并不會走doesNotRecognizeSelector:方法。(財務(wù)沒幫你找到老板,私家偵探來幫你找到了線索,如果你能通過線索找到老板并說服給你發(fā)工資,討薪完成,如果找不到,破釜沉舟,棄場拿貨,統(tǒng)統(tǒng)二十元... ...,也有可能在你找他的這段時間,他還把貨轉(zhuǎn)移了?。。。?/p>
規(guī)避崩潰
其實對于class_addMethod等關(guān)于方法的函數(shù)本人感覺不像之前說到的那些函數(shù)功能指向性那么明確,也可以說它可以實現(xiàn)的東西更為靈活,我們從消息轉(zhuǎn)發(fā)的途徑上來說一下這個東西的用處:
在+(BOOL)resolveInstanceMethod:(SEL)sel,(BOOL)resolveClassMethod:(SEL)sel方法中進(jìn)行轉(zhuǎn)發(fā):
首先在`Person`類中在.h中聲明兩個方法,但不去實現(xiàn):-(void)unKnowSel_obj;+(void)unKonwSel_class;在.m中實現(xiàn)這兩個方法:-(void)noObjMethod{NSLog(@"未實現(xiàn)這個實例方法");}+(void)noClassMethod{NSLog(@"未實現(xiàn)這個類方法");}并且重寫消息轉(zhuǎn)發(fā)的方法:// 當(dāng)一個對象調(diào)用未實現(xiàn)的方法,會調(diào)用這個方法處理,并且會把對應(yīng)的方法列表傳過來.//注意:實例方法是存在于當(dāng)前對象對應(yīng)的類的方法列表中+(BOOL)resolveInstanceMethod:(SEL)sel{? ? SEL aSel =NSSelectorFromString(@"noObjMethod");? ? Method aMethod = class_getInstanceMethod(self, aSel);? ? class_addMethod(self, sel, method_getImplementation(aMethod),"v@:");returnYES;}// 當(dāng)一個類調(diào)用未實現(xiàn)的方法,會調(diào)用這個方法處理,并且會把對應(yīng)的方法列表傳過來.//注意:類方法是存在于類的元類的方法列表中+(BOOL)resolveClassMethod:(SEL)sel{? ? SEL aSel =NSSelectorFromString(@"noClassMethod");? ? Method aMethod = class_getClassMethod(self, aSel);? ? class_addMethod(object_getClass(self), sel, method_getImplementation(aMethod),"v@:");returnYES;}在VC中調(diào)用未實現(xiàn)的兩個方法:Person* person = [[Person alloc] init];[person unKnowSel_obj];[Person unKonwSel_class];
打印結(jié)果:
RuntimeSkill[4503:948902]未實現(xiàn)這個實例方法RuntimeSkill[4503:948902]未實現(xiàn)這個類方法
可見,我們在第一步對調(diào)用的方法使用class_addMethod進(jìn)行實現(xiàn),可以使消息正確轉(zhuǎn)發(fā),找到指定對應(yīng)函數(shù)實現(xiàn)(IMP)(你去財務(wù)要薪資,直接人家就給你了?。0严⑥D(zhuǎn)發(fā)第一步的兩個方法干掉,我們這樣試試:
聲明一個`Boss`類,并在.m中實現(xiàn)方法:@implementationBoss-(void)unKnowSel_obj{NSLog(@"unKnowSel_obj_Boss");}@end在`Person`類中重寫方法:-(id)forwardingTargetForSelector:(SEL)aSelector{return[[Boss alloc] init];}在VC中調(diào)用未實現(xiàn)的兩個方法:Person* person = [[Person alloc] init];[person unKnowSel_obj];
打印結(jié)果:
RuntimeSkill[4540:956249]unKnowSel_obj_Boss
我們制定了相應(yīng)該方法的對象,同樣完成消息轉(zhuǎn)發(fā)。(你去財務(wù),財務(wù)說老板跑了,但是你找到老板了,正好老板有錢,你的工資到位了?。?/p>
我們再把forwardingTargetForSelector :方法去掉,做如下操作:
在`Person`類中重寫方法:- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector {if([NSStringFromSelector(aSelector) isEqualToString:@"unKnowSel_obj"]) {return[NSMethodSignaturesignatureWithObjCTypes:"v@:"];? ? }return[supermethodSignatureForSelector:aSelector];}-(void)forwardInvocation:(NSInvocation*)anInvocation{? ? [anInvocation invokeWithTarget:[[Boss alloc] init]];}在VC中調(diào)用未實現(xiàn)的兩個方法:Person* person = [[Person alloc] init];[person unKnowSel_obj];
打印結(jié)果:
RuntimeSkill[5019:1010897]unKnowSel_obj_Boss
我們通過NSInvocation轉(zhuǎn)化為正常的消息轉(zhuǎn)發(fā)。(最終如果你去財務(wù)和直接找老板都失敗了,你還可以通過特殊手段拿到錢,并且不管是不是老板給錢就行。也有可能偵探告訴你的消息是假的,當(dāng)你反應(yīng)過來,回去拿貨的時候,貨已經(jīng)被轉(zhuǎn)移了,你的討薪計劃失敗?。?/p>
使用場景
扯了這么多淡,無非就是想讓你認(rèn)清現(xiàn)實,伍麗娟坑你了?。?!

吃一塹長一智,我們來看看如何避免吧:
對于class_addMethod這個方法之前與JSPatch結(jié)合較多,但是現(xiàn)在蘋果大有勢不兩立只勢,如果風(fēng)聲能過過去我們再說。(估計是過不去了,但是我們?nèi)钥梢岳@一些彎彎來做熱修復(fù))。
我們進(jìn)行數(shù)據(jù)解析時,經(jīng)常碰到服務(wù)器會給我們返回NULL,導(dǎo)致crash,然后你就會為你的容錯機(jī)制不健全感到羞愧。

老板開會的時候又會像上次一樣想你投來你看這個菜比,這時候你馬上登陸統(tǒng)計平臺,就像我這么做線上異常分析,然后你發(fā)現(xiàn)服務(wù)器給了你一個NULL,頓時殺心四起,不是說好不給NULL嗎 ?不是說好做彼此的天使嗎?于是你看到了我的講解:因為服務(wù)器返回數(shù)據(jù)中只有數(shù)字,字符串, 數(shù)組和字典四種類型,所以我們只要在NULL找不到方法實現(xiàn)的時候向能響應(yīng)這個方法的對象進(jìn)行轉(zhuǎn)發(fā)就可以啦。方法如下:
給`NSNull`創(chuàng)建一個分類,并在.m中實現(xiàn):#import"NSNull+safe.h"@implementationNSNull(safe)#define pLog#define JsonObjects @[@"",@0,@{},@[]]- (id)forwardingTargetForSelector:(SEL)aSelector {for(idjsonObjinJsonObjects) {if([jsonObj respondsToSelector:aSelector]) {#ifdef pLogNSLog(@"NULL出現(xiàn)啦!這個對象應(yīng)該是是_%@",[jsonObjclass]);#endifreturnjsonObj;? ? ? ? }? ? }return[superforwardingTargetForSelector:aSelector];}
然后調(diào)用這樣調(diào)用:
NSDictionary* dict = [[NSNullalloc] init];[dict objectForKey:@"123"];
結(jié)果:
RuntimeSkill[5526:1078091]NULL出現(xiàn)啦!這個對象應(yīng)該是是___NSDictionary0如果不實現(xiàn)這個分類則直接異常:*** Terminating app due to uncaughtexception'NSInvalidArgumentException', reason:'-[NSNull objectForKey:]: unrecognized selector sent to instance 0x10c8de180'
加入這個之后,就再也不怕服務(wù)器不做你的天使啦?。?!
關(guān)于消息傳遞和消息轉(zhuǎn)發(fā)以及對應(yīng)的各種函數(shù)的用處并不能一言蔽之,主要還得看智商,靈活把握才能用的得心應(yīng)手。
結(jié)語
Runtime就先到這里了,總共7篇,你和伍麗娟的愛情故事也算有頭有尾,歡迎大家拍磚。端午節(jié)快樂?。?!搬磚總地址

舍一碗粥,粥要立筷不倒
作者:CornBallast
鏈接:http://www.itdecent.cn/p/c5bdd6f7a68c
來源:簡書
簡書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處。