iOS系列之Runtime

作者:洋仔
鏈接:https://juejin.cn/post/6961653605652758564

1.objc在向一個(gè)對(duì)象發(fā)送消息時(shí),發(fā)生了什么?

objc在向一個(gè)對(duì)象發(fā)送消息時(shí),runtime會(huì)根據(jù)對(duì)象的isa指針找到該對(duì)象實(shí)際所屬的類(lèi),然后在該類(lèi)中的方法列表以及其父類(lèi)方法列表中尋找方法運(yùn)行,如果一直到根類(lèi)還沒(méi)找到,轉(zhuǎn)向攔截調(diào)用,走消息轉(zhuǎn)發(fā)機(jī)制,一旦找到 ,就去執(zhí)行它的實(shí)現(xiàn)IMP 。

2.objc中向一個(gè)nil對(duì)象發(fā)送消息將會(huì)發(fā)生什么?

如果向一個(gè)nil對(duì)象發(fā)送消息,首先在尋找對(duì)象的isa指針時(shí)就是0地址返回了,所以不會(huì)出現(xiàn)任何錯(cuò)誤。也不會(huì)崩潰。

詳解: 如果一個(gè)方法返回值是一個(gè)對(duì)象,那么發(fā)送給nil的消息將返回0(nil);
如果方法返回值為指針類(lèi)型,其指針大小為小于或者等于sizeof(void*) ,float,double,long double 或者long long的整型標(biāo)量,發(fā)送給nil的消息將返回0;
如果方法返回值為結(jié)構(gòu)體,發(fā)送給nil的消息將返回0。結(jié)構(gòu)體中各個(gè)字段的值將都是0;
如果方法的返回值不是上述提到的幾種情況,那么發(fā)送給nil的消息的返回值將是未定義的。

3.objc中向一個(gè)對(duì)象發(fā)送消息[obj foo]和objc_msgSend()函數(shù)之間有什么關(guān)系?

在objc編譯時(shí),[obj foo] 會(huì)被轉(zhuǎn)意為:objc_msgSend(obj, @selector(foo));。

4.什么時(shí)候會(huì)報(bào)unrecognized selector的異常?

objc在向一個(gè)對(duì)象發(fā)送消息時(shí),runtime庫(kù)會(huì)根據(jù)對(duì)象的isa指針找到該對(duì)象實(shí)際所屬的類(lèi),然后在該類(lèi)中的方法列表以及其父類(lèi)方法列表中尋找方法運(yùn)行,如果,在最頂層的父類(lèi)中依然找不到相應(yīng)的方法時(shí),會(huì)進(jìn)入消息轉(zhuǎn)發(fā)階段,如果消息三次轉(zhuǎn)發(fā)流程仍未實(shí)現(xiàn),則程序在運(yùn)行時(shí)會(huì)掛掉并拋出異常unrecognized selector sent to XXX 。

5.能否向編譯后得到的類(lèi)中增加實(shí)例變量?能否向運(yùn)行時(shí)創(chuàng)建的類(lèi)中添加實(shí)例變量?為什么?

不能向編譯后得到的類(lèi)中增加實(shí)例變量;
能向運(yùn)行時(shí)創(chuàng)建的類(lèi)中添加實(shí)例變量;
1.因?yàn)榫幾g后的類(lèi)已經(jīng)注冊(cè)在 runtime 中,類(lèi)結(jié)構(gòu)體中的 objc_ivar_list 實(shí)例變量的鏈表和 instance_size 實(shí)例變量的內(nèi)存大小已經(jīng)確定,同時(shí)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 之前,原因同上.

6.給類(lèi)添加一個(gè)屬性后,在類(lèi)結(jié)構(gòu)體里哪些元素會(huì)發(fā)生變化?

instance_size :實(shí)例的內(nèi)存大?。籵bjc_ivar_list *ivars:屬性列表

7.一個(gè)objc對(duì)象的isa的指針指向什么?有什么作用?

指向他的類(lèi)對(duì)象,從而可以找到對(duì)象上的方法

8.[self class] 與 [super class]

@implementation Son : Father   - (id)init   {       self = [super init];       if (self) {           NSLog(@"%@", NSStringFromClass([self class]));           NSLog(@"%@", NSStringFromClass([super class]));       }       return self;   }   @end
NSStringFromClass([self class]) = Son NSStringFromClass([super class]) = Son

