RunTime

runtime 是什么?

  • runtime 又叫做運行時,是一套底層的 C 語言API,其為 iOS 內部的核心之一,我們平時編寫 oc代碼,底層都是基于它來實現的。比如
[receiver message]
// 底層運行時會被編譯器轉化為:
objc_msgSend(receiver, message);

// 有參數的
[receiver message:(id)arg...];

objc_msgSend(receiver, seletor, arg1, arg2, ...);

為什么需要 runtime

  • oc 是一門動態語言,它會將一些工作放在代碼運行時才處理并非編譯時。也就是說,有很多類和成員變量在我們編譯的時候是不知道的,而在運行時,我們編寫的代碼才會被轉換成完整的確定 的代碼運行。
  • 因此,編譯器是不夠的,我們還需要一個運行時系統(runtime system)來處理編譯后的代碼。
  • runtime 基本是用 c 和匯編寫成的,由此可見蘋果為了動態系統的高效做出的努力。蘋果的 GNU 各自維護一個開源的 runtime 版本,這兩個版本之間都在努力保持一致。

runtime 的作用

  • oc 在3個層面上與 runtime 系統進行交互:
  • 通過 oc 源碼,只要需要 oc 代碼,runtime 系統自動在幕后搞定一切,調用方法,編譯器會將 oc 代碼轉化成運行時代碼,在運行時確定數據結構和函數。
  • 通過 Foundation 框架的 NSObject 類定義方法。cocoa程序中絕大多數都是 NSObject 的子類,所有都繼承了 NSObject 的行為。(NSProxy 類是個例外,它是一個抽象類)。
    • 一些情況下 NSObject 類僅僅定義了完成某件事情的模板,并沒有提供所需要的代碼。例如:- description 方法,該類方法返回類內容的字符串表示,該方法主要用來調試程序。NSObject 類并不知道子類的內容,所以它只是返回類的名字和對象的地址,NSObject 的子類可以重新實現。
    • 還有一些 NSObject 的方法可以通過 runtime 系統中獲取信息,允許對象進行自我檢查。例如:
      • -class 方法返回對象的類:
      • -isKindOfClass:和-IsMemberOfClass:方法檢查對象是否存在指定的類的繼承體系中(是否是其子類或者父類或者當前類的成員變量)
      • -respondsToSelector:檢查對象是否響應指定的消息
      • -conformsToProtocol:檢查對象是否實現了指定協議類的方法
      • -methodForSelector:返回指定方法實現的地址
  • 通過對 Runtime 庫函數的直接調用
    • runtime 系統是具有公共接口的動態共享庫。
    • 許多函數可以讓你使用純 C 代碼實現 objc 同樣的功能。除非是寫一些 objc 與其他語言橋接或者底層的 debug 工作,你在寫 objc 代碼時一般不會用到這些 c 語言函數。

