淺談runtime的幾個應用場景

前言

寫這篇文章也是因為我一個朋友覺得學runtime沒啥用處,平時開發(fā)也用不到外盯,學了也就是為了應付面試而已饱苟,我不這么覺得狼渊,所以今天討論一下runtime這個黑科技能搞點啥東西出來囤锉,如有錯誤希望大家積極指正官地,我們一起進步驱入。

搞IOS的同學肯定都知道有這個東西,但又僅僅知道有這個東西的存在莺褒,oc的運行都得靠他遵岩,黑科技可遠觀而不可褻玩焉尘执。說白了runtime就是oc運行的一些機制宴凉,oc是一門動態(tài)語言弥锄,它的動態(tài)特性由runtime體現(xiàn)出來。今天主要研究四個問題饭庞。本文的demo稍后上傳但绕。

1.給分類添加屬性

2.消息轉(zhuǎn)發(fā)機制

3.動態(tài)交換方法的實現(xiàn)

4.手動實現(xiàn)多繼承(oc本身是不支持多繼承的)

在研究runtime之前先了解一下兩個經(jīng)常出現(xiàn)在runtime里的知識點

IMP

這是一個函數(shù)指針,顧名思義它保存的是一個函數(shù)的內(nèi)存地址六孵,通過IMP系統(tǒng)就可以找到這個函數(shù)然后去執(zhí)行它劫窒,也可以說說它指向一個方法的實現(xiàn)主巍。

SEL

這個類型我們經(jīng)常遇到比如[self performSelector:@selector(click) withObject:nil];這個方法里的:@selector(click)就是SEL類型孕索。SEL是方法編號搞旭,會根據(jù)方法的名字生成一個用來區(qū)分這個方法的唯一的一個ID肄渗,只要方法名稱相同翎嫡,那么它們的ID就是相同的。是對方法的一個包裝翁垂,它本身并不指向方法的實現(xiàn)沿猜,系統(tǒng)在內(nèi)部通過SEL找到對應的IMP啼肩,然后去執(zhí)行這個方法祈坠。

Method

在objc.h中, Method 的定義如下:

/// An opaque type that represents a method in a class definition.typedefstructobjc_method*Method;structobjc_method{SEL method_name? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;char*method_types? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;? ? IMP method_imp? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;}

Method = SEL + IMP + method_types,相當于在SEL和IMP之間建立了一個映射阁猜。

接下來進入正題蹋艺,我們都知道Category(分類)里面是不能添加屬性和成員變量的捎谨,為什么涛救?比如你在Category里申明了一個屬性@property(nonatomic,copy)NSString * userName;這個時候你看.m文件會有一個警告检吆,告訴我們要實現(xiàn)它的set方法咧栗,這就是原因所在致板,在分類里面添加屬性,系統(tǒng)只會申明set和get方法素征,并不會實現(xiàn)他們萝挤,并且也不會生成一個帶下劃線的成員變量御毅,也就是說在內(nèi)部我們沒辦法操作這個屬性。既然已經(jīng)知道問題了怜珍,那就可以開始解決的問題了端蛆,我們只要能在它的內(nèi)部自己實現(xiàn)set和get方法,然后可以操作這個屬性問題不就解決了酥泛,這個時候我們只要用到objc_setAssociatedObject和objc_getAssociatedObject這兩個函數(shù)就可以了今豆。首先我們創(chuàng)建一個UIView的分類Test嫌拣。然后在.m里實現(xiàn)set和get方法呆躲,代碼如下异逐。


這里面用到的知識點就是動態(tài)的關聯(lián)屬性

objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,

? ? ? ? ? ? ? ? ? ? ? ?? id _Nullable value, objc_AssociationPolicy policy)

可以看到這個函數(shù)需要傳入四個函數(shù)分別是被綁定的對象,鍵插掂,綁定的值和策略灰瞻,這個策略有幾個選項

OBJC_ASSOCIATION_ASSIGN = 0,

OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,

OBJC_ASSOCIATION_COPY_NONATOMIC = 3,?

