認(rèn)識(shí)Runtime運(yùn)行時(shí)機(jī)制

OC方法的本質(zhì)

首先了解OC方法的本質(zhì)到底是什么:

OC方法由兩個(gè)部分組成:

SEL: 方法編號(hào)(一本書(shū)的目錄編號(hào))
IMP: 方法實(shí)現(xiàn)炼七,是函數(shù)指針饼暑,指向函數(shù)(一本書(shū)的目錄頁(yè)碼期虾,頁(yè)碼指向?qū)?yīng)頁(yè)的內(nèi)容)

動(dòng)態(tài)綁定

簡(jiǎn)單舉個(gè)例子:一個(gè)Person類(lèi)的.m文件中不實(shí)現(xiàn)-(void)eat:(NSString *)
通過(guò)運(yùn)行時(shí)來(lái)動(dòng)態(tài)實(shí)現(xiàn)這個(gè)eat方法驴一,這個(gè)過(guò)程叫做 動(dòng)態(tài)綁定

 #import <objc/message.h>
 
 +(BOOL)resolveInstanceMethod:(SEL)sel{
    // 給類(lèi)添加eat方法慌植,IMP==eat
    if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
        //self:方法調(diào)用者甚牲,sel:方法編號(hào),eat:(IMP函數(shù)指針)方法實(shí)現(xiàn)
        class_addMethod(self, sel, eat, “”);
    }
    return [super resolveInstanceMethod:sel];
  }
  
 // 實(shí)現(xiàn)c函數(shù)eat()
 void eat(id self, SEL _cmd, NSString *objc){
    NSLog(@”我來(lái)了%@”, objc);
 }

OC方法調(diào)用 會(huì)傳遞兩個(gè) 隱式參數(shù) self, _cmd蝶柿,self 是方法的調(diào)用者丈钙,_cmd 是方法編號(hào);
OC的方法調(diào)用其實(shí)是 消息發(fā)送 (通過(guò)終端clang –rewrite-objc main.m,生成一個(gè).cpp文件交汤,可以看到.m文件的底層實(shí)現(xiàn)雏赦。)

Person *p = [[Person alloc]init];   
//[P eat:@”漢堡”]; 的底層就是objc_msgSend函數(shù)
objc_msgSend(P, @selector(eat:), @”漢堡”);

消息轉(zhuǎn)發(fā)

重定向

當(dāng)對(duì)象的方法簽名在頭文件中暴漏出來(lái),而在.m文件中忘記實(shí)現(xiàn)芙扎,一般程序會(huì)報(bào)運(yùn)行時(shí)錯(cuò)誤不識(shí)別的選擇器星岗,通過(guò)消息轉(zhuǎn)發(fā)可以改變這行為。

消息轉(zhuǎn)發(fā): 當(dāng)對(duì)象接收到與其 方法集 不匹配的消息時(shí)戒洼,通過(guò)消息轉(zhuǎn)發(fā)機(jī)制可以使對(duì)象執(zhí)行用戶預(yù)先定義的邏輯俏橘,如:將消息發(fā)送給能夠做出響應(yīng)的其他接收器(對(duì)象),或者將所有無(wú)法識(shí)別的消息都發(fā)送給同一個(gè)
接收器 再或者 默默的吞下消息(既不執(zhí)行處理過(guò)程也不使程序拋出運(yùn)行時(shí)錯(cuò)誤)圈浇。

還是上面的例子寥掐,在Dog類(lèi)中實(shí)現(xiàn)了eat方法例获,在Person類(lèi)中可以通過(guò) 消息轉(zhuǎn)發(fā) 讓Dog去相應(yīng)eat(我吃不了,dog你幫我吃吧)

//消息重定向
-(id)forwardingTargetForSelector:(SEL)aSelector{
    if ([_dog respondsToSelector:aSelector]) {  
        return _dog;     // 相當(dāng)于 [_dog performSelector:aSelector];  
    }   
    // 給nil發(fā)消息          
   return nil; 
}

