runtime原理

傳送:runtime官方源碼、github源碼
感謝:簡書Sam_Lau

將C++和Objective進行對比,雖然C++和Objective-C都是在C的基礎上加入面向對象的特性擴充而成的程序設計語言,但二者實現的機制差異很大。C++是基于靜態(tài)類型,而Objective-C是基于動態(tài)運行時類型。也就是說用C++編寫的程序編譯時就直接編譯成了可令機器讀懂的機器語言;用Objective-C編寫的程序不能直接編譯成可令機器讀懂的機器語言,而是在程序運行的時候,通過Runtime把程序轉為可令機器讀懂的機器語言。Runtime是Objective不可缺少的重要一部分。

一、runtime簡介

runtime是一套底層的C語言API,包含很多強大實用的C語言數據類型和C語言函數,平時我們編寫的OC代碼,底層都是基于runtime實現的。

二、Runtime數據結構

在Objective-C中,代碼在程序運行過程中都會被轉化成runtime的C代碼執(zhí)行,例如[target doSomething];
會被轉化成objc_msgSend(target, @selector(doSomething));

1. id和Class

打開/Public Headers/objc.h文件可以看到如下定義:

#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object { 
  Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif

Class是一個指向objc_class結構體的指針,而id是一個指向objc_object結構體的指針,其中的isa是一個指向objc_class結構體的指針。其中的id就是我們所說的對象,Class就是我們所說的類。
打開/Public Headers/runtime.h文件objc_class的定義如下:

typedef struct objc_class *Class;
struct objc_class { 
Class isa                             OBJC_ISA_AVAILABILITY; // metaclass
#if !__OBJC2__ 
Class super_class                     OBJC2_UNAVAILABLE; // 父類 
const char *name                      OBJC2_UNAVAILABLE; // 類名 
long version                          OBJC2_UNAVAILABLE; // 類的版本信息,默認為0,可以通過runtime函數class_setVersion或者class_getVersion進行修改、讀取 
long info                             OBJC2_UNAVAILABLE; // 類信息,供運行時期使用的一些位標識,如CLS_CLASS (0x1L) 表示該類為普通 class,其中包含實例方法和變量;CLS_META (0x2L) 表示該類為 metaclass,其中包含類方法; 
long instance_size                    OBJC2_UNAVAILABLE; // 該類的實例變量大?。ò◤母割惱^承下來的實例變量)
struct objc_ivar_list *ivars          OBJC2_UNAVAILABLE; // 該類的成員變量地址列表 
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法地址列表,與 info 的一些標志位有關,如CLS_CLASS (0x1L),則存儲實例方法,如CLS_META (0x2L),則存儲類方法; 
struct objc_cache *cache              OBJC2_UNAVAILABLE; // 緩存最近使用的方法地址,用于提升效率; 
struct objc_protocol_list *protocols  OBJC2_UNAVAILABLE; // 存儲該類聲明遵守的協(xié)議的列表
#endif
}
/* Use `Class` instead of `struct objc_class *` */

注意:OBJC2_UNAVAILABLE是一個Apple對Objc系統(tǒng)運行版本進行約束的宏定義,主要為了兼容非Objective-C 2.0的遺留版本,但我們仍能從中獲取一些有用信息。

由以上代碼可見,類與對象的區(qū)別就是類比對象多了很多特征成員,?類也可以當做一個objc_object來對待,也就是說類和對象都是對象,分別稱作類對象(class object)和實例對象(instance object),這樣我們就可以區(qū)別對象和類了。



上圖實線是super_class指針,虛線是isa指針。有幾個關鍵點需要解釋以下:

  • Root class (class)其實就是NSObject,NSObject是沒有超類的,所以Root class(class)的superclass指向nil。
  • 每個Class都有一個isa指針指向唯一的Meta class
  • Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一個回路。
  • 每個Meta class的isa指針都指向Root class (meta)。
  • 所有的metaclass中isa指針都是指向根metaclass,而根metaclass則指向自身。根metaclass是通過繼承根類產生的,與根class結構體成員一致,不同的是根metaclass的isa指針指向自身。
  • isa:objc_object(實例對象)中isa指針指向的類結構稱為class(也就是該對象所屬的類)其中存放著普通成員變量與動態(tài)方法(“-”開頭的方法);此處isa指針指向的類結構稱為metaclass,其中存放著static類型的成員變量與static類型的方法(“+”開頭的方法)。

2. SEL

SEL是selector在Objective-C中的表示類型。selector可以理解為區(qū)別方法的ID。

typedef struct objc_selector *SEL;

objc_selector的定義如下:

struct objc_selector { 
    char *name;                         OBJC2_UNAVAILABLE;// 名稱 
    char *types;                       OBJC2_UNAVAILABLE;// 類型
};

name和types都是char類型。

3. IMP

終于到IMP了,它在objc.h中得定義如下:

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

IMP是“implementation”的縮寫,它是由編譯器生成的一個函數指針。當你發(fā)起一個消息后(下文介紹),這個函數指針決定了最終執(zhí)行哪段代碼??梢岳@開消息傳遞階段而去執(zhí)行另一個方法實現。

4. Method