OBJC_ASSOCIATION_RETAIN = 01401,? ? ?

OBJC_ASSOCIATION_COPY = 01403 類似于assign,retain,copy。

而這個鍵一般用靜態(tài)字符常量辅甥,用來保存和獲取所關聯(lián)的值酝润。這個函數(shù)相當于我們的set方法。同理objc_getAssociatedObject這個函數(shù)相當于我們的get方法肆氓。通過動態(tài)的關聯(lián)屬性袍祖,我們就可以變相的操作這個userName屬性了底瓣。

接下來我們來看看消息轉(zhuǎn)發(fā)是怎么玩的

首先我們定義一個類叫MessageForward谢揪,然后申明一個方法-(void)sendMessage;

//創(chuàng)建一個對象

MessageForward * message = [[MessageForward alloc]init];

[message sendMessage];//調(diào)用方法

接下來會發(fā)生什么呢?

編譯器將代碼[message sendMessage];轉(zhuǎn)化為objc_msgSend(object, @selector (makeText));捐凭,在objc_msgSend函數(shù)中拨扶。首先通過message的isa指針找到message對應的class。在Class中先去cache中 通過SEL(方法編號)查找對應的method茁肠,若 cache中未找到患民。再去methodList中查找,若methodlist中未找到垦梆,則去superClass中查找匹颤。若能找到,則將method加 入到cache中托猩,以方便下次查找印蓖,并通過method中的函數(shù)指針跳轉(zhuǎn)到對應的函數(shù)中去執(zhí)行。這是正常流程京腥,如果經(jīng)常上面一系列騷操作都沒有找到這個方法的實現(xiàn)赦肃,那么。公浪。他宛。。欠气。厅各。。crash预柒,game over队塘。然后報一個無法識別方法的錯誤琐鲁。如果不想crash,那么我們需要去實現(xiàn)整個消息轉(zhuǎn)發(fā)流程人灼。

第一步動 態(tài)方法解析

查找接收者所屬的類围段,看其是否能動態(tài)添加方法,以處理這個“未知的方法”投放。我們需要實現(xiàn)如下方法

這個方法是NSObject里面奈泪,留給我們來動態(tài)的解析方法

根據(jù)sel得到對應的方法的名字,然后進行判斷灸芳,如果methodNam為 sendMessage涝桅,那么調(diào)用

class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,

? ? ? ? ? ? ? ? const char * _Nullable types)函數(shù),添加方法烙样,需要四個參數(shù)冯遂,第一個是誰添加到哪個類,第二個要添加方法的編號谒获,第三個參數(shù)是一個函數(shù)指針蛤肌,方法的實現(xiàn),第四個參數(shù)用來描述這個方法的類型批狱,比如返回值類型裸准,各個參數(shù)的類型

’v‘ :void類型,第一個字符代表返回值類型

’@‘ : 一個id類型的對象赔硫,第一個參數(shù)類型

’:‘ : 對應SEL炒俱,第二個參數(shù)類型

下來及就是實現(xiàn)這個方法


這里面有兩個隱藏參數(shù) self和_cmd需要我們寫上,如果有別的自定義參數(shù)可以跟著后面寫爪膊。這個時候再運行就不會蹦了权悟,而是輸出我最帥三個字。如果沒有動態(tài)添加方法那么將會進入下一步找備用推盛。

找備用接受者(也就是備胎峦阁,專門用來接鍋的,心疼一分鐘)

首先我們先準備一個接鍋俠小槐,創(chuàng)建一個類Reserve

申明sendMessage方法拇派,并實現(xiàn)它(申不申明都行)


根據(jù)aSelector得到對應的方法的名字,然后進行判斷凿跳,如果methodNam為 sendMessage件豌,那么調(diào)用 [[Reserve alloc]init];然后將這個對象返回,也就是讓它去響應這個消息控嗜,這個時候我們運行茧彤,程序會走到Reserve這個類當中的sendMessage方法,這樣找備胎的就完成了疆栏。備胎這個詞真的很讓人心痛曾掂。惫谤。。珠洗。溜歪。。许蓖。蝴猪。。膊爪。