runtime 的相關術語

  • SEL

    • 它是selector 在 objc 中的表示。selector 是方法選擇器,其實作用和名字一樣,日常生活中,我們通過人名辨別誰是誰,注意 objc 在相同的類中不會有命名相同的兩個方法。selector 對方法進行包裝,以便找到對應的方法實現。他的數據結構是:typedef struct objc_selector *SEL; 我們可以看出它是一個映射到方法 C 字符串,你可以通過 objc 編譯器命令@selector()或者 runtime 系統的 sel_registerName 函數來獲取一個 SEL 類型的方法選擇器。
    • 注意:不同類中相同名字的方法對應的 selector 是相同的,由于變量類型不同,所以不會導致他們調用方法實現混亂。
  • id

    • id 是一個參數類型,他是指向某個類的實例指針。定義如下:
    typedef struct objc_object *id;
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    
    • 以上定義,看到 objc_object 結構體包含一個 isa 指針,根據 isa 指針就可以找到對應所屬的類。
    • 注意:isa 指針在代碼運行時并不總指向實例對象所屬的類型,所以不能依靠它來確定類型,要響確定類型還是需要用對象的 -class 方法。PS:KVO 的實現原理就是將被觀察對象的 isa 指針指向一個中間類而不是真實類型。
  • Class

    • typedef struct objc_class *Class;
    • class 其實是指向 objc_class 的結構體的指針。objc_class 的數據結構如下
    
    struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    #if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    } OBJC2_UNAVAILABLE;、
    
    
    • 從 objc_class 可以看出,一個運行時類中關聯了它的父類指針、類名、成員變量、方法、緩存以及附屬協議。
    • 其中 objc_ivar_list 和 objc_method_list 分別是成員變量列表和方法列表:
    // objc_ivar_list 的實現
    
    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_method_list的實現
    
    struct objc_method_list {
        struct objc_method_list * _Nullable 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;
    }    
    
    • 由此可見,我們可以動態修改 *methodList 的值來添加成員方法,這也是 category 實現的原理,同樣解釋了 Category 不能添加屬性的原因
    • objc_ivar_list 結構體用來存儲成員變量的列表,而 objc_ivar則是存儲了單個成員變量的信息;同理,objc_method_list 結構體存儲著方法數組的列表,而單個方法信息由 objc_method 結構體存儲。
    • 值得注意的是,objc_class 中也有一個 isa 指針,這說明 objc 類本身也是一個對象。為了處理類和對象的關系,runtime 庫創建一個叫做 Meta Class(元類)的東西,類對象所屬的類叫做元類。meta Class 表述了對象本身所具備的元數據。
    • 我們所熟悉的類方法,就源自于 meta Class。我們可以理解為類方法就是類對象的實例方法。每個類僅有一個類對象,而每個類對象僅有一個與之相關的元類。
    • 當你發出一個類似[NSObject alloc](類方法)消息時,實際上,這個消息被發送給一個類對象(Class object),這個類對象丙戌是一個元類的實例,而這個元類同時也是一個根元類(root meta Class)的實例。所有元類的 isa 指針最終都指向根元類。
    • 所以當[NSObject alloc];這條消息發送給類對象的時候,運行時代碼 objc_msgSend()會去元類中查找能夠響應的方法實現,如果找到了,就會對這個類對象執行方法調用。
    • 最后 objc_class 中還有一個 objc_cahce,緩存。
  • method

    • method 代表類中某個方法的類型
    struct objc_method {
        SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
        char * _Nullable method_types                            OBJC2_UNAVAILABLE;
        IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
     }   
    
    • 方法類型是 SEL
    • 方法類型 method_types 是一個char 指針,存儲方法的參數類型和返回值類型
    • method_imp 指向了方法實現,本質是一個函數指針
    • Ivar 是表示成員變量的類型。
    struct objc_ivar {
        char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
        char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
        int ivar_offset                                          OBJC2_UNAVAILABLE;
    #ifdef __LP64__
        int space                                                OBJC2_UNAVAILABLE;
    #endif
    }    
    
    • 其中 ivar_offset 是基地址便宜字節
  • IMP

    • IMP 在 objc.h 中定義的是
    typedef void (*IMP)(void /* id, SEL, ... */ ); 
    
    • 他是一個函數指針,這是由編譯器生成的。當你發起一個 objc 消息之后,最終他會執行哪段代碼,就是由這個函數指針制定的。而 IMP 這個函數指針就指向了這個方法的實現。
    • 如果得到了執行某個實例某個方法的入口,我們就可以繞開消息傳遞階段,直接執行方法。
    • 你會發現 IMP 指向的方法與 objc_msgSend 函數類型相同,參數都包含了 id 和 SEL 類型。每個方法名都對應一個 SEL 類型的方法選擇器,而每個實例中的 SEL 對應的方法實現肯定是唯一的,通過一組 id 和 SEL 參數就能確定唯一的方法實現地址。
    • 而一個確定方法也只有唯一一組 id 和 SEL 參數。
  • cache

    • 定義如下
    typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;
    
    struct objc_cache {
        unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
        unsigned int occupied                                    OBJC2_UNAVAILABLE;
        Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE;
    };
    
    • cache 為方法調用的性能進行了優化,每當實例對象接收一個消息時,它不會直接在 isa 指針指向的類的方法類別中遍歷查找能夠響應的方法,因為每次都要查找的效率太低了,而是優先在 cache 中找。
    • runtime 系統會吧調用到的方法 cache 中,如果一個方法被調用,那么他有可能今后還會被調用,下次查找的時候就會效率更高。就像計算機組成原理中 CPU 繞過主存先訪問 cache 一樣。
  • property

    typedef struct objc_property *objc_property_t;
    
    • 可以通過 class_copyPropertyList 和 protocol_copyPropertyList 方法獲取類和協議中的屬性
    OBJC_EXPORT objc_property_t _Nonnull * _Nullable
        class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    
      OBJC_EXPORT objc_property_t _Nonnull * _Nullable
        protocol_copyPropertyList(Protocol * _Nonnull proto,
                          unsigned int * _Nullable outCount)
        OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
    
    
    • 返回的是屬性列表,列表中的每個元素都是一個 objc_property_t 指針
    @interface Person ()
    
    @property (nonatomic, strong) NSString *name;
    @property (nonatomic, assign) int age;
    @property (nonatomic, assign) double weight;
    
    @end
    
    // 寫 person 添加3個屬性。通過 runtime 獲取運行時屬性。
    
    unsigned int outCount = 0;
        
    objc_property_t *properties = class_copyPropertyList([Person class], &outCount);
        
    NSLog(@"%d", outCount);
        
    for (NSInteger i = 0; i < outCount; ++i) {
        NSString *name = @(property_getName(properties[i]));
        NSString *attributes = @(property_getAttributes(properties[i]));
        NSLog(@"name:%@\nattributes:%@", name, attributes);
    }
    
    [10522:615669] 4
    [10522:615669] name:name
    attributes:T@"NSString",&,N,V_name
    [10522:615669] name:age
    attributes:Ti,N,V_age
    [10522:615669] name:weight
    attributes:Td,N,V_weight
        
    