Method代表類中的某個方法的類型。

typedef struct objc_method *Method;

objc_method的定義如下:

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

方法名method_name類型為SEL,上文提到過。方法類型method_types是一個char指針,存儲著方法的參數類型和返回值類型。方法實現method_imp的類型為IMP,上文提到過。

5. Ivar

Ivar代表類中實例變量的類型

typedef struct objc_ivar *Ivar;

objc_ivar的定義如下:

struct objc_ivar {
  char *ivar_name       OBJC2_UNAVAILABLE; // 變量名 
  char *ivar_type       OBJC2_UNAVAILABLE; // 變量類型 
  int ivar_offset       OBJC2_UNAVAILABLE; // ?基地址偏移字節(jié)
#ifdef __LP64__ 
int space               OBJC2_UNAVAILABLE; // 占用空間
#endif
}

6. objc_property_t

objc_property_t是屬性,它的定義如下:

typedef struct objc_property *objc_property_t;

objc_property是內置的類型,與之關聯(lián)的還有一個objc_property_attribute_t,它是屬性的attribute,也就是其實是對屬性的詳細描述,包括屬性名稱、屬性編碼類型、原子類型/非原子類型等。它的定義如下:

typedef struct { 
  const char *name; // 名稱 
  const char *value; // 值(通常是空的)
} objc_property_attribute_t;

7. Cache

Catch的定義如下:

typedef struct objc_cache *Cache

objc_cache的定義如下:

struct objc_cache { 
  unsigned int mask       OBJC2_UNAVAILABLE; 
  unsigned int occupied   OBJC2_UNAVAILABLE; 
  Method buckets[1]       OBJC2_UNAVAILABLE;
};

mask: 指定分配cache buckets的總數。在方法查找中,Runtime使用這個字段確定數組的索引位置。occupied: 實際占用cache buckets的總數。buckets: 指定Method數據結構指針的數組。這個數組可能包含不超過mask+1個元素。需要注意的是,指針可能是NULL,表示這個緩存bucket沒有被占用,另外被占用的bucket可能是不連續(xù)的。這個數組可能會隨著時間而增長。objc_msgSend(下文講解)每調用一次方法后,就會把該方法緩存到cache列表中,下次的時候,就直接優(yōu)先從cache列表中尋找,如果cache沒有,才從methodLists中查找方法。

8. Catagory

這個就是我們平時所說的類別了,很熟悉吧。它可以動態(tài)的為已存在的類添加新的方法。它的定義如下:

typedef struct objc_category *Category;

objc_category的定義如下:

struct objc_category { 
  char *category_name                          OBJC2_UNAVAILABLE; // 類別名稱 
  char *class_name                             OBJC2_UNAVAILABLE; // 類名 
  struct objc_method_list *instance_methods    OBJC2_UNAVAILABLE; // 實例方法列表 
  struct objc_method_list *class_methods       OBJC2_UNAVAILABLE; // 類方法列表 
  struct objc_protocol_list *protocols         OBJC2_UNAVAILABLE; // 協(xié)議列表
}

三、Objective-C的消息機制

1. 基本消息傳遞

在面向對象編程中,對象調用方法叫做發(fā)送消息。在編譯時,程序的源代碼就會從對象發(fā)送消息轉換成Runtime的objc_msgSend函數調用。例如某實例變量receiver實現某一個方法oneMethod

[receiver oneMethod];

Runtime會將其轉成類似這樣的代碼

objc_msgSend(receiver, selector);


objc_msgSend函數的調用過程:

  • 第一步:檢測這個selector是不是要忽略的。
  • 第二步:檢測這個target是不是nil對象。nil對象發(fā)送任何一個消息都會被忽略掉。
  • 第三步:1.調用實例方法時,它會首先在自身isa指針指向的類(class)methodLists中查找該方法,如果找不到則會通過class的super_class指針找到父類的類對象結構體,然后從methodLists中查找該方法,如果仍然找不到,則繼續(xù)通過super_class向上一級父類結構體中查找,直至根class;2.當我們調用某個類方法時,它會首先通過自己的isa指針找到metaclass,并從其中methodLists中查找該類方法,如果找不到則會通過metaclass的super_class指針找到父類的metaclass對象結構體,然后從methodLists中查找該方法,如果仍然找不到,則繼續(xù)通過super_class向上一級父類結構體中查找,直至根metaclass;
  • 第四步:前三步都找不到就會進入動態(tài)方法解析。

2. 消息動態(tài)解析

動態(tài)解析流程圖:

runtime如何優(yōu)雅的crash:(在找不到調用的方法程序崩潰之前,你有機會通過重寫NSObject的四個方法來處理)
第一步:通過resolveInstanceMethod:方法決定是否動態(tài)添加方法。如果返回Yes則通過class_addMethod動態(tài)添加方法,消息得到處理,結束;如果返回No,則進入下一步;
第二步:這步會進入forwardingTargetForSelector:方法,用于指定備選對象響應這個selector,不能指定為self。如果返回某個對象則會調用對象的方法,結束。如果返回nil,則進入第三部;
第三部:這步我們要通過methodSignatureForSelector:方法簽名,如果返回nil,則消息無法處理。如果返回methodSignature,則進入下一步;
第四部:這步調用forwardInvocation:方法,我們可以通過anInvocation對象做很多處理,比如修改實現方法,修改響應對象等,如果方法調用成功,則結束。如果失敗,則進入doesNotRecognizeSelector方法,若我們沒有實現這個方法,那么就會crash。