方法簽名

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {  
    if(aSelector == @selector(eat:)) {  
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"]; // v@:@ (type encoding)  
    }  
    return nil;  
}  

- (void)forwardInvocation:(NSInvocation *)anInvocation {  
    if (anInvocation.selector == @selector(eat)) {  
        Dog *dog = [[Dog alloc] init];  
        [anInvocation invokeWithTarget:dog];  
    }  
} 

重寫(xiě)methodSignatureForSelector:和forwardInvocation:方法曹仗,將方法簽名,轉(zhuǎn)發(fā)給真正實(shí)現(xiàn)了該方法的目標(biāo)對(duì)象,讓其去調(diào)用已實(shí)現(xiàn)的方法蠕搜。

methodSignatureForSelector:的作用在于為另一個(gè)類(lèi)實(shí)現(xiàn)的消息創(chuàng)建一個(gè)有效的方法簽名怎茫,必須實(shí)現(xiàn),并且返回不為空的methodSignature妓灌,否則會(huì)crash
forwardInvocation:將選擇器轉(zhuǎn)發(fā)給一個(gè)真正實(shí)現(xiàn)了該消息的對(duì)象轨蛤。

1.forwardingTargetForSelector同為消息轉(zhuǎn)發(fā),但在實(shí)踐層面上有什么區(qū)別虫埂?何時(shí)可以考慮把消息下放到forwardInvocation階段轉(zhuǎn)發(fā)祥山?

forwardingTargetForSelector 僅支持一個(gè)對(duì)象的返回,也就是說(shuō)消息只能被轉(zhuǎn)發(fā)給一個(gè)對(duì)象掉伏。比如轉(zhuǎn)發(fā)給一個(gè)專(zhuān)門(mén)用于處理未識(shí)別的方法的處理類(lèi)缝呕。
forwardInvocation 可以將消息同時(shí)轉(zhuǎn)發(fā)給任意多個(gè)對(duì)象,如果你想執(zhí)行其他邏輯(如記錄日志并吞下該消息)斧散,可以考慮用 forwardInvocation

? 關(guān)于 signatureWithObjCTypes: 中的objcTypes供常,是OC的類(lèi)型編碼 Type Encodings【相關(guān)文檔鏈接
? 語(yǔ)法參照?qǐng)D:

image.png

前面提到OC方法調(diào)用 會(huì)傳遞兩個(gè) 隱式參數(shù) self, _cmd,self 是方法的調(diào)用者鸡捐,_cmd 是方法編號(hào)栈暇,指向方法本身。

例如:-(void)eat:(NSString *)food;實(shí)際上有三個(gè)參數(shù):self, _cmd和food箍镜。

eat: 轉(zhuǎn)ObjcTypes為:
"返回值類(lèi)型 第一參數(shù) 第二參數(shù) [第三參數(shù)...]"
如果沒(méi)有返回值用v源祈,如果有用@代替id類(lèi)型,
第一二參數(shù)是必須存在的色迂,即 id 類(lèi)型的 self香缺,和 SEL 類(lèi)型的 _cmd,第三參數(shù)是用戶自定義的參數(shù)脚草,可有可無(wú)

例如: "v@:@" : void id類(lèi)型的self SEL類(lèi)型的_cmd 自定義參數(shù)赫悄;
"@@:" :id類(lèi)型的返回值 id類(lèi)型的self SEL類(lèi)型的_cmd

因此我們可以調(diào)用[anInvocation getArgument: atIndex:] 獲取指定的參數(shù)值

Runtime應(yīng)用場(chǎng)景—HOOK(鉤子)

HOOK,方法欺騙

直接上例子:

