已更新
深入淺出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來創建類和對象,進行消息發送和轉發
更新(上面描述并不是不對,而是覺得不嚴謹)
特性: 編寫的代碼具有運行時、動態特性
Runtime用來干什么?用在哪些地方?
Runtime在Object-C的使用
Objective-C程序在三個不同的層次上與運行時系統交互:
- 通過Object-C源代碼進行交互
- 通過NSObject類中定義的方法交互
- 通過直接調用運行時函數
用來干什么 基本作用
- 在程序運行過程中,動態的創建類,動態添加、修改這個類的屬性和方法;
- 遍歷一個類中所有的成員變量、屬性、以及所有方法
- 消息傳遞、轉發
用在哪些地方 Runtime的典型事例
- 給系統分類添加屬性、方法
- 方法交換
- 獲取對象的屬性、私有屬性
- 字典轉換模型
- KVC、KVO
- 歸檔(編碼、解碼)
- NSClassFromString class<->字符串
- block
- 類的自我檢測
- ...
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
結構體的指針,也就是這個Class
的MetaClass
(元類)
- 類的實例對象的 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的類結構,達到很多在編譯時確定方法推遲到了運行時,從而達到動態修改、確定、交換...屬性及方法