runtime 與消息

  • 消息知道運行時才會與方法實現進行綁定。
  • objc_msgSend 方法看起來好像返回了數據,其實 objc_msgSend 從不返回數據,而是你的方法在運行時實現被調用后才會返回數據。消息發送步驟:
    • 首先你要檢測 selector 是不是要忽略。mac 開發有了垃圾回收舊不理會 retain、release 這些函數。
    • 檢測這個 selector 的 target 是不是 nil。objc 允許我們對一個 nil 對象執行任何方法不會 crash,因為運行時會被忽略掉。
    • 如果上面兩步都通過了,那么就開始查找這個類的實現 IMP,先從 cache 中找,如果找到了就運行對應的函數去執行相應的代碼。
    • 如果 cache 找不到就找類的方法列表中是否有對應的方法。
    • 如果累的方法列表中找不到就到父類的方法列表中找,一直找到 NSObject 類為止。
    • 如果還沒找到,就要開始進入動態方法解析了。
  • 在消息傳遞中,編譯器會根據情況在 objc_msgSend,objc_msgSend_stret,objc_msgSendSuper,objc_msgSendSuper——stret 這個四個方法中選擇一個調用。如果消息傳遞給父類,那么會調用名字帶有 Super 的函數,如果消息返回值是數據結構而不是簡單值時,會調用帶有 stret 的函數。

方法中的隱藏參數

  • 疑問:我們經常用到關鍵字 self,但是 self 是如何獲取當前方法的對象的呢?其實這也是 runtime 系統的作用,self 是在方法運行時被動態傳入的。
  • 當 objc_msgSend 找到方法對應實現時,他將直接調用該方法實現,并將消息中所有參數都傳遞給方法實現,同時還有兩個隱藏參數:
    • 接受消息的對象(self 所指向的內容,當前方法的對象指針)
    • 方法選擇器(_cmd 指向的內容,當前指針的 SEL 指針)
    • 因為在源代碼方法的定義中,我們并沒有發現這兩個參數的聲明。它們實在代碼編譯階段被插入方法實現中的。盡管這些參數沒有被明確聲明,在源碼中我們仍然可以引用它們。
    • 這兩個參數中,self 更實用。他是在方法實現中訪問消息接收者對象的實例變量的途徑。
  • 這時我們會想到另一個關鍵字 Super,實際上 Super 關鍵字接收消息時,編譯器會創建一個 objc_super 結構體

消息轉發

  • 重定向
    • 消息轉發機制執行前,runtime 系統允許我們替換消息的接收者為其他對象。通過- (id)forwardingTargetForSelector:(SEL)aSelector 方法。
    • 如果返回為 nil 或者 self,則會計入消息轉發機制(forwardInvocation:),否則向返回的對象重新發送消息。
  • 轉發
    • 當動態方法解析不做處理返回 NO 時,則會觸發消息轉發機制。

動態綁定

  • 在運行時確定要調用的方法,動態綁定將調用方法的確定也推遲到運行時。在編譯
    時,方法的調用并不和代碼綁定在一起,只有在消實發送出來之后,才確定被調用的
    代碼。通過動態類型和動態綁定技術,代碼每次執行都可以得到不同的結果。運行時
    因子負責確定消息的接收者和被調用的方法。運行時的消息分發機制為動態綁定提供
    支持。當向一個動態類型確定了的對象發送消息時,運行環境系統會通過接收者的isa
    指針定位對象的類,并以此為起點確定被調用的方法,方法和消息是動態綁定的。而
    且,不必在0bjective-C 代碼中做任何工作,就可以自動獲取動態綁定的好處。在每次發送消息時,特別是當消息的接收者是動態類型已經確定的對象時,動態綁定就會例行而透明地發生
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 轉載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 766評論 0 2
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,156評論 0 9
  • 我們常常會聽說 Objective-C 是一門動態語言,那么這個「動態」表現在哪呢?我想最主要的表現就是 Obje...
    Ethan_Struggle閱讀 2,231評論 0 7
  • 本文轉載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 785評論 0 1
  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,768評論 0 9