Runtime奇技淫巧之class_addMethod以及消息轉(zhuǎn)發(fā)機(jī)制

上回書說道奸攻,你和伍麗娟已經(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)并注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末喊儡,一起剝皮案震驚了整個(gè)濱河市拨与,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌艾猜,老刑警劉巖买喧,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捻悯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡淤毛,警方通過查閱死者的電腦和手機(jī)今缚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钱床,“玉大人荚斯,你說我怎么就攤上這事〔榕疲” “怎么了事期?”我有些...
    開封第一講書人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)纸颜。 經(jīng)常有香客問我兽泣,道長(zhǎng),這世上最難降的妖魔是什么胁孙? 我笑而不...
    開封第一講書人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任唠倦,我火速辦了婚禮,結(jié)果婚禮上涮较,老公的妹妹穿的比我還像新娘稠鼻。我一直安慰自己,他們只是感情好狂票,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開白布候齿。 她就那樣靜靜地躺著,像睡著了一般闺属。 火紅的嫁衣襯著肌膚如雪慌盯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,785評(píng)論 1 314
  • 那天掂器,我揣著相機(jī)與錄音亚皂,去河邊找鬼。 笑死国瓮,一個(gè)胖子當(dāng)著我的面吹牛灭必,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乃摹,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼厂财,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了峡懈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤与斤,失蹤者是張志新(化名)和其女友劉穎肪康,沒想到半個(gè)月后荚恶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡磷支,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年谒撼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雾狈。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡廓潜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出善榛,到底是詐尸還是另有隱情辩蛋,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布移盆,位于F島的核電站悼院,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏咒循。R本人自食惡果不足惜据途,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叙甸。 院中可真熱鬧颖医,春花似錦、人聲如沸裆蒸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)光戈。三九已至哪痰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間久妆,已是汗流浹背晌杰。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留筷弦,地道東北人肋演。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像烂琴,于是被迫代替她去往敵國(guó)和親爹殊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容