iOS 運行時(Runtime)之 類和對象

Runtime簡介

Objective-C runtime 運行 native code 。Java VM 運行 byte code。

原生碼(native code)又稱為機器碼(machine code),是電腦的CPU可直接解讀的數據,是計算機可以直接執行,執行速度最快的代碼。

字節碼(byte code)碼是一種中間狀態(中間碼)的二進制代碼(文件)。需要轉譯後才能成為機器碼。

比如java:字節碼在運行時通過JVM(JAVA虛擬機)做一次轉換生成機器指令,因此能夠更好的跨平臺運行。

Objc Runtime其實是一個Runtime庫,它基本上是用C和彙編寫的,這個庫使得C語言有了面向對象的能力。

在Runtime中,對象可以用C語言中的結構體表示,而方法可以用C函數來實現,另外再加上了一些額外的特性。這些結構體和函數被runtime函數封裝後,讓OC的面向對象編程變為可能。

當程序執行[object doSomething]時,會向消息接收者(object)發送一條消息(doSomething),runtime會根據消息接收者是否能響應該消息而做出不同的反應。

動態語言

靜態類型語言:指在編譯期間就去做數據類型檢查,多數靜態類型語言要求在使用變量之前必須聲明數據類型。比如C#,Java。

動態類型語言:指在運行期間才去做數據類型檢查,也就是說,用動態語言編程時,永遠不用去給任何變量去指定數據類型。該語言會在你第一次給該變量賦值的時候,在內部把數據類型記錄下來。JavaScript、ruby或者Python是典型的動態類型的語言。

OC語言,編譯階段並不能決定真正調用哪個函數,只要函數聲明過即使沒有實現也不會報錯。

我們常說OC是一門動態語言,就是因為它總是把一些決定性的工作從編譯階段推遲到運行時階段。OC代碼的運行不僅需要編譯器,還需要運行時系統(Runtime)來執行編譯後的代碼。

Runtime是一套底層純C語言API,OC代碼最終都會被編譯器轉化為運行時代碼,通過消息機制決定函數調用方式,這也是OC作為動態語言使用的基礎。

isa

在Objective-C中,任何類的定義都是對象。類和類的實例(對象)沒有任何本質上的區別。任何對象都有isa指針。

isa

:是類指針,之所以說isa是指針是因為Class其實是一個指向objc_class結構體的指針,而isa 是它唯一的私有成員變量,即所有對象都有isa指針(isa位置在成員變量第一個位置)

objc_class是個結構體,而且第一個成員變量也是isa指針,這個指針指向的Class不是指向自己而是metaclass(元類)

metaclass

每個實例對象也就是struct objc_object結構體它的isa的指針指向類對象Class,而Class裡也有個isa的指針, 指向metaClass(元類)。

元類是類對象的類,類對象是元類的實例。

這時候相對於元類,objc_class就是元類的實例對象。

基於這種設計模式,不難發現:

  1. 調用 "+" 開頭的類方法 是在調用元類的對象方法,元類保存了類方法的列表和成員變量
  2. 由於每個類有且只有一個,所以每個類對象都是其對應元類的單例
  3. 元類(metaClass)也是類,它也是對象。
  4. 元類也有isa指針,它的isa指針最終指向的是一個根元類(root metaClass)
  5. 根元類的isa指針指向本身,這樣形成了一個封閉的內循環。
iOS 運行時(Runtime)之 類和對象

看圖說話:

1. objc_object中的isa指的是對象的類。

2. objc_class中的isa指的是類的元類。

3. superClass是一層層集成的,到最後NSObject的superClass是nil。而NSObject的isa指向根元類,這個根元類的isa指向它自己,而它的superClass是NSObject,也就是最後形成一個環。

4. metaClass也是相互繼承的。

5. 從objc_class結構體可以看出來,裡面有個isa屬性,還有個super_class屬性,它倆都是指針,其實在objc_class的定義中也能看出來,每一個objc_class都有isa,但是不一定會有super_class。

重點總結:

meta-class也是一個類,也可以向它發送一個消息,那麼它的isa又是指向什麼呢?為了不讓這種結構無限延伸下去,Objective-C的設計者讓所有的meta-class的isa指向基類的meta-class,以此作為它們的所屬類。即,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指針是指向它自己。這樣就形成了一個完美的閉環。

方法在 objc-runtime-new.h中定義:

struct method_t { 

SEL name;
const char *types;
IMP imp;
struct SortBySELAddress :
public std::binary_function<const> const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
/<const>

我們說下上面代碼中的SEL和IMP。

SEL:表示該方法的名稱, IMP: 指向該方法的具體實現的函數指針。

SEL 的含義:

typedef struct objc_selector *SEL;

它是一個指向 objc_selector 指針,表示方法的名字/簽名。如下所示,打印出 selector,會打印出來方法的名稱。

不同類的實例對象performSelector相同的 selector 時,會在各自的消息選標(selector)/實現地址(address) 方法鏈表中根據 selector 去查找具體的方法實現IMP, 然後用這個方法實現去執行具體的實現代碼。這是一個動態綁定的過程,在編譯的時候,我們不知道最終會執行哪一些代碼,只有在執行的時候,通過selector去查詢,我們才能確定具體的執行代碼。

IMP 的含義:

在前面我們也看到 IMP 的定義為:

typedef id (*IMP)(id, SEL, ...);

id是一個指向 objc_object 結構體的指針,任何繼承自 NSObject 的類對象都可以用id 來指代,因為 NSObject 的第一個成員實例就是isa。

IMP 是一個函數指針,這個被指向的函數包含一個接收消息的對象id(self 指針), 調用方法的選標 SEL (方法名),以及不定個數的方法參數,並返回一個id。也就是說 IMP 是消息最終調用的執行代碼,是方法真正的實現代碼 。我們可以像在C語言裡面一樣使用這個函數指針。

NSObject 類中的methodForSelector:方法就是這樣一個獲取指向方法實現IMP 的指針,methodForSelector:返回的指針和賦值的變量類型必須完全一致,包括方法的參數類型和返回值類型。

IMP實際上是一個函數指針,指向方法實現的地址。

Method用於表示類定義中的方法,則定義如下:

typedef struct objc_method *Method
struct objc_method{
SEL method_name OBJC2_UNAVAILABLE; // 方法名
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE; // 方法實現
}

我們可以看到該結構體中包含一個SEL和IMP,實際上相當於在SEL和IMP之間作了一個映射。有了SEL,我們便可以找到對應的IMP,從而調用方法的實現代碼。

iOS 運行時(Runtime)之 類和對象


分享到:


相關文章: