1. 程式人生 > >Objective-C特性:Runtime

Objective-C特性:Runtime

轉載來自:http://www.jianshu.com/p/25a319aee33d

Objective-C是基於C語言加入了面向物件特性訊息轉發機制的動態語言,這意味著它不僅需要一個編譯器,還需要Runtime系統來動態建立類和物件,進行訊息傳送和轉發。下面通過分析Apple開源的Runtime程式碼(我使用的版本是objc4-646.tar)來深入理解Objective-C的Runtime機制。

Runtime資料結構

在Objective-C中,使用[receiver message]語法並不會馬上執行receiver物件的message方法的程式碼,而是向receiver傳送一條message

訊息,這條訊息可能由receiver來處理,也可能由轉發給其他物件來處理,也有可能假裝沒有接收到這條訊息而沒有處理。其實[receiver message]被編譯器轉化為:

id objc_msgSend ( id self, SEL op, ... );

下面從兩個資料結構idSEL來逐步分析和理解Runtime有哪些重要的資料結構。

SEL

SEL是函式objc_msgSend第二個引數的資料型別,表示方法選擇器,按下面路徑開啟objc.h檔案


SEL Data Structure

檢視到SEL資料結構如下:

typedef struct objc_selector *SEL;

其實它就是對映到方法的C字串,你可以通過Objc編譯器命令@selector()或者Runtime系統的sel_registerName函式來獲取一個SEL型別的方法選擇器。
如果你知道selector對應的方法名是什麼,可以通過NSString* NSStringFromSelector(SEL aSelector)方法將SEL轉化為字串,再用NSLog列印。

id

接下來看objc_msgSend第一個引數的資料型別idid是通用型別指標,能夠表示任何物件。按下面路徑開啟objc.h檔案


id Data Structure.png

檢視到id資料結構如下:

/// 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;

id其實就是一個指向objc_object結構體指標,它包含一個Class isa成員,根據isa指標就可以順藤摸瓜找到物件所屬的類

注意:根據Apple的官方文件Key-Value Observing Implementation Details提及,key-value observing是使用isa-swizzling的技術實現的,isa指標在執行時被修改,指向一箇中間類而不是真正的類。所以,你不應該使用isa指標來確定類的關係,而是使用class方法來確定例項物件的類。

Class

isa指標的資料型別是ClassClass表示物件所屬的類,按下面路徑開啟objc.h檔案


Class Data Structure
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

可以檢視到Class其實就是一個objc_class結構體指標,但這個標頭檔案找不到它的定義,需要在runtime.h才能找到objc_class結構體的定義。

按下面路徑開啟runtime.h檔案


objc_class Data Structure

檢視到objc_class結構體定義如下:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

注意:OBJC2_UNAVAILABLE是一個Apple對Objc系統執行版本進行約束的巨集定義,主要為了相容非Objective-C 2.0的遺留版本,但我們仍能從中獲取一些有用資訊。

讓我們分析一些重要的成員變量表示什麼意思和對應使用哪些資料結構。

  • isa表示一個Class物件的Class,也就是Meta Class。在面向物件設計中,一切都是物件,Class在設計中本身也是一個物件。我們會在objc-runtime-new.h檔案找到證據,發現objc_class有以下定義:

    struct objc_class : objc_object {
      // Class ISA;
      Class superclass;
      cache_t cache;             // formerly cache pointer and vtable
      class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
      ......
    }

    由此可見,結構體objc_class也是繼承objc_object,說明Class在設計中本身也是一個物件

    其實Meta Class也是一個Class,那麼它也跟其他Class一樣有自己的isasuper_class指標,關係如下:


    Class isa and superclass relationship from Google

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

    • Root class (class)其實就是NSObjectNSObject是沒有超類的,所以Root class(class)的superclass指向nil。
    • 每個Class都有一個isa指標指向唯一的Meta class
    • Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一個迴路。
    • 每個Meta class的isa指標都指向Root class (meta)。
  • super_class表示例項物件對應的父類
  • name表示類名
  • ivars表示多個成員變數,它指向objc_ivar_list結構體。在runtime.h可以看到它的定義:

    struct objc_ivar_list {
      int ivar_count                                           OBJC2_UNAVAILABLE;
    #ifdef __LP64__
      int space                                                OBJC2_UNAVAILABLE;
    #endif
      /* variable length structure */
      struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
    }

    objc_ivar_list其實就是一個連結串列,儲存多個objc_ivar,而objc_ivar結構體儲存類的單個成員變數資訊。

  • methodLists表示方法列表,它指向objc_method_list結構體的二級指標,可以動態修改*methodLists的值來新增成員方法,也是Category實現原理,同樣也解釋Category不能新增例項變數的原因。在runtime.h可以看到它的定義:

    struct objc_method_list {
      struct objc_method_list *obsolete                        OBJC2_UNAVAILABLE;
    
      int method_count                                         OBJC2_UNAVAILABLE;
    #ifdef __LP64__
      int space                                                OBJC2_UNAVAILABLE;
    #endif
      /* variable length structure */
      struct objc_method method_list[1]                        OBJC2_UNAVAILABLE;
    }

    同理,objc_method_list也是一個連結串列,儲存多個objc_method,而objc_method結構體儲存類的某個方法的資訊。

  • cache用來快取經常訪問的方法,它指向objc_cache結構體,後面會重點講到。

  • protocols表示類遵循哪些協議

Method

Method表示類中的某個方法,在runtime.h檔案中找到它的定義:

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}

其實Method就是一個指向objc_method結構體指標,它儲存了方法名(method_name)、方法型別(method_types)和方法實現(method_imp)等資訊。而method_imp的資料型別是IMP,它是一個函式指標,後面會重點提及。

Ivar

Ivar表示類中的例項變數,在runtime.h檔案中找到它的定義:

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}

Ivar其實就是一個指向objc_ivar結構體指標,它包含了變數名(ivar_name)、變數型別(ivar_type)等資訊。

IMP

在上面講Method時就說過,IMP本質上就是一個函式指標,指向方法的實現,在objc.h找到它的定義:

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

當你向某個物件傳送一條資訊,可以由這個函式指標來指定方法的實現,它最終就會執行那段程式碼,這樣可以繞開訊息傳遞階段而去執行另一個方法實現。

Cache

顧名思義,Cache主要用來快取,那它快取什麼呢?我們先在runtime.h檔案看看它的定義:

typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};

Cache其實就是一個儲存Method的連結串列,主要是為了優化方法呼叫的效能。當物件receiver呼叫方法message時,首先根據物件receiverisa指標查詢到它對應的類,然後在類的methodLists中搜索方法,如果沒有找到,就使用super_class指標到父類中的methodLists查詢,一旦找到就呼叫方法。如果沒有找到,有可能訊息轉發,也可能忽略它。但這樣查詢方式效率太低,因為往往一個類大概只有20%的方法經常被呼叫,佔總呼叫次數的80%。所以使用Cache來快取經常呼叫的方法,當呼叫方法時,優先在Cache查詢,如果沒有找到,再到methodLists查詢。

訊息傳送

前面從objc_msgSend作為入口,逐步深入分析Runtime的資料結構,瞭解每個資料結構的作用和它們之間關係後,我們正式轉入訊息傳送這個正題。

objc_msgSend函式

在前面已經提過,當某個物件使用語法[receiver message]來呼叫某個方法時,其實[receiver message]被編譯器轉化為:

id objc_msgSend ( id self, SEL op, ... );

現在讓我們看一下objc_msgSend它具體是如何傳送訊息:

  1. 首先根據receiver物件的isa指標獲取它對應的class
  2. 優先在classcache查詢message方法,如果找不到,再到methodLists查詢
  3. 如果沒有在class找到,再到super_class查詢
  4. 一旦找到message這個方法,就執行它實現的IMP

Objc Message.gif

self與super

為了讓大家更好地理解selfsuper,借用sunnyxx部落格的ios程式設計師6級考試一道題目:下面的程式碼分別輸出什麼?

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

self表示當前這個類的物件,而super是一個編譯器標示符,和self指向同一個訊息接受者。在本例中,無論是[self class]還是[super class],接受訊息者都是Son物件,但superself不同的是,self呼叫class方法時,是在子類Son中查詢方法,而super呼叫class方法時,是在父類Father中查詢方法。

當呼叫[self class]方法時,會轉化為objc_msgSend函式,這個函式定義如下:

id objc_msgSend(id self, SEL op, ...)

這時會從當前Son類的方法列表中查詢,如果沒有,就到Father類查詢,還是沒有,最後在NSObject類查詢到。我們可以從NSObject.mm檔案中看到- (Class)class的實現:

- (Class)
            
           

相關推薦

Objective-C特性Runtime

轉載來自:http://www.jianshu.com/p/25a319aee33d Objective-C是基於C語言加入了面向物件特性和訊息轉發機制的動態語言,這意味著它不僅需要一個編譯器,還需要Runtime系統來動態建立類和物件,進行訊息傳送和轉發。下面通過分

Objective-C 中的Runtime的詳細使用

enc ring 博客 document 每次 tps htm lec guid Runtime全面了解 一直以來,OC被大家冠以動態語言的稱謂,其實是因為OC中包含的runtime機制。Runtime 又叫運行時,是一套底層的 C 語言 API,其為 iO

C錯誤runtime error,返回區域性變數或臨時變數的指標

在codeforce上runtime error runtime error (執行時錯誤)就是程式執行到一半,程式就崩潰了。 比如說: ①除以零 ②陣列越界:int a[3]; a[10000000]=10; ③指標越界:int * p; p=(int *)malloc(5 * si

c++ 特性 右值引用與移動語義

c++11中引入了右值引用。 左值與右值 先區分左值與右值,這裡參考c++ 右值引用中對左值和右值區分方法: 左值和右值都是針對表示式,左值是指表示式結束後依然存在的持久物件,右值是指表示式結束時就不再存在的臨時物件。 一個區分左值和右值的簡便方法是

黑馬程式設計師Objective-C筆記點語法

一.點語法本質 點語法的本質的本質還是方法的呼叫,不是訪問成員變數。當編譯器遇到點語法時,自動轉化成相應的語法。 二.例項應用 P.age=10; =[P setAge:10] set方法,設定成員變數的值  int a = P.age=[P age]  get方法,

黑馬程式設計師Objective-C筆記

#import //包含主標頭檔案,因為後面用到了NSobject @interface Person : NSObject //在這裡冒號表示繼承,讓person繼承NSobject的目的是讓Person這個類具備建立物件的能力 { // 這裡宣告屬性(預設情況初始化是0),成員變

黑馬程式設計師Objective-C筆記封裝,繼承,多型

例子: #import @interface Animal : NSObject - (void)eat; @end @implementation Animal - (void)eat { NSLog(@"Animal---Eating something!!"); } @end

Objective-C Runtime 執行時之五協議與分類

Objective-C中的分類允許我們通過給一個類新增方法來擴充它(但是通過category不能新增新的例項變數),並且我們不需要訪問類中的程式碼就可以做到。 Objective-C中的協議是普遍存在的介面定義方式,即在一個類中通過@protocol定義介面,在另外

Objective-C Runtime 執行時之六拾遺

前面幾篇基本介紹了runtime中的大部分功能,包括對類與物件、成員變數與屬性、方法與訊息、分類與協議的處理。runtime大部分的功能都是圍繞這幾點來實現的。 本章的內容並不算重點,主要針對前文中對Objective-C Runtime Reference內容遺漏

Objective-C Runtime 執行時之一類與物件

Objective-C語言是一門動態語言,它將很多靜態語言在編譯和連結時期做的事放到了執行時來處理。這種動態語言的優勢在於:我們寫程式碼時更具靈活性,如我們可以把訊息轉發給我們想要的物件,或者隨意交換一個方法的實現等。 這種特性意味著Objective-C不僅需要一

Objective-C Runtime 執行時之二成員變數與屬性

在前面一篇文章中,我們介紹了Runtime中與類和物件相關的內容,從這章開始,我們將討論類實現細節相關的內容,主要包括類中成員變數,屬性,方法,協議與分類的實現。 本章的主要內容將聚集在Runtime對成員變數與屬性的處理。在討論之前,我們先介紹一個重要的概念:型別

Objective-C Runtime 執行時之三方法與訊息

前面我們討論了Runtime中對類和物件的處理,及對成員變數與屬性的處理。這一章,我們就要開始討論Runtime中最有意思的一部分:訊息處理機制。我們將詳細討論訊息的傳送及訊息的轉發。不過在討論訊息之前,我們先來了解一下與方法相關的一些內容。 基礎資料型別 SEL

Objective-C Runtime 執行時之四Method Swizzling

理解Method Swizzling是學習runtime機制的一個很好的機會。在此不多做整理,僅翻譯由Mattt Thompson發表於nshipster的Method Swizzling一文。 Method Swizzling是改變一個selector的實際實現的

Objective-C Runtime 總結訊息機制 篇

Objective-C語言是一門動態語言,它將很多靜態語言在編譯和連結時期做的事放到了執行時來處理。這種動態語言的優勢在於:我們寫程式碼時更具靈活性,如我們可以把訊息轉發給我們想要的物件,或者隨意交換一個方法的實現等。 與Runtime互動 Objc 從

Objective-C Runtime 執行時成員變數(ivars)及屬性

獲取類的成員變數和屬性: 在objc_class中,所有的成員變數、屬性的資訊是放在連結串列ivars中的。ivars是一個數組,陣列中每個元素是指向Ivar(變數資訊)的指標。runtime提供了豐富的函式來操作這一欄位。大體上可以分為以下幾類: 1.成員變數操作函式

iOS學習筆記56(Runtime)-Objective-C Runtime 執行時之三方法與訊息

前面我們討論了Runtime中對類和物件的處理,及對成員變數與屬性的處理。這一章,我們就要開始討論Runtime中最有意思的一部分:訊息處理機制。我們將詳細討論訊息的傳送及訊息的轉發。不過在討論訊息之前,我們先來了解一下與方法相關的一些內容。 基礎資料型別 SEL

objective-c runtime安全措施之二反注入

《O'Reilly.Hacking.and.Securing.iOS.Applications>>讀書筆記 反注入:在類函式被呼叫前做完整性檢測(預防應用自定義函式或apple標準庫函式被修改或替換) 原理:呼叫dladdr()函式檢查類方法的基本資訊是否合法

第 1 條了解 Objective-C 語言的起源

還在 特性 只知道 程序 開發 不能 核心 nbsp 原因   馬上就要發布 Swift 4 了,自己也在學習 Swift,後面 iOS 編程估計也快是 Swift 的天下了,我卻還在這抱著一本講 OC 的書在啃,怪只能怪自己之前太懶了,按定價好幾十塊錢買的書不讀完,簡直對

深入理解Objective-CCategory

fix 忽略 DDU 相關 情況 內存布局 先生 們的 ntc https://tech.meituan.com/DiveIntoCategory.html 摘要 無論一個類設計的多麽完美,在未來的需求演進中,都有可能會碰到一些無法預測的情況。那怎麽擴展已有的類呢?一般而言

Objective-C多態動態類型識別+動態綁定+動態加載

出錯 靜態類 檢查 而不是 memberof ati 運行 strong 函數指針 http://blog.csdn.net/tskyfree/article/details/7984887 一、Objective-C多態 1.概念:相同接口,不同的實現 來自不同類可以定義