深入淺出Runtime (一) 什么是Runtime? 定義?

已更新
深入淺出Runtime (二) Runtime的消息機制
深入淺出Runtime (三) Runtime的消息轉發
深入淺出Runtime (四) Runtime的實際應用之一,字典轉模型

深入淺出 Runtime詳解

Runtime是什么?

  • 運行時(Runtime)是指將數據類型的確定由編譯時推遲到了運行時
  • Runtime是一套比較底層的純C語言API, 屬于1個C語言庫, 包含了很多底層的C語言API
  • 平時編寫的OC代碼,在程序運行過程中,其實最終會轉換成Runtime的C語言代碼,Runtime是Object-C的幕后工作者
  • Object-C需要Runtime來創建類和對象,進行消息發送和轉發

更新(上面描述并不是不對,而是覺得不嚴謹)

  • 將盡可能多的決策從編譯時和鏈接時推遲到運行時(Apple
  • 運行時系統充當著Object-C語言的操作系統,它使語言能夠工作(Apple)

特性: 編寫的代碼具有運行時、動態特性

Runtime用來干什么?用在哪些地方?

Runtime在Object-C的使用
Objective-C程序在三個不同的層次上與運行時系統交互:

  • 通過Object-C源代碼進行交互
  • 通過NSObject類中定義的方法交互
  • 通過直接調用運行時函數

用來干什么 基本作用

  • 在程序運行過程中,動態的創建類,動態添加、修改這個類的屬性和方法;
  • 遍歷一個類中所有的成員變量、屬性、以及所有方法
  • 消息傳遞、轉發

用在哪些地方 Runtime的典型事例

  • 給系統分類添加屬性、方法
  • 方法交換
  • 獲取對象的屬性、私有屬性
  • 字典轉換模型
  • KVC、KVO
  • 歸檔(編碼、解碼)
  • NSClassFromString class<->字符串
  • block
  • 類的自我檢測
  • ...

Runtime的定義

Runtime開源代碼

在Object-C中的NSObject對象中

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

上述表述Objective-C類是由Class類型來表示的,它實際上是一個指向objc_class結構體的指針

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

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

由此可見可以看到Class、id 前者是類,后者指向類的指針,id是指向objc_object的一個指針,而objc_object有個isa指向objc_class的一個指針
So,不管id,還是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;
/* Use `Class` instead of `struct objc_class *` */

在runtime使用當中,我們經常需要用到的字段,它們的定義

  • isa Class對象,指向objc_class結構體的指針,也就是這個ClassMetaClass(元類)
    - 類的實例對象的 isa 指向該類;該類的 isa 指向該類的 MetaClass
    - MetaCalss的isa對象指向RootMetaCalss
  • super_class Class對象指向父類對象
    - 如果該類的對象已經是RootClass,那么這個super_class指向nil
    - MetaCalss的SuperClass指向父類的MetaCalss
    - MetaCalss是RootMetaCalss,那么該MetaClass的SuperClass指向該對象的RootClass

一張圖可以完美的解釋這個知識點


  • ivars 類中所有屬性的列表,使用場景:我們在字典轉換成模型的時候需要用到這個列表找到屬性的名稱,去取字典中的值,KVC賦值,或者直接Runtime賦值

  • methodLists 類中所有的方法的列表,類中所有方法的列表,使用場景:如在程序中寫好方法,通過外部獲取到方法名稱字符串,然后通過這個字符串得到方法,從而達到外部控制App已知方法。

  • cache 主要用于緩存常用方法列表,每個類中有很多方法,我平時不用的方法也會在里面,每次運行一個方法,都要去methodLists遍歷得到方法,如果類的方法不多還行,但是基本的類中都會有很多方法,這樣勢必會影響程序的運行效率,所以cache在這里就會被用上,當我們使用這個類的方法時先判斷cache是否為空,為空從methodLists找到調用,并保存到cache,不為空先從cache中找方法,如果找不到在去methodLists,這樣提高了程序方法的運行效率

  • protocols 故名思義,這個類中都遵守了哪些協議,使用場景:判斷類是否遵守了某個協議上

深入Runtime的運行原理

當我寫到深入Runtime的運行原理的時候,腦海中冒出的想法是怎么深入,從哪里開始挖掘runtime的內容:

第一個想法就是

  • 介紹runtime的幾個方法
  • runtime的使用方法
  • runtime的實際操作場景、應用
  • runtime的總結

如果說這些就是深入runtime,就是調用下api

然后想了很久,每次都會想就直接介紹使用,總結完事;心里賊不得勁
不能就這么簡簡單單了事,那么開始想到哪點,深入做哪點深入

大綱(后續會補充):

  • 類底層代碼、類的本質?
  • 類底層是如何調用方法?
  • Runtime消息傳遞
  • Runtime消息轉發
  • Runtime起到了什么作用?
  • Runtime實際應用

類底層代碼、類的本質?

為了更好的認識類是怎么工作的,我們將要將一段Object-C的代碼用clang看下底層的C/C++的寫法

typedef enum : NSUInteger {
    ThisRPGGame = 0,
    ThisActionGame = 1,
    ThisBattleFlagGame = 2,
} ThisGameType;


@interface Game : NSObject
@property (copy,nonatomic)NSString *Name;
@property (assign,nonatomic)ThisGameType Type;
@end

@implementation Game
@synthesize Name,Type;

- (void)GiveThisGameName:(NSString *)name{
    Name = name;
}

- (void)GiveThisGameType:(ThisGameType)type{
    Type = type;
}

@end

使用命令,在當前文件夾中會出現Game.cpp的文件

# clang -rewrite-objc Game.m

Game.cpp
由于生成的文件很龐大,可以仔細去研讀,受益匪淺

研讀方式:如果按照從上往下的順序去研讀,會很不理解,所以我的研讀方式從關鍵點切入首先理解關鍵的幾點,然后在慢慢拋析

/*
 * 顧名思義存放property的結構體
 * 當我們使用perproty的時候,會生成這樣一個結構體
 * 具體存儲的數據為 
 * 實際內容:"Name","T@\"NSString\",C,N,VName" 
 * 原型:@property (copy,nonatomic)NSString *Name;
 * 這個具體是怎么實現的,我會在后面繼續深入研究,本文主要來理解runtime的理解
 **/
struct _prop_t {
    const char *name;        //名字
    const char *attributes;  //屬性
};

/*
 *類中方法的結構體,cmd和imp的關系是一一對應的關系
 *創建對象生成isa指針,指向這個對象的結構體時 
 *同時生成了一個表"Dispatch table"通過這個_cmd的編號找到對應方法
 *使用場景:
 *例如方法交換,方法判斷。。。
 **/ 
struct _objc_method {
    struct objc_selector * _cmd;   //SEL 對應著OC中的@selector()
    const char *method_type;       //方法的類型
    void  *_imp;                   //方法的地址
}; 


/*
 * method_list_t 結構體:
 * 原型:
 * - (void)GiveThisGameName:(NSString *)name;
 * 實際存儲的方式:
 * {(struct objc_selector *)"GiveThisGameName:", "v24@0:8@16", (void *)_I_Game_GiveThisGameName_}
 * 其主要目的是存儲一個數組,基本的數據類型是 _objc_method
 * 擴展:當然這其中有你的屬性,自動生成的setter、getter方法
 **/
 
static struct _method_list_t {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[6];
}

/*
 * 表示這個類中所遵守的協議對象
 * 使用場景:
 * 判斷類是否遵守這個協議,從而動態添加、重寫、交換某些方法,來達到某些目的
 * 
 **/

struct _protocol_t {
    void * isa;  // NULL
    const char *protocol_name;
    const struct _protocol_list_t * protocol_list; // super protocols
    const struct method_list_t *instance_methods;  // 實例方法
    const struct method_list_t *class_methods;     //類方法
    const struct method_list_t *optionalInstanceMethods;  //可選的實例方法
    const struct method_list_t *optionalClassMethods;  //可選的類方法
    const struct _prop_list_t * properties;  //屬性列表
    const unsigned int size;  // sizeof(struct _protocol_t)
    const unsigned int flags;  // = 0
    const char ** extendedMethodTypes;  //擴展的方法類型
};

/*
 * 類的變量的結構體
 * 原型:
 * NSString *Name;
 * 存儲內容:
 * {(unsigned long int *)&OBJC_IVAR_$_Game$Name, "Name", "@\"NSString\"", 3, 8}
 * 根據存儲內容可以大概了解這些屬性的工作內容
 **/
struct _ivar_t {
    unsigned long int *offset;  // pointer to ivar offset location
    const char *name;  //名字
    const char *type;  //屬于什么變量
    unsigned int alignment; //未知
    unsigned int  size;    //大小
};


/*
 *  這個就是類中的各種方法、屬性、等等信息
 *  底層也是一個結構體
 *  名稱、方法列表、協議列表、變量列表、layout、properties。。
 *  
 **/
struct _class_ro_t {
    unsigned int flags;
    unsigned int instanceStart;
    unsigned int instanceSize;
    unsigned int reserved;
    const unsigned char *ivarLayout;  //布局
    const char *name;  //名字
    const struct _method_list_t *baseMethods;//方法列表 
    const struct _objc_protocol_list *baseProtocols; //協議列表
    const struct _ivar_list_t *ivars;  //變量列表
    const unsigned char *weakIvarLayout;  //弱引用布局
    const struct _prop_list_t *properties;  //屬性列表
};

/*
 * 類本身
 * oc在創建類的時候都會創建一個 _class_t的結構體
 * 我的理解是在runtime中的object-class結構體在底層就會變成_class_t結構體
 **/
struct _class_t {
    struct _class_t *isa;  //元類的指針
    struct _class_t *superclass; //父類的指針
    void *cache;   //緩存
    void *vtable;  //表信息、未知
    struct _class_ro_t *ro;  //這個就是類中的各種方法、屬性、等等信息
};


/*
 * 類擴展的結構體
 * 在OC中寫的分類
 **/
struct _category_t {
    const char *name;  //名稱
    struct _class_t *cls;  //這個是哪個類的擴展
    const struct _method_list_t *instance_methods;  //實例方法列表
    const struct _method_list_t *class_methods;     //類方法列表
    const struct _protocol_list_t *protocols;       //協議列表
    const struct _prop_list_t *properties;          //屬性列表
};

上述是Object-C中類中基本的數據,了解了類的定義,我們基本可以這么理解,類就是多個結構體組合的一個集合體,類中的行為、習慣、屬性抽象,按照機器能懂的數據存儲到我們底層的結構體當中,在我們需要使用的時候直接獲取使用。

那么就開始研究一下,類是如何使用,類的基本使用過程以及過程中runtime所做的事情。

類底層是如何調用方法?

了解了類的組成,那么類是通過什么樣的形式去獲取方法屬性并得到應用?
在Object-C開發中我們經常會說到,對象調用方法,其本質就是想這個對象發送消息,為什么會有這么一說?下面我們來驗證一下

Object-C代碼

int main(int argc, char * argv[]) {

    Game *game = [Game alloc];
    [game init];
    [game Play];
    return  0;
}

底層代碼的實現

int main(int argc, char * argv[]) {

    Game *game = ((Game *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Game"), sel_registerName("alloc"));
    game = ((Game *(*)(id, SEL))(void *)objc_msgSend)((id)game, sel_registerName("init"));
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)game, sel_registerName("Play"));
    return 0;
}

代碼中使用了

  • objc_msgSend 消息發送
  • objc_getClass 獲取對象
  • sel_registerName 獲取方法的SEL

因為目前重點是objc_msgSend,其他的Runtime的方法會在后面繼續一一道來, So 一個對象調用其方法,在Object-C中就是向這個對象發送一條消息,消息的格式

objc_msgSend("對象","SEL","參數"...)
objc_msgSend( id self, SEL op, ... )

總結

Rumtime是Objective-C語言動態的核心,Objective-C的對象一般都是基于Runtime的類結構,達到很多在編譯時確定方法推遲到了運行時,從而達到動態修改、確定、交換...屬性及方法

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容