詳解:這個(gè)題目主要是考察關(guān)于 Objective-C 中對(duì) self 和 super 的理解。
self 是類(lèi)的隱藏參數(shù),指向當(dāng)前調(diào)用方法的這個(gè)類(lèi)的實(shí)例;
super 本質(zhì)是一個(gè)編譯器標(biāo)示符,和 self 是指向的同一個(gè)消息接受者。不同點(diǎn)在于:super 會(huì)告訴編譯器,當(dāng)調(diào)用方法時(shí),去調(diào)用父類(lèi)的方法,而不是本類(lèi)中的方法。
當(dāng)使用 self 調(diào)用方法時(shí),會(huì)從當(dāng)前類(lèi)的方法列表中開(kāi)始找,如果沒(méi)有,就從父類(lèi)中再找;而當(dāng)使用 super 時(shí),則從父類(lèi)的方法列表中開(kāi)始找。然后調(diào)用父類(lèi)的這個(gè)方法。
在調(diào)用[super class]的時(shí)候,runtime會(huì)去調(diào)用objc_msgSendSuper方法,而不是objc_msgSend;
由于找到了父類(lèi)NSObject里面的class方法的IMP,又因?yàn)閭魅氲娜雲(yún)bjc_super->receiver = self。self就是son,調(diào)用class,所以父類(lèi)的方法class執(zhí)行IMP之后,輸出還是son,最后輸出兩個(gè)都一樣,都是輸出son。

題庫(kù)資料已上傳到Github:https://github.com/Henry-ley/rest/blob/main/README.md內(nèi)附整理的學(xué)習(xí)思維導(dǎo)圖以及一些iOS資料。

9.runtime如何通過(guò)selector找到對(duì)應(yīng)的IMP地址?

每一個(gè)類(lèi)對(duì)象中都一個(gè)方法列表,方法列表中記錄著方法的名稱(chēng),方法實(shí)現(xiàn),以及參數(shù)類(lèi)型,其實(shí)selector本質(zhì)就是方法名稱(chēng),通過(guò)這個(gè)方法名稱(chēng)就可以在方法列表中找到對(duì)應(yīng)的方法實(shí)現(xiàn).

10._objc_msgForward函數(shù)是做什么的,直接調(diào)用它將會(huì)發(fā)生什么?

_objc_msgForward是 IMP 類(lèi)型,用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個(gè)對(duì)象發(fā)送一條消息,但它并沒(méi)有實(shí)現(xiàn)的時(shí)候,_objc_msgForward會(huì)嘗試做消息轉(zhuǎn)發(fā)。

11.runtime 如何實(shí)現(xiàn) weak 屬性??知道SideTable嗎?

weak 此特質(zhì)表明該屬性定義了一種「非擁有關(guān)系」(nonowning relationship)。為這種屬性設(shè)置新值時(shí),設(shè)置方法既不持有新值(新指向的對(duì)象),也不釋放舊值(原來(lái)指向的對(duì)象)。
runtime 對(duì)注冊(cè)的類(lèi),會(huì)進(jìn)行內(nèi)存布局,從一個(gè)粗粒度的概念上來(lái)講,這時(shí)候會(huì)有一個(gè) hash 表,這是一個(gè)全局表,表中是用 weak 指向的對(duì)象內(nèi)存地址作為 key,用所有指向該對(duì)象的 weak 指針表作為 value。當(dāng)此對(duì)象的引用計(jì)數(shù)為 0 的時(shí)候會(huì) dealloc,假如該對(duì)象內(nèi)存地址是 a,那么就會(huì)以 a 為 key,在這個(gè) weak 表中搜索,找到所有以 a 為鍵的 weak 對(duì)象,從而設(shè)置為 nil。
runtime 如何實(shí)現(xiàn) weak 屬性具體流程大致分為 3 步:

1、初始化時(shí):runtime 會(huì)調(diào)用 objc_initWeak 函數(shù),初始化一個(gè)新的 weak 指針指向?qū)ο蟮牡刂贰?br> 2、添加引用時(shí):objc_initWeak 函數(shù)會(huì)調(diào)用 objc_storeWeak() 函數(shù),objc_storeWeak() 的作用是更新指針指向(指針可能原來(lái)指向著其他對(duì)象,這時(shí)候需要將該 weak 指針與舊對(duì)象解除綁定,會(huì)調(diào)用到 weak_unregister_no_lock),如果指針指向的新對(duì)象非空,則創(chuàng)建對(duì)應(yīng)的弱引用表,將 weak 指針與新對(duì)象進(jìn)行綁定,會(huì)調(diào)用到 weak_register_no_lock。在這個(gè)過(guò)程中,為了防止多線(xiàn)程中競(jìng)爭(zhēng)沖突,會(huì)有一些鎖的操作。
3、釋放時(shí):調(diào)用 clearDeallocating 函數(shù),clearDeallocating 函數(shù)首先根據(jù)對(duì)象地址獲取所有 weak 指針地址的數(shù)組,然后遍歷這個(gè)數(shù)組把其中的數(shù)據(jù)設(shè)為 nil,最后把這個(gè) entry 從 weak 表中刪除,最后清理對(duì)象的記錄。