如果上面兩步騷操作都失效了自阱,那就只能用最后的大招了“消息轉(zhuǎn)發(fā)”,平時玩游戲的小伙伴都知道大招和普通的技能相比除了更牛x以外還有一個令人頭疼的后遺癥米酬,CD時間太長沛豌,在這里也就對應的程序的消耗更大,越往后面走消耗越大赃额〖优桑看代碼。


首先調(diào)用methodSignatureForSelector這個方法爬早,對方法進行簽名哼丈,獲取到方法的類型信息启妹,比如返回值類型筛严,各個參數(shù)類型等。然后調(diào)用-(void)forwardInvocation:(NSInvocation *)anInvocation饶米,將消息進行轉(zhuǎn)發(fā)給Reserve的對象r桨啃,讓這個r去響應這個消息,這個時候再運行檬输,依然完美的的解決了問題照瘾。

但是。丧慈。析命。。逃默。鹃愤。。完域。软吐。。這個連大招都失靈了會怎么樣吟税,一樣crash凹耙,這就很讓人尷尬姿现,我一頓操作猛如虎,一看戰(zhàn)績0-5肖抱,為了避免這種尷尬的情況我們還有最后一手底牌备典,請看代碼

-(void)doesNotRecognizeSelector:(SEL)aSelector{

? ? NSLog(@"消息無法處理");

};

這個方法就是最后的最后都失效了調(diào)用的意述,至少保證程序不會crash啊熊经,也不枉擼了那么多代碼。消息轉(zhuǎn)發(fā)機制到這里就算結(jié)束了欲险。

接下來我們就要動態(tài)交換方法的實現(xiàn)

首先我們創(chuàng)建一個UIView的分類Exchange然后如下代碼


所謂交換方法實際上交換兩個方法的實現(xiàn)镐依,比如有a,b兩個方法天试,將他們交換槐壳,那么調(diào)用a方法會去執(zhí)行b方法的實現(xiàn),反之調(diào)用b方法會去執(zhí)行a方法的實現(xiàn)喜每。這個很簡單务唐,我的demo里有,可以下載玩一下带兜。

接下類就是重頭戲了枫笛,多繼承(oc本身是不支持多繼承的)

記得在很久以前我去面試,面試官問我oc支持多繼承嗎刚照,當時雖然我是一個剛畢業(yè)的菜鳥刑巧,但這點常識還是有的,立刻回答了沒有(畢竟我也是背過不少面試題的无畔,我猜測接下來他會問啊楚,那oc協(xié)議可以多繼承嗎,哈哈浑彰,都是套路恭理,辛虧我早有準備),正當我胸有成竹的時候郭变,他說現(xiàn)在我就想實現(xiàn)多繼承你說怎么辦颜价。窩草,這么傲嬌诉濒,你想實現(xiàn)就實現(xiàn)周伦,蘋果不要面子的啊,對于這個問題當時我覺得不可能啊循诉,從來沒聽說過横辆,所以就回答了不知道,果然這場面試就這樣gg了”吩椋回去之后百度了一下困肩,窩草,居然真的有這種騷操作脆侮,今天就和大家分享一下锌畸。

我們先聊聊大致的思路

一個類既繼承A類,又繼承B類靖避,也就說這兩個類方法它都可以用潭枣,有什么辦法可以同時調(diào)用兩個類的方法呢,答案其實上面已經(jīng)說了幻捏,協(xié)議盆犁,oc是支持協(xié)議的多繼承的,我們把方法的申明寫在協(xié)議里篡九,然后讓這個類同時繼承A類和B類的協(xié)議谐岁,這樣就可以調(diào)用這兩個類的方法了,調(diào)用的時候編譯器不會報錯榛臼,但運行時還是會crash伊佃,因為在這個類內(nèi)部找不到方法的實現(xiàn),方法的實現(xiàn)是在A類和B類里沛善,那么怎么辦航揉,其實答案也在上面了,我們可以通過消息轉(zhuǎn)發(fā)機智金刁,將消息轉(zhuǎn)發(fā)到A類和B類里帅涂,這樣就解決得了找不到方法實現(xiàn)的問題,首先創(chuàng)建兩個類Man和Women胀葱,定義各自的協(xié)議代碼如下漠秋。