/*當(dāng)url中含有中文時(shí)(需要轉(zhuǎn)碼)馏慨,request還是能創(chuàng)建埂淮,但是此時(shí)  
  request中的url為空,request的創(chuàng)建方法沒(méi)有檢測(cè)url為空的情況写隶,  
  很容易出現(xiàn)難以定位的bug(Swift中有可選類(lèi)型倔撞,可以避免這問(wèn)題)。
*/
NSURL *url = [NSURL URLWithString:@”http://www.baidu.com/中文”];
NSURLRequest *request = [NSURLRequest requestWithURL:url];

解決:①用 category 創(chuàng)建分類(lèi) NSURL+HOOKXW_URLWithString 方法慕趴,將用到 URLWithString 的地方替換成我們自己的方法 XW_URLWithString 痪蝇, XW_URLWithString 保留了 URLWithString 原本內(nèi)部創(chuàng)建 url 的方式鄙陡,添加 url 是否為空的判斷。
不足:每次都要去導(dǎo)入頭文件躏啰,每次都要去替換項(xiàng)目中原本的urlWithString方法趁矾,比較麻煩;

②利用runtime運(yùn)行時(shí)给僵,改變方法調(diào)用的順序毫捣。
OC發(fā)送 URLWithString 消息會(huì)對(duì)應(yīng)的的去找這個(gè)方法的實(shí)現(xiàn),用運(yùn)行時(shí)可以去改變這種一一對(duì)應(yīng)的關(guān)系帝际,
只要HOOKURLWithString 這個(gè)方法的調(diào)用蔓同,當(dāng)發(fā)送 URLWithString(SEL)消息時(shí),讓它去找 XW_URLWithString(IMP)這個(gè)實(shí)現(xiàn)蹲诀。

NSURL+HOOK.m 文件中在 +(void)load 方法中下鉤子HOOK斑粱,以交換方法的IMP實(shí)現(xiàn)。

#import<objc/runtime.h>

+(void)load {
    //獲取method
    Method URLWithStr = class_getClassMethod(self, @selector(URLWithString:)); 
    Method XWURLWithStr = class_getClassMethod(self, @selector(XW_URLWithString:));
    //交換方法的IMP
    method_exchangeImplementations(URLWithStr, XWURLWithStr);
}

外界不需要導(dǎo)入 NSURL+HOOK.h 脯爪,也不需要修改 URLWithStringXW_URLWithString 就能直接把 URLWithString 方法實(shí)現(xiàn)替換则北。

XW_URLWithString 實(shí)現(xiàn)如下:

+(void) XW_URLWithString:(NSString*)URLString{
    // 保留系統(tǒng)原本的實(shí)現(xiàn),實(shí)現(xiàn)交換后這里不能用URLWithString披粟,否則會(huì)遞歸
    NSURL *url = [NSURL XW_URLWithString:URLString];
    if(url == nil){
        NSLog(@"空了");
    }
    return url;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咒锻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子守屉,更是在濱河造成了極大的恐慌惑艇,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拇泛,死亡現(xiàn)場(chǎng)離奇詭異滨巴,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)俺叭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)恭取,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人熄守,你說(shuō)我怎么就攤上這事蜈垮。” “怎么了裕照?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵攒发,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我晋南,道長(zhǎng)惠猿,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任负间,我火速辦了婚禮偶妖,結(jié)果婚禮上姜凄,老公的妹妹穿的比我還像新娘。我一直安慰自己趾访,他們只是感情好态秧,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著扼鞋,像睡著了一般屿聋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上藏鹊,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音转锈,去河邊找鬼盘寡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛撮慨,可吹牛的內(nèi)容都是我干的竿痰。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼砌溺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼影涉!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起规伐,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蟹倾,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后猖闪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體鲜棠,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年培慌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了豁陆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吵护,死狀恐怖盒音,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情馅而,我是刑警寧澤祥诽,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站用爪,受9級(jí)特大地震影響原押,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜偎血,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一诸衔、第九天 我趴在偏房一處隱蔽的房頂上張望盯漂。 院中可真熱鬧,春花似錦笨农、人聲如沸就缆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)竭宰。三九已至,卻和暖如春份招,著一層夾襖步出監(jiān)牢的瞬間切揭,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工锁摔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留廓旬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓谐腰,卻偏偏與公主長(zhǎng)得像孕豹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子十气,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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