Runtime

什么是運行時

運行時是OC動態(tài)性得以實現(xiàn)的一個機(jī)制,OC以一個動態(tài)語言,把靜態(tài)語言編譯和鏈接的事情放到了運行時來處理,但是怎么處理呢,運行時機(jī)制就是處理這個事情的,它是一套用C和匯編編寫的API。
并且蘋果開源了API, 其中主要在文件runtime.h 和 message.h中

核心概念

類的本質(zhì)
objc_class 和 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

}

// Class  結(jié)構(gòu)體指針,  指向某個類實例, 表示這個類
typedef struct objc_class *Class;

類的本質(zhì),或者說數(shù)據(jù)格式就是結(jié)構(gòu)體, 一個結(jié)構(gòu)體變量就是一個類對象,或者說實例。

objc_object 和 id

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;

objc_selector 和 SEL

typedef struct objc_selector *SEL;

IMP 可以理解為函數(shù)指針, 指向函數(shù)實現(xiàn)首地址

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif
/// 方法    Method  結(jié)構(gòu)體指針,  一個方法包含 方法名SEL,  方法類型 和 方法的實現(xiàn)IMP
struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
} 
typedef struct objc_method *Method;


/// 實例變量
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
}
typedef struct objc_ivar *Ivar;

/// 分類  包括分類名,  類名,實例方法列表,類方法列表和協(xié)議列表
struct objc_category {
    char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
    char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
}
typedef struct objc_category *Category;

/// 屬性
typedef struct objc_property *objc_property_t;

類,父類,元類

/*
        1, 實例的類其實也是對象, 叫做類對象,區(qū)別就是類對象在內(nèi)存中只有一份  類對象的類叫做元類
        2,當(dāng)調(diào)用實例方法時, 會去類對象的方法列表中查找匹配
        3,當(dāng)調(diào)用類方法時,  會去類的元類中查找
        4,觀察類存儲結(jié)構(gòu)的定義,  發(fā)現(xiàn)有isa和  super兩個class類型的數(shù)據(jù),  其中isa指向所屬的類,super指向父類
        5.每個實例對象的類都是類對象,每個類對象的類都是元類對象,每個元類對象的類都是根元類(root meta class的isa指向自身)
      6.類對象的父類最終繼承自根類對象NSObject,NSObject的父類為nil
        7.元類對象(包括根元類)的父類最終繼承自根類對象NSObject
     */
類和元類結(jié)構(gòu)圖.png

方法調(diào)用的本質(zhì)?
方法調(diào)用的本質(zhì)就是向方法調(diào)用者發(fā)送了一條消息,核心方法是
objc_msgSend(void /* id self, SEL op, ... */ ) , OC中的每一個方法調(diào)用會轉(zhuǎn)化成這個c函數(shù)調(diào)用, 其中第一個參數(shù)是方法的調(diào)用者, 第二個參數(shù)表示方法名, 之后是可變參數(shù)列表, 可以傳入調(diào)用方法需要的參數(shù)。那么這個方法底層做了什么呢, 它會去該實例的類對象的方法列表中尋找同名的方法, 如果在本類中找不到就去到父類中尋找, 如果找到同名方法SEL或者Selector后, 拿到對應(yīng)的IMP,然后根據(jù)參數(shù)調(diào)用對應(yīng)的函數(shù)。如果找不到就會進(jìn)入消息轉(zhuǎn)發(fā)

有什么作用

