上回書說道奸攻,你和伍麗娟已經(jīng)不可能了匆背!我們也同時(shí)了解呼伸,雖然你的硬需求不能擴(kuò)展,但是你可以努力奮斗钝尸,用你殘缺的體魄通過不斷累積方法走上人生巔峰括享,這... ...搂根,就是我們今天的主題,但... ...奶浦,你還是個(gè)單身狗兄墅!
我們之前說過過于Method的一些方法,并且充分說明了SEL澳叉,Method隙咸,IMP之間是何種關(guān)系,今天我們先來重新把有關(guān)于它常用的方法做一次梳理:
// 添加方法BOOLclass_addMethod( Class cls, SEL name, IMP imp,constchar*types );// 獲取實(shí)例方法Methodclass_getInstanceMethod( Class cls, SEL name );// 獲取類方法Methodclass_getClassMethod( Class cls, SEL name );// 獲取所有方法的ListMethod *class_copyMethodList( Class cls,unsignedint*outCount );// 替代方法的實(shí)現(xiàn)IMPclass_replaceMethod( Class cls, SEL name, IMP imp,constchar*types );// 返回方法的具體實(shí)現(xiàn)IMPclass_getMethodImplementation( Class cls, SEL name );IMPclass_getMethodImplementation_stret( Class cls, SEL name );// 類實(shí)例是否響應(yīng)指定的selectorBOOLclass_respondsToSelector( Class cls, SEL sel );??:當(dāng)判斷一個(gè)實(shí)例方法是否實(shí)現(xiàn)時(shí)成洗,第一個(gè)參數(shù)要用類對(duì)象五督,也就是[Personclass]。? ? 當(dāng)判斷一個(gè)類方法是否實(shí)現(xiàn)時(shí)瓶殃,第一個(gè)參數(shù)要傳元類充包,也就是object_getClass([Personclass])。
其他函數(shù)根據(jù)注釋遥椿,參數(shù)以及返回值應(yīng)該都能明白基矮,說下class_addMethod這個(gè)方法的實(shí)現(xiàn)會(huì)覆蓋父類的方法實(shí)現(xiàn),但不會(huì)取代本類中已存在的實(shí)現(xiàn)冠场,如果本類中包含一個(gè)同名的實(shí)現(xiàn)家浇,則函數(shù)會(huì)返回NO。如果要修改已存在實(shí)現(xiàn)碴裙,可以使用class_replaceMethod或者更深一步使用method_setImplementation钢悲,如果你想把一個(gè)函數(shù)替換為一個(gè)并未實(shí)現(xiàn)的函數(shù),原對(duì)應(yīng)函數(shù)實(shí)現(xiàn)保持不變(淡定舔株,不會(huì)crash)莺琳。大概代碼如下:
在ViewController中實(shí)現(xiàn):-(void)swizzTest{NSLog(@"swizzTest_swizz");}在Person類中實(shí)現(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的方法了?之前寫的幾篇總有人局限于類和對(duì)象的概念里出不來载慈,會(huì)覺得只有對(duì)本類內(nèi)進(jìn)行操作才是可行的惭等,在runtime的概念里,就是一堆C的結(jié)構(gòu)體和函數(shù)這些個(gè)玩意娃肿,對(duì)于方法咕缎,只要取到函數(shù)的指針,還不是你想干嘛就干嘛料扰,為所欲為凭豪,勇往無(wú)前,不撞南墻不回頭晒杈!
參數(shù)分析
對(duì)于這些C函數(shù)嫂伞,我們來剖析一下它的參數(shù):
Class cls:類對(duì)象(??:我們可以看到獲取方法的函數(shù)有兩個(gè)class_getInstanceMethod,class_getClassMethod,分別獲取實(shí)例方法和類方法帖努,但是如果我們要添加獲或者替換方法就需要注意你操作的是實(shí)例方法還是類方法撰豺,如果是類方法這個(gè)參數(shù)一定要傳本類的元類)
SEL name:方法的selector
IMP imp:函數(shù)對(duì)應(yīng)實(shí)現(xiàn)
const char *types:代表函數(shù)類型,比如無(wú)參數(shù)無(wú)返回值->”v@:”拼余,int類型返回值污桦,一個(gè)參數(shù)傳入->”i@:@”,如果你知道了對(duì)應(yīng)的Method匙监,你可以直接通過method_getTypeEncoding函數(shù)獲取凡橱。
消息轉(zhuǎn)發(fā)
我們都知道調(diào)用一個(gè)沒有實(shí)現(xiàn)的方法時(shí),會(huì)crash亭姥,我們來微笑著稼钩,一步步的看它是如何crash的,也許你還能插一手达罗。同時(shí)想要深入靈活的了解關(guān)于函數(shù)方法的東西坝撑,我們也需要明白消息轉(zhuǎn)發(fā)的機(jī)制:
消息轉(zhuǎn)發(fā)第一步:+(BOOL)resolveInstanceMethod:(SEL)sel,+(BOOL)resolveClassMethod:(SEL)sel->討薪
當(dāng)向調(diào)用一個(gè)方法粮揉,但沒有實(shí)現(xiàn)時(shí)巡李,消息會(huì)通過上面兩個(gè)方法尋找是否能找到實(shí)現(xiàn)?如果沒有則返回NO扶认,進(jìn)入下一步击儡。(雖然伍麗娟和你不可能了,但是她給你介紹了個(gè)工作蝠引,去浙江溫州伍氏皮革廠工作。但是老板到了開工資的日期蛀柴,卻沒有發(fā)工資螃概,你抓緊去問問到底還能不能發(fā)工資)。
- (id)forwardingTargetForSelector:(SEL)aSelector-> 尋找溫州皮革廠老板(能給錢的人)
第一步如果返回NO會(huì)通過- (id)forwardingTargetForSelector:(SEL)aSelector方法再次尋找鸽疾,不過這次找的是一個(gè)能響應(yīng)該方法的對(duì)象吊洼。如果返回一個(gè)能響應(yīng)該消息的對(duì)象,那么消息會(huì)轉(zhuǎn)發(fā)到該對(duì)象那里制肮, 如果返回nil則進(jìn)行下一步冒窍,如果返回的對(duì)象不能相應(yīng)此消息,直接返回異常豺鼻。(你發(fā)現(xiàn)老板根本就不給發(fā)工資综液,老板帶著資金跑路了!于是你開始找老板儒飒,找到老板就能拿回工資谬莹,找不到老板你就只能出此下策進(jìn)行下一步了)。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector->私家偵探找線索
當(dāng)forwardingTargetForSelector :返回nil時(shí),會(huì)進(jìn)行這一步附帽,生成方法簽名埠戳,如果方法簽名為nil直接調(diào)用doesNotRecognizeSelector:返回異常,如果正常生成方法簽名蕉扮,則進(jìn)行最后一步整胃。(找私家偵探幫你找線索,如果沒找到直接上口號(hào)老板帶著小姨子跑了喳钟,我們沒有辦法屁使,只能拿著錢包抵工資,原價(jià)二百多荚藻,三百多的錢包統(tǒng)統(tǒng)二十元... ...屋灌!如果偵探有線索你將踏上尋找老板并說服他發(fā)工資的漫漫長(zhǎng)路。)
- (void)forwardInvocation:(NSInvocation *)anInvocation->私家偵探有線索应狱,你踏上討薪的長(zhǎng)征路共郭!
到這這一步,其實(shí)我們還可以通過NSInvocation來力挽狂瀾(我們?cè)谇懊嬲f過這個(gè)東西疾呻,也是很神奇的存在除嘹,不過有點(diǎn)麻煩),如果在這一步也不處理岸蜗,只要你實(shí)現(xiàn)forwardInvocation :方法就不會(huì)拋出異常尉咕,消息被過濾掉,也就是并不會(huì)走doesNotRecognizeSelector:方法璃岳。(財(cái)務(wù)沒幫你找到老板年缎,私家偵探來幫你找到了線索,如果你能通過線索找到老板并說服給你發(fā)工資铃慷,討薪完成单芜,如果找不到,破釜沉舟犁柜,棄場(chǎng)拿貨洲鸠,統(tǒng)統(tǒng)二十元... ...,也有可能在你找他的這段時(shí)間馋缅,他還把貨轉(zhuǎn)移了0峭蟆!S┿病)
規(guī)避崩潰
其實(shí)對(duì)于class_addMethod等關(guān)于方法的函數(shù)本人感覺不像之前說到的那些函數(shù)功能指向性那么明確瘾腰,也可以說它可以實(shí)現(xiàn)的東西更為靈活,我們從消息轉(zhuǎn)發(fā)的途徑上來說一下這個(gè)東西的用處:
在+(BOOL)resolveInstanceMethod:(SEL)sel稚疹,(BOOL)resolveClassMethod:(SEL)sel方法中進(jìn)行轉(zhuǎn)發(fā):
首先在`Person`類中在.h中聲明兩個(gè)方法居灯,但不去實(shí)現(xiàn):-(void)unKnowSel_obj;+(void)unKonwSel_class;在.m中實(shí)現(xiàn)這兩個(gè)方法:-(void)noObjMethod{NSLog(@"未實(shí)現(xiàn)這個(gè)實(shí)例方法");}+(void)noClassMethod{NSLog(@"未實(shí)現(xiàn)這個(gè)類方法");}并且重寫消息轉(zhuǎn)發(fā)的方法:// 當(dāng)一個(gè)對(duì)象調(diào)用未實(shí)現(xiàn)的方法祭务,會(huì)調(diào)用這個(gè)方法處理,并且會(huì)把對(duì)應(yīng)的方法列表傳過來.//注意:實(shí)例方法是存在于當(dāng)前對(duì)象對(duì)應(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)一個(gè)類調(diào)用未實(shí)現(xiàn)的方法,會(huì)調(diào)用這個(gè)方法處理,并且會(huì)把對(duì)應(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)用未實(shí)現(xiàn)的兩個(gè)方法:Person* person = [[Person alloc] init];[person unKnowSel_obj];[Person unKonwSel_class];
打印結(jié)果:
RuntimeSkill[4503:948902]未實(shí)現(xiàn)這個(gè)實(shí)例方法RuntimeSkill[4503:948902]未實(shí)現(xiàn)這個(gè)類方法
可見怪嫌,我們?cè)诘谝徊綄?duì)調(diào)用的方法使用class_addMethod進(jìn)行實(shí)現(xiàn)义锥,可以使消息正確轉(zhuǎn)發(fā),找到指定對(duì)應(yīng)函數(shù)實(shí)現(xiàn)(IMP)(你去財(cái)務(wù)要薪資岩灭,直接人家就給你了0璞丁)。把消息轉(zhuǎn)發(fā)第一步的兩個(gè)方法干掉噪径,我們這樣試試:
聲明一個(gè)`Boss`類柱恤,并在.m中實(shí)現(xiàn)方法:@implementationBoss-(void)unKnowSel_obj{NSLog(@"unKnowSel_obj_Boss");}@end在`Person`類中重寫方法:-(id)forwardingTargetForSelector:(SEL)aSelector{return[[Boss alloc] init];}在VC中調(diào)用未實(shí)現(xiàn)的兩個(gè)方法:Person* person = [[Person alloc] init];[person unKnowSel_obj];
打印結(jié)果:
RuntimeSkill[4540:956249]unKnowSel_obj_Boss
我們制定了相應(yīng)該方法的對(duì)象,同樣完成消息轉(zhuǎn)發(fā)找爱。(你去財(cái)務(wù)梗顺,財(cái)務(wù)說老板跑了,但是你找到老板了车摄,正好老板有錢寺谤,你的工資到位了!)
我們?cè)侔裦orwardingTargetForSelector :方法去掉吮播,做如下操作:
在`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)用未實(shí)現(xiàn)的兩個(gè)方法:Person* person = [[Person alloc] init];[person unKnowSel_obj];
打印結(jié)果:
RuntimeSkill[5019:1010897]unKnowSel_obj_Boss
我們通過NSInvocation轉(zhuǎn)化為正常的消息轉(zhuǎn)發(fā)变屁。(最終如果你去財(cái)務(wù)和直接找老板都失敗了,你還可以通過特殊手段拿到錢意狠,并且不管是不是老板給錢就行粟关。也有可能偵探告訴你的消息是假的,當(dāng)你反應(yīng)過來环戈,回去拿貨的時(shí)候闷板,貨已經(jīng)被轉(zhuǎn)移了,你的討薪計(jì)劃失斣喝;坠浮)
使用場(chǎng)景
扯了這么多淡,無(wú)非就是想讓你認(rèn)清現(xiàn)實(shí)迫悠,伍麗娟坑你了!9摇创泄!
吃一塹長(zhǎng)一智,我們來看看如何避免吧:
對(duì)于class_addMethod這個(gè)方法之前與JSPatch結(jié)合較多括蝠,但是現(xiàn)在蘋果大有勢(shì)不兩立只勢(shì)鞠抑,如果風(fēng)聲能過過去我們?cè)僬f。(估計(jì)是過不去了忌警,但是我們?nèi)钥梢岳@一些彎彎來做熱修復(fù))搁拙。
我們進(jìn)行數(shù)據(jù)解析時(shí)秒梳,經(jīng)常碰到服務(wù)器會(huì)給我們返回NULL,導(dǎo)致crash箕速,然后你就會(huì)為你的容錯(cuò)機(jī)制不健全感到羞愧酪碘。
老板開會(huì)的時(shí)候又會(huì)像上次一樣想你投來你看這個(gè)菜比,這時(shí)候你馬上登陸統(tǒng)計(jì)平臺(tái)盐茎,就像我這么做線上異常分析兴垦,然后你發(fā)現(xiàn)服務(wù)器給了你一個(gè)NULL,頓時(shí)殺心四起字柠,不是說好不給NULL嗎 探越?不是說好做彼此的天使嗎?于是你看到了我的講解:因?yàn)榉?wù)器返回?cái)?shù)據(jù)中只有數(shù)字窑业,字符串钦幔, 數(shù)組和字典四種類型,所以我們只要在NULL找不到方法實(shí)現(xiàn)的時(shí)候向能響應(yīng)這個(gè)方法的對(duì)象進(jìn)行轉(zhuǎn)發(fā)就可以啦常柄。方法如下:
給`NSNull`創(chuàng)建一個(gè)分類鲤氢,并在.m中實(shí)現(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)啦!這個(gè)對(duì)象應(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)啦拐纱!這個(gè)對(duì)象應(yīng)該是是___NSDictionary0如果不實(shí)現(xiàn)這個(gè)分類則直接異常:*** Terminating app due to uncaughtexception'NSInvalidArgumentException', reason:'-[NSNull objectForKey:]: unrecognized selector sent to instance 0x10c8de180'
加入這個(gè)之后铜异,就再也不怕服務(wù)器不做你的天使啦!=占堋揍庄!
關(guān)于消息傳遞和消息轉(zhuǎn)發(fā)以及對(duì)應(yīng)的各種函數(shù)的用處并不能一言蔽之,主要還得看智商东抹,靈活把握才能用的得心應(yīng)手蚂子。
結(jié)語(yǔ)
Runtime就先到這里了,總共7篇缭黔,你和伍麗娟的愛情故事也算有頭有尾食茎,歡迎大家拍磚。端午節(jié)快樂A蠼鳌1鹩妗!搬磚總地址
舍一碗粥惧互,粥要立筷不倒
作者:CornBallast
鏈接:http://www.reibang.com/p/c5bdd6f7a68c
來源:簡(jiǎn)書
簡(jiǎn)書著作權(quán)歸作者所有哎媚,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處。