來(lái)源:iOS 模塊注解—「Runtime面試刻坊、工作」看我就 ?? 了 ^_^. - 簡(jiǎn)書(shū)
runtime(簡(jiǎn)稱(chēng)運(yùn)行時(shí))枷恕,是一套 純C(C和匯編寫(xiě)的) 的API。 ?是運(yùn)行時(shí)機(jī)制谭胚,也就是在運(yùn)行時(shí)候的一些機(jī)制徐块,其中最主要的是消息機(jī)制。消息機(jī)制原理:對(duì)象根據(jù)方法編號(hào)SEL去映射表查找對(duì)應(yīng)的方法實(shí)現(xiàn)灾而。
每一個(gè) OC 的方法胡控,底層必然有一個(gè)與之對(duì)應(yīng)的?runtime?方法。任何方法調(diào)用本質(zhì):就是發(fā)送一個(gè)消息(用?runtime發(fā)送消息旁趟,OC 底層實(shí)現(xiàn)通過(guò)?runtime?實(shí)現(xiàn))昼激。
對(duì)于 C 語(yǔ)言,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù)锡搜。
OC在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù)橙困,只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱(chēng)找到對(duì)應(yīng)的函數(shù)來(lái)調(diào)用,在編譯階段耕餐,OC 可以?調(diào)用任何函數(shù)凡傅,即使這個(gè)函數(shù)并未實(shí)現(xiàn),只要聲明過(guò)就不會(huì)報(bào)錯(cuò)蛾方,只有當(dāng)運(yùn)行的時(shí)候才會(huì)報(bào)錯(cuò)像捶,這是因?yàn)镺C是運(yùn)行時(shí)動(dòng)態(tài)調(diào)用的。而 C 語(yǔ)言?調(diào)用未實(shí)現(xiàn)的函數(shù)?就會(huì)報(bào)錯(cuò)桩砰。
runtime 方法調(diào)用流程「消息機(jī)制」
面試:消息機(jī)制方法調(diào)用流程
怎么去調(diào)用eat方法拓春,對(duì)象方法:(保存到類(lèi)對(duì)象的方法列表) ,類(lèi)方法:(保存到元類(lèi)(Meta Class)中方法列表)亚隅。
1.OC 在向一個(gè)對(duì)象發(fā)送消息時(shí)硼莽,runtime庫(kù)會(huì)根據(jù)對(duì)象的isa指針找到該對(duì)象對(duì)應(yīng)的類(lèi)或其父類(lèi)中查找方法。煮纵。
2.注冊(cè)方法編號(hào)(這里用方法編號(hào)的好處懂鸵,可以快速查找)。
3.根據(jù)方法編號(hào)去查找對(duì)應(yīng)方法行疏。
4.找到只是最終函數(shù)實(shí)現(xiàn)地址匆光,根據(jù)地址去方法區(qū)調(diào)用對(duì)應(yīng)函數(shù)。
補(bǔ)充:一個(gè)objc對(duì)象的isa的指針指向什么酿联?有什么作用终息?
每一個(gè)對(duì)象內(nèi)部都有一個(gè)isa指針,這個(gè)指針是指向它的真實(shí)類(lèi)型贞让,根據(jù)這個(gè)指針就能知道將來(lái)調(diào)用哪個(gè)類(lèi)的方法周崭。
runtime 常見(jiàn)作用
1.交換方法/添加方法
// 1.獲取 imageNamed方法地址
// class_getClassMethod(獲取某個(gè)類(lèi)的方法)
Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
// 2.獲取 ln_imageNamed方法地址
Method ln_imageNamedMethod = class_getClassMethod(self, @selector(ln_imageNamed:));
// 3.交換方法地址,相當(dāng)于交換實(shí)現(xiàn)方式;「method_exchangeImplementations 交換兩個(gè)方法的實(shí)現(xiàn)」
method_exchangeImplementations(imageNamedMethod, ln_imageNamedMethod);?
2.為分類(lèi)添加屬性
- (void)setName:(NSString *)name {
// objc_setAssociatedObject(將某個(gè)值跟某個(gè)對(duì)象關(guān)聯(lián)起來(lái)喳张,將某個(gè)值存儲(chǔ)到某個(gè)對(duì)象中)
// object:給哪個(gè)對(duì)象添加屬性 // key:屬性名稱(chēng) // value:屬性值 // policy:保存策略 objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}
- (NSString *)name {
return objc_getAssociatedObject(self, @"name");
}?
3.字典轉(zhuǎn)模型
// Runtime:根據(jù)模型中屬性,去字典中取出對(duì)應(yīng)的value給模型屬性賦值// 思路:遍歷模型中所有屬性->使用運(yùn)行時(shí)
+ (instancetype)modelWithDict:(NSDictionary *)dict{
// 1.創(chuàng)建對(duì)應(yīng)的對(duì)象
id objc = [[self alloc] init];
// 2.利用runtime給對(duì)象中的屬性賦值 /** class_copyIvarList: 獲取類(lèi)中的所有成員變量
Ivar:成員變量 第一個(gè)參數(shù):表示獲取哪個(gè)類(lèi)中的成員變量
第二個(gè)參數(shù):表示這個(gè)類(lèi)有多少成員變量续镇,傳入一個(gè)Int變量地址,會(huì)自動(dòng)給這個(gè)變量賦值 返回值Ivar *:指的是一個(gè)ivar數(shù)組销部,會(huì)把所有成員屬性放在一個(gè)數(shù)組中摸航,通過(guò)返回的數(shù)組就能全部獲取到。 count: 成員變量個(gè)數(shù) */ unsigned int count = 0; // 獲取類(lèi)中的所有成員變量
Ivar *ivarList = class_copyIvarList(self, &count);
// 遍歷所有成員變量
for (int i = 0; i < count; i++) {
// 根據(jù)角標(biāo)柴墩,從數(shù)組取出對(duì)應(yīng)的成員變量
Ivar ivar = ivarList[i];
// 獲取成員變量名字
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 處理成員變量名->字典中的key(去掉 _ ,從第一個(gè)角標(biāo)開(kāi)始截取)
NSString *key = [ivarName substringFromIndex:1];
// 根據(jù)成員屬性名去字典中查找對(duì)應(yīng)的
value id value = dict[key];
// 【如果模型屬性數(shù)量大于字典鍵值對(duì)數(shù)理忙厌,模型屬性會(huì)被賦值為nil】
// 而報(bào)錯(cuò) (could not set nil as the value for the key age.)
if (value) {
// 給模型中屬性賦值
[objc setValue:value forKey:key];
} }
return objc;
}?
原因:Ivar:成員變量,以下劃線開(kāi)頭,Property 屬性
獲取類(lèi)里面屬性?class_copyPropertyList
獲取類(lèi)中的所有成員變量?class_copyIvarList
動(dòng)態(tài)添加方法
【iOS】消息轉(zhuǎn)發(fā)機(jī)制 - 簡(jiǎn)書(shū)
應(yīng)用場(chǎng)景:如果一個(gè)類(lèi)方法非常多江咳,加載類(lèi)到內(nèi)存的時(shí)候也比較耗費(fèi)資源逢净,需要給每個(gè)方法生成映射表,可以使用動(dòng)態(tài)給某個(gè)類(lèi)歼指,添加方法解決爹土。
注解:OC 中我們很習(xí)慣的會(huì)用懶加載,當(dāng)用到的時(shí)候才去加載它踩身,但是實(shí)際上只要一個(gè)類(lèi)實(shí)現(xiàn)了某個(gè)方法胀茵,就會(huì)被加載進(jìn)內(nèi)存。當(dāng)我們不想加載這么多方法的時(shí)候挟阻,就會(huì)使用到 runtime 動(dòng)態(tài)的添加方法琼娘。
需求:runtime 動(dòng)態(tài)添加方法處理調(diào)用一個(gè)未實(shí)現(xiàn)的方法 和 去除報(bào)錯(cuò)峭弟。
+ (BOOL)resolveInstanceMethod:(SEL)sel
- (id)forwardingTargetForSelector:(SEL)aSelector
經(jīng)歷了前兩步,還是無(wú)法處理消息脱拼,那么就會(huì)做最后的嘗試瞒瘸,先調(diào)用methodSignatureForSelector:獲取方法簽名,用簽名生成invocation然后再調(diào)用forwardInvocation:進(jìn)行處理熄浓,這一步的處理可以直接轉(zhuǎn)發(fā)給其它對(duì)象情臭,即和第二步的效果等效,但是很少有人這么干赌蔑,因?yàn)橄⑻幚碓娇亢蟾┰冢捅硎咎幚硐⒌某杀驹酱螅阅艿拈_(kāi)銷(xiāo)就越大娃惯。所以跷乐,在這種方式下,會(huì)改變消息內(nèi)容趾浅,比如增加參數(shù)劈猿,改變選擇子等等。
消息轉(zhuǎn)發(fā)給其他對(duì)象時(shí)潮孽,可以穿多個(gè)參數(shù)
問(wèn)題: objc在向一個(gè)對(duì)象發(fā)送消息時(shí)揪荣,發(fā)生了什么?
解答: 根據(jù)對(duì)象的 isa 指針找到類(lèi)對(duì)象 id往史,在查詢(xún)類(lèi)對(duì)象里面的 methodLists 方法函數(shù)列表仗颈,如果沒(méi)有在好到,在沿著 superClass ,尋找父類(lèi)椎例,再在父類(lèi) methodLists 方法列表里面查詢(xún)挨决,最終找到 SEL ,根據(jù) id 和 SEL 確認(rèn) IMP(指針函數(shù)),在發(fā)送消息;
03
問(wèn)題: 什么時(shí)候會(huì)報(bào)unrecognized selector錯(cuò)誤订歪?iOS有哪些機(jī)制來(lái)避免走到這一步脖祈?
解答: 當(dāng)發(fā)送消息的時(shí)候,我們會(huì)根據(jù)類(lèi)里面的 methodLists 列表去查詢(xún)我們要?jiǎng)佑玫腟EL刷晋,當(dāng)查詢(xún)不到的時(shí)候盖高,我們會(huì)一直沿著父類(lèi)查詢(xún),當(dāng)最終查詢(xún)不到的時(shí)候我們會(huì)報(bào) unrecognized selector 錯(cuò)誤眼虱,當(dāng)系統(tǒng)查詢(xún)不到方法的時(shí)候喻奥,會(huì)調(diào)用 +(BOOL)resolveInstanceMethod:(SEL)sel 動(dòng)態(tài)解釋的方法來(lái)給我一次機(jī)會(huì)來(lái)添加,調(diào)用不到的方法捏悬∽膊希或者我們可以再次使用 -(id)forwardingTargetForSelector:(SEL)aSelector 重定向的方法來(lái)告訴系統(tǒng),該調(diào)用什么方法过牙,一來(lái)保證不會(huì)崩潰甥厦。
經(jīng)歷了前兩步纺铭,還是無(wú)法處理消息,那么就會(huì)做最后的嘗試刀疙,先調(diào)用methodSignatureForSelector:獲取方法簽名彤蔽,然后再調(diào)用forwardInvocation:進(jìn)行處理,這一步的處理可以直接轉(zhuǎn)發(fā)給其它對(duì)象庙洼,即和第二步的效果等效,但是很少有人這么干镊辕,因?yàn)橄⑻幚碓娇亢笥凸唬捅硎咎幚硐⒌某杀驹酱螅阅艿拈_(kāi)銷(xiāo)就越大征懈。所以石咬,在這種方式下,會(huì)改變消息內(nèi)容卖哎,比如增加參數(shù)鬼悠,改變選擇子等等。
?鏈接:http://www.reibang.com/p/5dd7703ce2fc
04
問(wèn)題: 能否向編譯后得到的類(lèi)中增加實(shí)例變量亏娜?能否向運(yùn)行時(shí)創(chuàng)建的類(lèi)中添加實(shí)例變量焕窝?為什么?
解答: 1维贺、不能向編譯后得到的類(lèi)增加實(shí)例變量 2它掂、能向運(yùn)行時(shí)創(chuàng)建的類(lèi)中添加實(shí)例變量∷萜【解釋】:1. 編譯后的類(lèi)已經(jīng)注冊(cè)在 runtime 中,類(lèi)結(jié)構(gòu)體中的 objc_ivar_list 實(shí)例變量的鏈表和 instance_size 實(shí)例變量的內(nèi)存大小已經(jīng)確定虐秋,runtime會(huì)調(diào)用 class_setvarlayout 或 class_setWeaklvarLayout 來(lái)處理strong weak 引用.所以不能向存在的類(lèi)中添加實(shí)例變量。2. 運(yùn)行時(shí)創(chuàng)建的類(lèi)是可以添加實(shí)例變量垃沦,調(diào)用class_addIvar函數(shù). 但是的在調(diào)用 objc_allocateClassPair 之后客给,objc_registerClassPair 之前,原因同上.
05
問(wèn)題: runtime如何實(shí)現(xiàn)weak變量的自動(dòng)置nil?
解答: runtime 對(duì)注冊(cè)的類(lèi)肢簿, 會(huì)進(jìn)行布局靶剑,對(duì)于 weak 對(duì)象會(huì)放入一個(gè) hash 表中。 用 weak 指向的對(duì)象內(nèi)存地址作為 key池充,當(dāng)此對(duì)象的引用計(jì)數(shù)為0的時(shí)候會(huì) dealloc抬虽,假如 weak 指向的對(duì)象內(nèi)存地址是a,那么就會(huì)以a為鍵纵菌, 在這個(gè) weak 表中搜索阐污,找到所有以a為鍵的 weak 對(duì)象,從而設(shè)置為 nil咱圆。
06
問(wèn)題: 給類(lèi)添加一個(gè)屬性后笛辟,在類(lèi)結(jié)構(gòu)體里哪些元素會(huì)發(fā)生變化功氨?
解答: instance_size :實(shí)例的內(nèi)存大小手幢;objc_ivar_list *ivars:屬性列表
一捷凄、oc語(yǔ)言的特性
OC做為一門(mén)面向?qū)ο笳Z(yǔ)言,具有面向?qū)ο蟮恼Z(yǔ)言特性围来,如封裝跺涤、繼承、多態(tài)监透。他具有靜態(tài)語(yǔ)言的特性(如C++)桶错,又有動(dòng)態(tài)語(yǔ)言的效率(動(dòng)態(tài)綁定、動(dòng)態(tài)加載等)胀蛮。
OC的動(dòng)態(tài)特性表現(xiàn)為了三個(gè)方面:動(dòng)態(tài)類(lèi)型院刁、動(dòng)態(tài)綁定、動(dòng)態(tài)加載粪狼。之所以叫做動(dòng)態(tài)退腥,是因?yàn)楸仨毜竭\(yùn)行時(shí)(run time)才會(huì)做一些事情。
(1)動(dòng)態(tài)類(lèi)型
動(dòng)態(tài)類(lèi)型再榄,說(shuō)簡(jiǎn)單點(diǎn)就是id類(lèi)型狡刘。動(dòng)態(tài)類(lèi)型是跟靜態(tài)類(lèi)型相對(duì)的。像內(nèi)置的明確的基本類(lèi)型都屬于靜態(tài)類(lèi)型(int困鸥、NSString等)颓帝。靜態(tài)類(lèi)型在編譯的時(shí)候就能被識(shí)別出來(lái)。所以窝革,若程序發(fā)生了類(lèi)型不對(duì)應(yīng)购城,編譯器就會(huì)發(fā)出警告。而動(dòng)態(tài)類(lèi)型就編譯器編譯的時(shí)候是不能被識(shí)別的虐译,要等到運(yùn)行時(shí)(run time)瘪板,即程序運(yùn)行的時(shí)候才會(huì)根據(jù)語(yǔ)境來(lái)識(shí)別。所以這里面就有兩個(gè)概念要分清:編譯時(shí)跟運(yùn)行時(shí)漆诽∥昱剩基本的動(dòng)態(tài)特性在常規(guī)的Cocoa開(kāi)發(fā)中非常常用,特別是動(dòng)態(tài)類(lèi)型和動(dòng)態(tài)綁定厢拭。由于Cocoa程序大量地使用Protocol-Delegate的 設(shè)計(jì)模式兰英,因此絕大部分的delegate指針類(lèi)型必須是id,以滿足運(yùn)行時(shí)delegate的動(dòng)態(tài)替
(2)動(dòng)態(tài)綁定
動(dòng)態(tài)綁定(dynamic binding)供鸠,讓代碼在運(yùn)行時(shí)判斷需要調(diào)用什么方法畦贸,而不是在編譯時(shí)。與其他面向?qū)ο笳Z(yǔ)言一樣,方法調(diào)用和代碼并沒(méi)有在編譯時(shí)連接在一起薄坏,而是在消息發(fā)送時(shí)才進(jìn)行連接趋厉。運(yùn)行時(shí)決定調(diào)用哪個(gè)方法。
(3)動(dòng)態(tài)加載
根據(jù)需求加載所需要的資源胶坠,這點(diǎn)很容易理解君账,對(duì)于iOS開(kāi)發(fā)來(lái)說(shuō),基本就是根據(jù)不同的機(jī)型做適配沈善。最經(jīng)典的例子就是在Retina設(shè)備上加載@2x 的圖片乡数,而在老一些的普通屏設(shè)備上加載原圖。隨著Retina iPad的推出闻牡,和之后可能的Retina Mac的出現(xiàn)净赴,這個(gè)特性相信會(huì)被越來(lái)越多地使用。
oc 語(yǔ)言的優(yōu)點(diǎn):類(lèi)目澈侠、動(dòng)態(tài)識(shí)別、支持c語(yǔ)言埋酬、oc與c++可以混編
缺點(diǎn):不支持命名空間哨啃、不支持運(yùn)算符重載、不支持多重繼承