利用runtime提供的API我們可以獲取所有已經(jīng)注冊的類,獲取指定類的信息(包括名字,所有實例變量,屬性, 方法信息) 動態(tài)的創(chuàng)建類, 給類添加方法,屬性(assiociateObject)和 交換方法的實現(xiàn)(hook)
1, 獲取類的所有實例變量(類型 名字)其中實例變量包括類中定義的實例變量和屬性生成的實例變量
2,獲取屬性
3,獲取方法。 獲取對象方法是在本類中查找,, 獲取類方法需要到元類中查找
4,添加屬性。
4.1,對于還沒有注冊的類 添加屬性有相應(yīng)的函數(shù),但是屬性類型編碼需要看一下
4.2, 對于已經(jīng)注冊過的類,如果想添加屬性的話,只能使用關(guān)聯(lián)對象了。對于還沒有注冊的類 有相應(yīng)的函數(shù)可以添加屬性
5,添加方法 改變方法的實現(xiàn)
6,動態(tài)的添加一個類 創(chuàng)建實例
7,獲取實例變量 和 屬性區(qū)別
8, 用運行時配合KVC 改變系統(tǒng)的私有屬性
詳見demo https://github.com/JTWang4778/RuntimeDemo

在Swift中使用和在OC中使用有什么區(qū)別

Swift代碼中已經(jīng)沒有了Objective-C的運行時消息機(jī)制, 在代碼編譯時即確定了其實際調(diào)用的方法. 所以純粹的Swift類和對象沒有辦法使用runtime, 更不存在method swizzling.
為了兼容Objective-C, 凡是繼承NSObject的類都會保留其動態(tài)性, 依然遵循Objective-C的?運行時消息機(jī)制, 因此可以通過runtime獲取其屬性和方法, 實現(xiàn)method swizzling.

面向切面編程

APO, Aspect Oriented Programming面向切面編程, 可以通過預(yù)編譯方式和運行期動態(tài)代理實現(xiàn)在不修改源代碼的情況下給程序動態(tài)統(tǒng)一添加功能的一種技術(shù)。利用運行時我們可以在IOS開發(fā)中運用面向切面編程的思路給工程統(tǒng)一添加某一項功能。 典型的是給現(xiàn)有的類添加方法和屬性,然后hook到原有實現(xiàn)添加處理。 有名的三方有FDFullscreenPopGesture 和 MLeaksFinder

關(guān)于runtime的幾個問題

1, + class, -class 和 objc_getClass 作用一樣嗎?
object_getClass(obj)返回的是obj中的isa指針;而[obj class]則分兩種情況:一是當(dāng)obj為實例對象時,[obj class]中class是實例方法:- (Class)class,返回的obj對象中的isa指針;二是當(dāng)obj為類對象(包括元類和根類以及根元類)時,調(diào)用的是類方法:+ (Class)class,返回的結(jié)果為其本身。
http://www.itdecent.cn/p/ae5c32708bc6
2,isKindOfClass 和 isMemmberOfClass 區(qū)別

//    JTView *subInstance = [JTView new];
//    // 調(diào)用者是否為給定類的實例或任何繼承自給定類的實例。
//    BOOL asdf = [subInstance isKindOfClass:[JTView class]];
//    if (asdf) {
//        NSLog(@"是子類");
//    }else {
//        NSLog(@"不是子類");
//    }
//    // 是否是給定類的實例
//    if ([subInstance isMemberOfClass:[JTView class]]) {
//        NSLog(@"是該類的子類");
//    }else {
//        NSLog(@"不是該類的子類");
//    }

3, 幾個面試題
http://blog.sunnyxx.com/2014/11/06/runtime-nuts/

4,+load 和 +initialize 方法

  • 調(diào)用方式不同,load方法是在main方法執(zhí)行之前,直接調(diào)用函數(shù),而initialize走的是消息機(jī)制
  • 調(diào)用時機(jī)不同, load方法是在main方法執(zhí)行之前, 類加載進(jìn)內(nèi)存的時候調(diào)用的,并且只要類實現(xiàn)了load方法必定調(diào)用。而initialize是惰性調(diào)用, 只有第一次使用類的時候才會調(diào)用
  • load方法的調(diào)用順序是(如果都實現(xiàn)的話)超類,子類,分類,對于多個分類的調(diào)用順序就不一定了。如果沒有實現(xiàn)就不調(diào)用,不會調(diào)用父類的load方法。也就是說一個類的load方法只調(diào)用一次,但是initialize可能調(diào)用多次。
  • initialize 是線程安全的, 如果子類沒有實現(xiàn)就會調(diào)用父類的方法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容