然后創(chuàng)建一個Children類,同時集成上面兩個協(xié)議抵屿。不過這個Children類的基類不是NSObject而是NSProxy,這是一個虛類捅位,沒有構(gòu)造方法轧葛,需要自己手動實現(xiàn)init方法。相當于輕量級的NSObject艇搀,主要實現(xiàn)-(nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel和-(void)forwardInvocation:(NSInvocation *)invocation兩個方法進行消息轉(zhuǎn)發(fā)尿扯,上面已經(jīng)說了這兩個方法。首先我們先寫個init方法焰雕。


第一步創(chuàng)建一個字典然后調(diào)用-(void)registerMethodsWithTarget:(id)target方法


這個方法是干嘛的衷笋,首先獲取到調(diào)用者所在類的所有實例方法,然后進行遍歷矩屁,然后以調(diào)用者為值辟宗,方法名作為鍵爵赵,添加到_methodsMap這個字典里,后邊會用的泊脐,先不說空幻。接下來進行消息轉(zhuǎn)發(fā)


首先調(diào)用-(nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel這個方法,返回方法簽名容客。

然后調(diào)用-(void)forwardInvocation:(NSInvocation *)invocation進行消息轉(zhuǎn)發(fā)秕铛,首先獲取到sel,然后根據(jù)sel獲取到methodName缩挑,然后真?zhèn)€methodName到_methodsMap這個字典去取值但两,得到目標target,就是我們再init方法里創(chuàng)建一個Man對象和一個Women對象供置,然后進行判斷如果target響應這個方法镜遣,將消息轉(zhuǎn)發(fā)給這個target,結(jié)束士袄。

這些就是runtime的幾個簡單應用悲关,還有好多好多騷操作,不過需要我們我們慢慢地去發(fā)現(xiàn)娄柳,runtime對于新手來說還是不太理解和操作的寓辱,用不好就是天大的bug,建議大家用的時候一定要小心一點赤拒,因為它是運行時候的機制秫筏,所以在編譯階段編譯器是發(fā)現(xiàn)不了錯誤的,出bug了也很難定位挎挖,雙刃劍吧这敬,用好的吊炸天,用不好蕉朵,估計就要上天臺了崔涂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市始衅,隨后出現(xiàn)的幾起案子冷蚂,更是在濱河造成了極大的恐慌,老刑警劉巖汛闸,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蝙茶,死亡現(xiàn)場離奇詭異,居然都是意外死亡诸老,警方通過查閱死者的電腦和手機隆夯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蹄衷,你說我怎么就攤上這事忧额。” “怎么了宦芦?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵宙址,是天一觀的道長。 經(jīng)常有香客問我调卑,道長抡砂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任恬涧,我火速辦了婚禮注益,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘溯捆。我一直安慰自己丑搔,他們只是感情好,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布提揍。 她就那樣靜靜地躺著啤月,像睡著了一般。 火紅的嫁衣襯著肌膚如雪劳跃。 梳的紋絲不亂的頭發(fā)上谎仲,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機與錄音刨仑,去河邊找鬼郑诺。 笑死,一個胖子當著我的面吹牛杉武,可吹牛的內(nèi)容都是我干的辙诞。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼轻抱,長吁一口氣:“原來是場噩夢啊……” “哼飞涂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起十拣,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤封拧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后夭问,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡曹铃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年缰趋,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡秘血,死狀恐怖味抖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情灰粮,我是刑警寧澤仔涩,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站粘舟,受9級特大地震影響熔脂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜柑肴,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一霞揉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晰骑,春花似錦适秩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至抚官,卻和暖如春扬跋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背耗式。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工胁住, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人刊咳。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓彪见,卻偏偏與公主長得像,于是被迫代替她去往敵國和親娱挨。 傳聞我的和親對象是個殘疾皇子余指,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

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