您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關(guān)iOS開發(fā)中runtime的基本知識(shí)有哪些,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
1. 運(yùn)行時(shí)
1.1 基本概念: 運(yùn)行時(shí)
Runtime 的概念
Runtime 又叫運(yùn)行時(shí),是一套底層的 C 語言 API,其為 iOS 內(nèi)部的核心之一,我們平時(shí)編寫的 OC 代碼,底層都是基于它來實(shí)現(xiàn)的。比如:
以上你可能看不出它的價(jià)值,但是我們需要了解的是 Objective-C 是一門動(dòng)態(tài)語言,它會(huì)將一些工作放在代碼運(yùn)行時(shí)才處理而并非編譯時(shí)。也就是說,有很多類和成員變量在我們編譯的時(shí)是不知道的,而在運(yùn)行時(shí),我們所編寫的代碼會(huì)轉(zhuǎn)換成完整的確定的代碼運(yùn)行。
因此,編譯器是不夠的,我們還需要一個(gè)運(yùn)行時(shí)系統(tǒng)(Runtime system)來處理編譯后的代碼。Runtime 基本是用 C 和匯編寫的,由此可見蘋果為了動(dòng)態(tài)系統(tǒng)的高效而做出的努力。蘋果和 GNU 各自維護(hù)一個(gè)開源的 Runtime 版本,這兩個(gè)版本之間都在努力保持一致。
Runtime 的作用
Objc 在三種層面上與 Runtime 系統(tǒng)進(jìn)行交互:
通過 Objective-C 源代碼
通過 Foundation 框架的 NSObject 類定義的方法
通過對(duì) Runtime 庫(kù)函數(shù)的直接調(diào)用
1.2 各種基本概念的C表達(dá)
在 Objective-C 中,類、對(duì)象和方法都是一個(gè) C 的結(jié)構(gòu)體,從 objc/objc.h(對(duì)象,objc_object,id)以及objc/runtime.h(其它,類,方法,方法列表,變量列表,屬性列表等相關(guān)的)以及中,我們可以找到他們的定義。
① 類
類對(duì)象(Class)是由程序員定義并在運(yùn)行時(shí)由編譯器創(chuàng)建的,它沒有自己的實(shí)例變量,這里需要注意的是類的成員變量和實(shí)例方法列表是屬于實(shí)例對(duì)象的,但其存儲(chǔ)于類對(duì)象當(dāng)中的。我們?cè)趏bjc/objc.h下看看Class的定義:
Class
在這里我還是要推薦下我自己建的iOS開發(fā)學(xué)習(xí)群:680565220,群里都是學(xué)ios開發(fā)的,如果你正在學(xué)習(xí)ios ,小編歡迎你加入,今天分享的這個(gè)案例已經(jīng)上傳到群文件,大家都是軟件開發(fā)黨,不定期分享干貨(只有iOS軟件開發(fā)相關(guān)的),包括我自己整理的一份2018最新的iOS進(jìn)階資料和高級(jí)開發(fā)教程
可以看到類是由Class類型來表示的,它是一個(gè)objc_class結(jié)構(gòu)類型的指針。我們接著來看objc_class結(jié)構(gòu)體的定義:
objc_class
參數(shù)解析
isa指針是和Class同類型的objc_class結(jié)構(gòu)指針,類對(duì)象的指針指向其所屬的類,即元類。元類中存儲(chǔ)著類對(duì)象的類方法,當(dāng)訪問某個(gè)類的類方法時(shí)會(huì)通過該isa指針從元類中尋找方法對(duì)應(yīng)的函數(shù)指針。
super_class指針指向該類所繼承的父類對(duì)象,如果該類已經(jīng)是最頂層的根類(如NSObject或NSProxy), 則 super_class為NULL。
cache:用于緩存最近使用的方法。一個(gè)接收者對(duì)象接收到一個(gè)消息時(shí),它會(huì)根據(jù)isa指針去查找能夠響應(yīng)這個(gè)消息的對(duì)象。在實(shí)際使用中,這個(gè)對(duì)象只有一部分方法是常用的,很多方法其實(shí)很少用或者根本用不上。這種情況下,如果每次消息來時(shí),我們都是methodLists中遍歷一遍,性能勢(shì)必很差。這時(shí),cache就派上用場(chǎng)了。在我們每次調(diào)用過一個(gè)方法后,這個(gè)方法就會(huì)被緩存到cache列表中,下次調(diào)用的時(shí)候runtime就會(huì)優(yōu)先去cache中查找,如果cache沒有,才去methodLists中查找方法。這樣,對(duì)于那些經(jīng)常用到的方法的調(diào)用,但提高了調(diào)用的效率。
version:我們可以使用這個(gè)字段來提供類的版本信息。這對(duì)于對(duì)象的序列化非常有用,它可是讓我們識(shí)別出不同類定義版本中實(shí)例變量布局的改變。
protocols:當(dāng)然可以看出這一個(gè)objc_protocol_list的指針。關(guān)于objc_protocol_list的結(jié)構(gòu)體構(gòu)成后面會(huì)講。
獲取類的類名
實(shí)例對(duì)象是我們對(duì)類對(duì)象alloc或者new操作時(shí)所創(chuàng)建的,在這個(gè)過程中會(huì)拷貝實(shí)例所屬的類的成員變量,但并不拷貝類定義的方法。調(diào)用實(shí)例方法時(shí),系統(tǒng)會(huì)根據(jù)實(shí)例的isa指針去類的方法列表及父類的方法列表中尋找與消息對(duì)應(yīng)的selector指向的方法。同樣的,我們也來看下其定義:
objc_object
可以看到,這個(gè)結(jié)構(gòu)體只有一個(gè)isa變量,指向?qū)嵗龑?duì)象所屬的類。任何帶有以指針開始并指向類結(jié)構(gòu)的結(jié)構(gòu)都可以被視作objc_object, 對(duì)象最重要的特點(diǎn)是可以給其發(fā)送消息。 NSObject類的alloc和allocWithZone:方法使用函數(shù)class_createInstance來創(chuàng)建objc_object數(shù)據(jù)結(jié)構(gòu)。
另外我們常見的id類型,它是一個(gè)objc_object結(jié)構(gòu)類型的指針。該類型的對(duì)象可以轉(zhuǎn)換為任何一種對(duì)象,類似于C語言中void *指針類型的作用。其定義如下所示:
id
元類(Metaclass)就是類對(duì)象的類,每個(gè)類都有自己的元類,也就是objc_class結(jié)構(gòu)體里面isa指針?biāo)赶虻念? Objective-C的類方法是使用元類的根本原因,因?yàn)槠渲写鎯?chǔ)著對(duì)應(yīng)的類對(duì)象調(diào)用的方法即類方法。
當(dāng)向?qū)ο蟀l(fā)消息,runtime會(huì)在這個(gè)對(duì)象所屬類方法列表中查找發(fā)送消息對(duì)應(yīng)的方法,但當(dāng)向類發(fā)送消息時(shí),runtime就會(huì)在這個(gè)類的meta class方法列表里查找。所有的meta class,包括Root class,Superclass,Subclass的isa都指向Root class的meta class,這樣能夠形成一個(gè)閉環(huán)。
所以由上圖可以看到,在給實(shí)例對(duì)象或類對(duì)象發(fā)送消息時(shí),尋找方法列表的規(guī)則為:
當(dāng)發(fā)送消息給實(shí)例對(duì)象時(shí),消息是在尋找這個(gè)對(duì)象的類的方法列表(實(shí)例方法)
當(dāng)發(fā)送消息給類對(duì)象時(shí),消息是在尋找這個(gè)類的元類的方法列表(類方法)
元類,就像之前的類一樣,它也是一個(gè)對(duì)象,也可以調(diào)用它的方法。所以這就意味著它必須也有一個(gè)類。所有的元類都使用根元類作為他們的類。比如所有NSObject的子類的元類都會(huì)以NSObject的元類作為他們的類。
根據(jù)這個(gè)規(guī)則,所有的元類使用根元類作為他們的類,根元類的元類則就是它自己。也就是說基類的元類的isa指針指向他自己。
操作函數(shù)
super_class和meta-class
在Objective-C中,屬性(property)和成員變量是不同的。那么,屬性的本質(zhì)是什么?它和成員變量之間有什么區(qū)別?簡(jiǎn)單來說屬性是添加了存取方法的成員變量,也就是:
@property = ivar + getter + setter;
因此,我們每定義一個(gè)@property都會(huì)添加對(duì)應(yīng)的ivar, getter和setter到類結(jié)構(gòu)體objc_class中。具體來說,系統(tǒng)會(huì)在objc_ivar_list中添加一個(gè)成員變量的描述,然后在methodLists中分別添加setter和getter方法的描述。下面的objc_property_t是聲明的屬性的類型,是一個(gè)指向objc_property結(jié)構(gòu)體的指針。
用法舉例
另外,關(guān)于屬性有一個(gè)objc_property_attribute_t結(jié)構(gòu)體列表,objc_property_attribute_t結(jié)構(gòu)體包含name和value
objc_property_attribute_t
常用的屬性如下:
屬性類型 name值:T value:變化
編碼類型 name值:C(copy) &(strong) W(weak)空(assign) 等 value:無
非/原子性 name值:空(atomic) N(Nonatomic) value:無
變量名稱 name值:V value:變化
例如
⑤ 成員變量
Ivar: 實(shí)例變量類型,是一個(gè)指向objc_ivar結(jié)構(gòu)體的
指針
Ivar
在objc_class中,所有的成員變量、屬性的信息是放在鏈表ivars中的。ivars是一個(gè)數(shù)組,數(shù)組中每個(gè)元素是指向Ivar(變量信息)的指針。
objc_ivar_list
其中,
方法名類型為 SEL
方法類型 method_types 是個(gè) char 指針,存儲(chǔ)方法的參數(shù)類型和返回值類型
method_imp 指向了方法的實(shí)現(xiàn),本質(zhì)是一個(gè)函數(shù)指針
簡(jiǎn)言之,Method = SEL + IMP + method_types,相當(dāng)于在SEL和IMP之間建立了一個(gè)映射。
操作函數(shù)
在源碼中沒有直接找到 objc_selector 的定義,從一些書籍上與 Blog 上看到可以將 SEL 理解為一個(gè) char* 指針。
具體這 objc_selector 結(jié)構(gòu)體是什么取決與使用GNU的還是Apple的運(yùn)行時(shí), 在Mac OS X中SEL其實(shí)被映射為一個(gè)C字符串,可以看作是方法的名字,它并不一個(gè)指向具體方法實(shí)現(xiàn)(IMP類型才是)。
對(duì)于所有的類,只要方法名是相同的,產(chǎn)生的selector都是一樣的。
操作函數(shù)
實(shí)際上就是一個(gè)函數(shù)指針,指向方法實(shí)現(xiàn)的首地址。通過取得 IMP,我們可以跳過 runtime 的消息傳遞機(jī)制,直接執(zhí)行 IMP指向的函數(shù)實(shí)現(xiàn),這樣省去了 runtime 消息傳遞過程中所做的一系列查找操作,會(huì)比直接向?qū)ο蟀l(fā)送消息高效一些,當(dāng)然必須說明的是,這種方式只適用于極特殊的優(yōu)化場(chǎng)景,如效率敏感的場(chǎng)景下大量循環(huán)的調(diào)用某方法。
操作函數(shù)
上面提到了objc_class結(jié)構(gòu)體中的cache字段,它用于緩存調(diào)用過的方法。這個(gè)字段是一個(gè)指向objc_cache結(jié)構(gòu)體的指針,其定義如下:
Cache
該結(jié)構(gòu)體的字段描述如下:
mask:一個(gè)整數(shù),指定分配的緩存bucket的總數(shù)。在方法查找過程中,Objective-C runtime使用這個(gè)字段來確定開始線性查找數(shù)組的索引位置。指向方法selector的指針與該字段做一個(gè)AND位操作(index = (mask & selector))。這可以作為一個(gè)簡(jiǎn)單的hash散列算法。
occupied:一個(gè)整數(shù),指定實(shí)際占用的緩存bucket的總數(shù)。
buckets:指向Method數(shù)據(jù)結(jié)構(gòu)指針的數(shù)組。這個(gè)數(shù)組可能包含不超過mask+1個(gè)元素。需要注意的是,指針可能是NULL,表示這個(gè)緩存bucket沒有被占用,另外被占用的bucket可能是不連續(xù)的。這個(gè)數(shù)組可能會(huì)隨著時(shí)間而增長(zhǎng)。
? 協(xié)議鏈表
前面objc_class的結(jié)構(gòu)體中有個(gè)協(xié)議鏈表的參數(shù),協(xié)議鏈表用來存儲(chǔ)聲明遵守的正式協(xié)議
objc_protocol_list
2. 方法調(diào)用流程
objc_msgSend() Tour 系列文章通過對(duì) objc_msgSend 的匯編源碼分析,總結(jié)出以下流程:
2.1 方法調(diào)用流程
檢查 selector 是否需要忽略
檢查 target 是否為 nil,如果是 nil 就直接 cleanup,然后 return
在 target 的 Class 中根據(jù) selector 去找 IMP
2.2 尋找 IMP 的過程:
在當(dāng)前 class 的方法緩存里尋找(cache methodLists)
找到了跳到對(duì)應(yīng)的方法實(shí)現(xiàn),沒找到繼續(xù)往下執(zhí)行
從當(dāng)前 class 的 方法列表里查找(methodLists),找到了添加到緩存列表里,然后跳轉(zhuǎn)到對(duì)應(yīng)的方法實(shí)現(xiàn);沒找到繼續(xù)往下執(zhí)行
從 superClass 的緩存列表和方法列表里查找,直到找到基類為止
以上步驟還找不到 IMP,則進(jìn)入消息動(dòng)態(tài)處理和消息轉(zhuǎn)發(fā)流程,詳見請(qǐng)關(guān)注微信公眾號(hào):程序員大牛!
我們能在 objc4官方源碼 中找到上述尋找 IMP 的過程,具體對(duì)應(yīng)的代碼如下:
objc-class.mm
3. 運(yùn)行時(shí)相關(guān)的API
3.1 通過 Foundation 框架的 NSObject 類定義的方法
Cocoa 程序中絕大部分類都是 NSObject 類的子類,所以都繼承了 NSObject 的行為。(NSProxy 類時(shí)個(gè)例外,它是個(gè)抽象超類)
一些情況下,NSObject 類僅僅定義了完成某件事情的模板,并沒有提供所需要的代碼。例如 -description 方法,該方法返回類內(nèi)容的字符串表示,該方法主要用來調(diào)試程序。NSObject 類并不知道子類的內(nèi)容,所以它只是返回類的名字和對(duì)象的地址,NSObject 的子類可以重新實(shí)現(xiàn)。
還有一些 NSObject 的方法可以從 Runtime 系統(tǒng)中獲取信息,允許對(duì)象進(jìn)行自我檢查。例如:
-class方法返回對(duì)象的類;
-isKindOfClass: 和 -isMemberOfClass: 方法檢查對(duì)象是否存在于指定的類的繼承體系中(是否是其子類或者父類或者當(dāng)前類的成員變量);
-respondsToSelector: 檢查對(duì)象能否響應(yīng)指定的消息;
-conformsToProtocol:檢查對(duì)象是否實(shí)現(xiàn)了指定協(xié)議類的方法;
-methodForSelector: 返回指定方法實(shí)現(xiàn)的地址。
常見的一個(gè)例子:
id 和 void * 轉(zhuǎn)換API:(__bridge void *)
在 ARC 有效時(shí),通過 (__bridge void *)轉(zhuǎn)換 id 和 void * 就能夠相互轉(zhuǎn)換。為什么轉(zhuǎn)換?這是因?yàn)閛bjc_getAssociatedObject的參數(shù)要求的。先看一下它的API:
可以知道,這個(gè)“屬性名”的key是必須是一個(gè)void *類型的參數(shù)。所以需要轉(zhuǎn)換。關(guān)于這個(gè)轉(zhuǎn)換,下面給一個(gè)轉(zhuǎn)換的例子:
4. 運(yùn)行時(shí)實(shí)戰(zhàn)指南
上面的API不是提供大家背的,而是用來查閱的,當(dāng)你要用到的時(shí)候查閱。因?yàn)檫@些原理和API光看沒用,需要實(shí)戰(zhàn)之后再回過頭來查閱和理解。
看完上述內(nèi)容,你們對(duì)iOS開發(fā)中runtime的基本知識(shí)有哪些有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。