? ? ? ? ?大家好,今天給大家分享下對于runtime 的理解 ,網(wǎng)上關(guān)于runtime 的介紹非常多,內(nèi)容也各種各樣,本篇根據(jù)自己對runtime的理解,從? 是什么,為什么,干什么,怎么干 幾個方面談談對runtime的理解.
一、是什么?
? ? ? ? runtime ,又叫運行時,字面意思是程序運行的時候,即run起來的時候,是ios中非常重要非常核心的一個特性,runtime是指在程序運行時,才確定數(shù)據(jù)的類型、消息的具體響應等,將數(shù)據(jù)類型的確定及消息的響應由編譯時推到了程序運行的時候。
二、為什么?
? ? ? ?Objective-C語言是一門動態(tài)語言,runtime是一套底層的 C 語言 API。它會將一些工作放在代碼運行時才處理而并非編譯時。也就是說,有很多類和成員變量在我們編譯的時是不知道的,而在運行時,我們所編寫的代碼會轉(zhuǎn)換成完整的確定的代碼運行。它能讓我們動態(tài)生成、修改、刪除一個類、一個成員變量、一個消息。
三、干什么
因為runtime的特性,它可以運行的時候,動態(tài)的操作類。比如:
? ? a.動態(tài)創(chuàng)建類一個類,KVO的底層實現(xiàn)就是這么干的,動態(tài)生成一個中間類,繼承原來的類,攔截setter方法,當屬于被修改時,setter將攔截的消息發(fā)出,觀察者響應
? ? ?b.動態(tài)的為某個類添加屬性、消息,Category底層實現(xiàn)原理,這里要注意,動態(tài)添加的是屬性而不是成員變量,這是因為類一旦實例化,是無法往Ivar的list里添加變量的,但是可以添加屬性,分不清屬性跟變量的同學出門先百度去。運行時,Category會動態(tài)的往實例的Method List里添加消息實現(xiàn),消息會被追 加到消息列表尾部,響應的時候,objc_msgSend()會從消息列表里,從后往前找能響應的消息,這里注意,是從后往前在消息列表里找,所以Category如果跟主類的消息名一樣的話,會覆蓋主類的消息響應。
? ? c.動態(tài)的修改某個類的屬性、消息, ios?swizzling實現(xiàn),也是hook的前提,即在運行時,通過消息的Selector獲取實例的消息實現(xiàn)IMP,然后把IMP替換成自定義的IMP。從而可以hook住sdk或別的類里代碼,加上我們自己的邏輯。
? ? d.其他,還有很多我們可以利用runtime特么實現(xiàn)的功能,這里不一一介紹了,大家感興趣可以專門搜集下這個專題。
四、怎么干
這個問題,是這4個標題里最復雜的,也是最重要的,首先我們來看一些runtime相關(guān)的概念和機制。
? ? 1.我們要使用runtime的特性,就要引入runtime的頭文件,升級了xcode 9,現(xiàn)在ios11里,runtime大概包含的幾個頭文件,如圖:
? ? 2.常見概念SEL:SEL又叫選擇器,是表示一個方法的selector的指針,其定義如下:
typedef struct objc_selector *SEL;
方法的selector用于表示運行時方法的名字。Objective-C在編譯時,會依據(jù)每一個方法的名字、參數(shù)序列,生成一個唯一的整型標識(Int類型的地址),這個標識就是SEL。通常,SEL的獲取方法有以下三種:
? ? a、sel_registerName函數(shù)
? ? b、Objective-C編譯器提供的@selector()
? ? c、NSSelectorFromString(@"xx")方法
3.常概念I(lǐng)MP:IMP實際上是一個函數(shù)指針,指向方法實現(xiàn)的地址。
其定義如下:
id (*IMP)(id, SEL,...)
第一個參數(shù):是指向self的指針(如果是實例方法,則是類實例的內(nèi)存地址;如果是類方法,則是指向元類的指針)
第二個參數(shù):是方法選擇器(selector)
接下來的參數(shù):方法的參數(shù)列表。
前面介紹過的SEL就是為了查找方法的最終實現(xiàn)IMP的。由于每個方法對應唯一的SEL,因此我們可以通過SEL方便快速準確地獲得它所對應的IMP,查找過程將在下面討論。取得IMP后,我們就獲得了執(zhí)行這個方法代碼的入口點,此時,我們就可以像調(diào)用普通的C語言函數(shù)一樣來使用這個函數(shù)指針了。
4.Method:Method用于表示類定義中的方法,則定義如下:
typedef struct objc_method *Methodstruct objc_method{ ? ?SEL method_name ? ? ?OBJC2_UNAVAILABLE; //方法名char *method_types ? OBJC2_UNAVAILABLE; ? ?IMP method_imp ? ? ? OBJC2_UNAVAILABLE; //方法實現(xiàn)}
我們可以看到該結(jié)構(gòu)體中包含一個SEL和IMP,實際上相當于在SEL和IMP之間作了一個映射。有了SEL,我們便可以找到對應的IMP,從而調(diào)用方法的實現(xiàn)代碼。
5.消息調(diào)用流程:參考公眾號第一篇,objc_msgSend()找消息響應的流程
6.消息轉(zhuǎn)發(fā):
當一個對象無法接收某一消息時,就會啟動所謂“消息轉(zhuǎn)發(fā)(message forwarding)”機制,這是對上面流程5的一個補充。通過這一機制,我們可以告訴對象如何處理未知的消息。默認情況下,對象接收到未知的消息,會執(zhí)行系統(tǒng)默認的處理,導致程序崩潰。當然,了解這一特性之后,我們也可以做一些策略,不讓它crash。做法就是動態(tài)攔截。網(wǎng)上例子特別多,不多做敘述。
示例:
1.swizzling
注意:交換兩個方法的實現(xiàn)一般寫在類的load方法里面,因為load方法會在程序運行前加載一次,而initialize方法會在類或者子類在 第一次使用的時候調(diào)用,當有分類的時候會調(diào)用多次
2.動態(tài)添加屬性(不是變量),又叫關(guān)聯(lián)對象
3.動態(tài)添加方法
從警告里可以看到,我們沒有在頭文件中聲名eat方法,eat方法是我們動態(tài)添加的。
綜上,這些是我們本節(jié)對runtime ?的一些探究,runtime本身是一個非常復雜的實現(xiàn),這里只是討論了其中的一部分,拋磚引玉,文中圖片里引用的相關(guān)api,像class_addMethod(),performSelector()這些api,大家不理解是什么意思的,可以直接在document里查詢,這里不做說明。
商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處