3.Associated Objects

Categories can be used to declare either instance methods or class methods but are not usually suitable for declaring additional properties. It’s valid syntax to include a property declaration in a category interface, but it’s not possible to declare an additional instance variable in a category. This means the compiler won’t synthesize any instance variable, nor will it synthesize any property accessor methods. You can write your own accessor methods in the category implementation, but you won’t be able to keep track of a value for that property unless it’s already stored by the original class. (Programming with Objective-C)

當想使用Category對已存在的類進行擴展時,一般只能添加實例方法或類方法,而不適合添加額外的屬性。雖然可以在Category頭文件中聲明property屬性,但在實現文件中編譯器是無法synthesize任何實例變量和屬性訪問方法。這時需要自定義屬性訪問方法并且使用Associated Objects來給已存在的類Category添加自定義的屬性。Associated Objects提供三個API來向對象添加、獲取和刪除關聯(lián)值:

  • void objc_setAssociatedObject (id object, const void *key, id value, objc_AssociationPolicy policy )
  • id objc_getAssociatedObject (id object, const void *key )
  • void objc_removeAssociatedObjects (id object )

其中objc_AssociationPolicy
是個枚舉類型,它可以指定Objc內存管理的引用計數機制。

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { 
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */ 
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. * The association is not made atomically. */ 
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied. * The association is not made atomically. */ 
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object. * The association is made atomically. */ 
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied. * The association is made atomically. */};

下面有個關于NSObject+AssociatedObject Category添加屬性associatedObject
示例代碼:
NSObject+AssociatedObject.h

@interface NSObject (AssociatedObject)
@property (strong, nonatomic) id associatedObject;
@end

NSObject+AssociatedObject.m

@implementation NSObject (AssociatedObject)
- (void)setAssociatedObject:(id)associatedObject{ 
objc_setAssociatedObject(self, @selector(associatedObject), associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject{
 return objc_getAssociatedObject(self, _cmd);
}
@end

Associated Objects的key要求是唯一并且是常量,而SEL是滿足這個要求的,所以上面的采用隱藏參數_cmd作為key。

4.Method Swizzling

Method Swizzling就是在運行時將一個方法的實現代替為另一個方法的實現。如果能夠利用好這個技巧,可以寫出簡潔、有效且維護性更好的代碼。可以參考兩篇關于Method Swizzling技巧的文章:
nshipster Method Swizzling
Method Swizzling 和 AOP 實踐

5.Aspect-Oriented Programming(AOP)

類似記錄日志、身份驗證、緩存等事務非常瑣碎,與業(yè)務邏輯無關,很多地方都有,又很難抽象出一個模塊,這種程序設計問題,業(yè)界給它們起了一個名字叫橫向關注點(Cross-cutting concern),AOP作用就是分離橫向關注點(Cross-cutting concern)來提高模塊復用性,它可以在既有的代碼添加一些額外的行為(記錄日志、身份驗證、緩存)而無需修改代碼。
危險性
Method Swizzling就像一把瑞士小刀,如果使用得當,它會有效地解決問題。但使用不當,將帶來很多麻煩。在stackoverflow上有人已經提出這樣一個問題:What are the Dangers of Method Swizzling in Objective C?,它的危險性主要體現以下幾個方面:

  • Method swizzling is not atomic
  • Changes behavior of un-owned code
  • Possible naming conflicts
  • Swizzling changes the method's arguments
  • The order of swizzles matters
  • Difficult to understand (looks recursive)
  • Difficult to debug

總結

雖然在平時項目不是經常用到Objective-C的Runtime特性,但當你閱讀一些iOS開源項目時,你就會發(fā)現很多時候都會用到。所以深入理解Objective-C的Runtime數據結構、消息轉發(fā)機制有助于你更容易地閱讀和學習開源項目。

擴展閱讀

玉令天下博客的Objective-C Runtime
顧鵬博客的Objective-C Runtime
Associated Objects
Method Swizzling
Method Swizzling 和 AOP 實踐
Objective-C Runtime Reference
What are the Dangers of Method Swizzling in Objective C?
ios程序員6級考試(答案和解釋)
Objective C類方法load和initialize的區(qū)別

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

相關閱讀更多精彩內容

  • 一、Runtime簡介 Runtime簡稱運行時。OC就是運行時機制,也就是在運行時候的一些機制,其中最主要的是消...
    林安530閱讀 1,112評論 0 2
  • 轉至元數據結尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 2,054評論 0 9
  • 我們初次接觸runtime,聽起來總是那么神秘高級,各種論壇對runtime介紹數不勝數。筆者今天對"高大上"的r...
    零距離仰望星空閱讀 2,604評論 6 20
  • 轉載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 832評論 0 2
  • 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,889評論 33 466

友情鏈接更多精彩內容