SideTable結(jié)構(gòu)體是負(fù)責(zé)管理類(lèi)的引用計(jì)數(shù)表和weak表,

12.使用runtime Associate方法關(guān)聯(lián)的對(duì)象,需要在主對(duì)象dealloc的時(shí)候釋放么?

無(wú)論在MRC下還是ARC下均不需要,被關(guān)聯(lián)的對(duì)象在生命周期內(nèi)要比對(duì)象本身釋放的晚很多,它們會(huì)在被 NSObject -dealloc 調(diào)用的object_dispose()方法中釋放。

1、調(diào)用 -release :引用計(jì)數(shù)變?yōu)榱?對(duì)象正在被銷(xiāo)毀,生命周期即將結(jié)束. 不能再有新的 __weak 弱引用,否則將指向 nil.
調(diào)用 [self dealloc] 

2、 父類(lèi)調(diào)用 -dealloc 
繼承關(guān)系中最直接繼承的父類(lèi)再調(diào)用 -dealloc 
如果是 MRC 代碼 則會(huì)手動(dòng)釋放實(shí)例變量們(iVars)
繼承關(guān)系中每一層的父類(lèi) 都再調(diào)用 -dealloc 

3、NSObject 調(diào) -dealloc 只做一件事:調(diào)用 Objective-C runtime 中object_dispose() 方法 

4. 調(diào)用 object_dispose()為 C++ 的實(shí)例變量們(iVars)調(diào)用 destructors為 ARC 狀態(tài)下的 實(shí)例變量們(iVars)調(diào)用 -release 
解除所有使用 runtime Associate方法關(guān)聯(lián)的對(duì)象 
解除所有 __weak 引用 調(diào)用 free()

13. 什么是method swizzling(俗稱(chēng)黑魔法)

在Objective-C中調(diào)用一個(gè)方法,其實(shí)是向一個(gè)對(duì)象發(fā)送消息,查找消息的唯一依據(jù)是selector的名字。利用Objective-C的動(dòng)態(tài)特性,可以實(shí)現(xiàn)在運(yùn)行時(shí)偷換selector對(duì)應(yīng)的方法實(shí)現(xiàn),達(dá)到給方法掛鉤的目的。
每個(gè)類(lèi)都有一個(gè)方法列表,存放著方法的名字和方法實(shí)現(xiàn)的映射關(guān)系,selector的本質(zhì)其實(shí)就是方法名,IMP有點(diǎn)類(lèi)似函數(shù)指針,指向具體的Method實(shí)現(xiàn),通過(guò)selector就可以找到對(duì)應(yīng)的IMP。
換方法的幾種實(shí)現(xiàn)方式

利用 method_exchangeImplementations 交換兩個(gè)方法的實(shí)現(xiàn)
利用 class_replaceMethod 替換方法的實(shí)現(xiàn)
利用 method_setImplementation 來(lái)直接設(shè)置某個(gè)方法的IMP

14.runtime具體應(yīng)用

利用關(guān)聯(lián)對(duì)象(AssociatedObject)給分類(lèi)添加屬性
遍歷類(lèi)的所有成員變量(修改textfield的占位文字顏色、字典轉(zhuǎn)模型、自動(dòng)歸檔解檔)
交換方法實(shí)現(xiàn)(交換系統(tǒng)的方法)
利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的異常問(wèn)題
KVC 字典轉(zhuǎn)模型

15.runtime如何通過(guò)selector找到對(duì)應(yīng)的IMP地址?

每一個(gè)類(lèi)對(duì)象中都一個(gè)對(duì)象方法列表(對(duì)象方法緩存)

類(lèi)方法列表是存放在類(lèi)對(duì)象中isa指針指向的元類(lèi)對(duì)象中(類(lèi)方法緩存)。
方法列表中每個(gè)方法結(jié)構(gòu)體中記錄著方法的名稱(chēng),方法實(shí)現(xiàn),以及參數(shù)類(lèi)型,其實(shí)selector本質(zhì)就是方法名稱(chēng),通過(guò)這個(gè)方法名稱(chēng)就可以在方法列表中找到對(duì)應(yīng)的方法實(shí)現(xiàn)。
當(dāng)我們發(fā)送一個(gè)消息給一個(gè)NSObject對(duì)象時(shí),這條消息會(huì)在對(duì)象的類(lèi)對(duì)象方法列表里查找。
當(dāng)我們發(fā)送一個(gè)消息給一個(gè)類(lèi)時(shí),這條消息會(huì)在類(lèi)的Meta Class對(duì)象的方法列表里查找。

16.簡(jiǎn)述下Objective-C中調(diào)用方法的過(guò)程

Objective-C是動(dòng)態(tài)語(yǔ)言,每個(gè)方法在運(yùn)行時(shí)會(huì)被動(dòng)態(tài)轉(zhuǎn)為消息發(fā)送,即:objc_msgSend(receiver, selector),整個(gè)過(guò)程介紹如下:

objc在向一個(gè)對(duì)象發(fā)送消息時(shí),runtime庫(kù)會(huì)根據(jù)對(duì)象的isa指針找到該對(duì)象實(shí)際所屬的類(lèi)
然后在該類(lèi)中的方法列表以及其父類(lèi)方法列表中尋找方法運(yùn)行
如果,在最頂層的父類(lèi)(一般也就NSObject)中依然找不到相應(yīng)的方法時(shí),程序在運(yùn)行時(shí)會(huì)掛掉并拋出異常unrecognized selector sent to XXX
但是在這之前,objc的運(yùn)行時(shí)會(huì)給出三次拯救程序崩潰的機(jī)會(huì),這三次拯救程序奔潰的說(shuō)明見(jiàn)問(wèn)題《什么時(shí)候會(huì)報(bào)unrecognized selector的異?!分械恼f(shuō)明。

17.load和initialize的區(qū)別

兩者都會(huì)自動(dòng)調(diào)用父類(lèi)的,不需要super操作,且僅會(huì)調(diào)用一次(不包括外部顯示調(diào)用).

load和initialize方法都會(huì)在實(shí)例化對(duì)象之前調(diào)用,以main函數(shù)為分水嶺,前者在main函數(shù)之前調(diào)用,后者在之后調(diào)用。這兩個(gè)方法會(huì)被自動(dòng)調(diào)用,不能手動(dòng)調(diào)用它們。
load和initialize方法都不用顯示的調(diào)用父類(lèi)的方法而是自動(dòng)調(diào)用,即使子類(lèi)沒(méi)有initialize方法也會(huì)調(diào)用父類(lèi)的方法,而load方法則不會(huì)調(diào)用父類(lèi)。
load方法通常用來(lái)進(jìn)行Method Swizzle,initialize方法一般用于初始化全局變量或靜態(tài)變量。
load和initialize方法內(nèi)部使用了鎖,因此它們是線(xiàn)程安全的。實(shí)現(xiàn)時(shí)要盡可能保持簡(jiǎn)單,避免阻塞線(xiàn)程,不要再使用鎖。

18.怎么理解Objective-C是動(dòng)態(tài)運(yùn)行時(shí)語(yǔ)言

主要是將數(shù)據(jù)類(lèi)型的確定由編譯時(shí),推遲到了運(yùn)行時(shí)。這個(gè)問(wèn)題其實(shí)淺涉及到兩個(gè)概念,運(yùn)行時(shí)和多態(tài)。
簡(jiǎn)單來(lái)說(shuō), 運(yùn)行時(shí)機(jī)制使我們直到運(yùn)行時(shí)才去決定一個(gè)對(duì)象的類(lèi)別,以及調(diào)用該類(lèi)別對(duì)象指定方法。
多態(tài):不同對(duì)象以自己的方式響應(yīng)相同的消息的能力叫做多態(tài)。
意思就是假設(shè)生物類(lèi)(life)都擁有一個(gè)相同的方法-eat;那人類(lèi)屬于生物,豬也屬于生物,都繼承了life后,實(shí)現(xiàn)各自的eat,但是調(diào)用是我們只需調(diào)用各自的eat方法。也就是不同的對(duì)象以自己的方式響應(yīng)了相同的消 息(響應(yīng)了eat這個(gè)選擇器)。因此也可以說(shuō),運(yùn)行時(shí)機(jī)制是多態(tài)的基礎(chǔ).

題庫(kù)資料已上傳到Github:https://github.com/Henry-ley/rest/blob/main/README.md內(nèi)附整理的學(xué)習(xí)思維導(dǎo)圖以及一些iOS資料。

文章到這里就結(jié)束了,你也可以私信我及時(shí)獲取最新資料以及面試相關(guān)資料。如果你有什么意見(jiàn)和建議歡迎給我留言。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容