已更新
深入淺出Runtime (二) Runtime的消息機(jī)制
深入淺出Runtime (三) Runtime的消息轉(zhuǎn)發(fā)
深入淺出Runtime (四) Runtime的實(shí)際應(yīng)用之一妓蛮,字典轉(zhuǎn)模型
深入淺出 Runtime詳解
Runtime是什么?
運(yùn)行時(shí)(Runtime)是指將數(shù)據(jù)類型的確定由編譯時(shí)推遲到了運(yùn)行時(shí)Runtime是一套比較底層的純C語言API, 屬于1個(gè)C語言庫, 包含了很多底層的C語言API平時(shí)編寫的OC代碼圾叼,在程序運(yùn)行過程中蛤克,其實(shí)最終會(huì)轉(zhuǎn)換成Runtime的C語言代碼,Runtime是Object-C的幕后工作者Object-C需要Runtime來創(chuàng)建類和對象夷蚊,進(jìn)行消息發(fā)送和轉(zhuǎn)發(fā)
更新(上面描述并不是不對构挤,而是覺得不嚴(yán)謹(jǐn))
- 將盡可能多的決策從編譯時(shí)和鏈接時(shí)推遲到運(yùn)行時(shí)(Apple)
- 運(yùn)行時(shí)系統(tǒng)充當(dāng)著Object-C語言的操作系統(tǒng),它使語言能夠工作(Apple)
特性: 編寫的代碼具有運(yùn)行時(shí)惕鼓、動(dòng)態(tài)特性
Runtime用來干什么筋现?用在哪些地方?
Runtime在Object-C的使用
Objective-C程序在三個(gè)不同的層次上與運(yùn)行時(shí)系統(tǒng)交互:
- 通過Object-C源代碼進(jìn)行交互
- 通過NSObject類中定義的方法交互
- 通過直接調(diào)用運(yùn)行時(shí)函數(shù)
用來干什么 基本作用
- 在程序運(yùn)行過程中箱歧,動(dòng)態(tài)的創(chuàng)建類夫否,動(dòng)態(tài)添加、修改這個(gè)類的屬性和方法叫胁;
- 遍歷一個(gè)類中所有的成員變量凰慈、屬性、以及所有方法
- 消息傳遞驼鹅、轉(zhuǎn)發(fā)
用在哪些地方 Runtime的典型事例
- 給系統(tǒng)分類添加屬性微谓、方法
- 方法交換
- 獲取對象的屬性森篷、私有屬性
- 字典轉(zhuǎn)換模型
- 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類型來表示的仲智,它實(shí)際上是一個(gè)指向objc_class結(jié)構(gòu)體的指針
/// 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
的一個(gè)指針,而objc_object
有個(gè)isa
指向objc_class
的一個(gè)指針
So,不管id
肴焊,還是Class
最后指向的都是objc_class
這個(gè)結(jié)構(gòu)體
objc_class
結(jié)構(gòu)體中的定義如下:
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使用當(dāng)中前联,我們經(jīng)常需要用到的字段,它們的定義
-
isa
Class
對象娶眷,指向objc_class
結(jié)構(gòu)體的指針似嗤,也就是這個(gè)Class
的MetaClass
(元類)
- 類的實(shí)例對象的 isa 指向該類;該類的 isa 指向該類的 MetaClass
- MetaCalss的isa對象指向RootMetaCalss -
super_class Class對象指向父類對象
- 如果該類的對象已經(jīng)是RootClass,那么這個(gè)super_class
指向nil
- MetaCalss的SuperClass指向父類的MetaCalss
- MetaCalss是RootMetaCalss届宠,那么該MetaClass的SuperClass指向該對象的RootClass
一張圖可以完美的解釋這個(gè)知識(shí)點(diǎn)
ivars 類中所有屬性的列表烁落,使用場景:我們在字典轉(zhuǎn)換成模型的時(shí)候需要用到這個(gè)列表找到屬性的名稱,去取字典中的值豌注,KVC賦值伤塌,或者直接Runtime賦值
methodLists 類中所有的方法的列表,類中所有方法的列表轧铁,使用場景:如在程序中寫好方法寸谜,通過外部獲取到方法名稱字符串,然后通過這個(gè)字符串得到方法属桦,從而達(dá)到外部控制App已知方法。
cache 主要用于緩存常用方法列表他爸,每個(gè)類中有很多方法聂宾,我平時(shí)不用的方法也會(huì)在里面,每次運(yùn)行一個(gè)方法诊笤,都要去
methodLists
遍歷得到方法系谐,如果類的方法不多還行,但是基本的類中都會(huì)有很多方法讨跟,這樣勢必會(huì)影響程序的運(yùn)行效率纪他,所以cache
在這里就會(huì)被用上,當(dāng)我們使用這個(gè)類的方法時(shí)先判斷cache
是否為空晾匠,為空從methodLists
找到調(diào)用茶袒,并保存到cache
,不為空先從cache
中找方法凉馆,如果找不到在去methodLists
薪寓,這樣提高了程序方法的運(yùn)行效率protocols 故名思義亡资,這個(gè)類中都遵守了哪些協(xié)議,使用場景:判斷類是否遵守了某個(gè)協(xié)議上
深入Runtime的運(yùn)行原理
當(dāng)我寫到深入Runtime的運(yùn)行原理的時(shí)候,腦海中冒出的想法是怎么深入向叉,從哪里開始挖掘runtime的內(nèi)容:
第一個(gè)想法就是
- 介紹runtime的幾個(gè)方法
- runtime的使用方法
- runtime的實(shí)際操作場景锥腻、應(yīng)用
- runtime的總結(jié)
如果說這些就是深入runtime,就是調(diào)用下api
然后想了很久母谎,每次都會(huì)想就直接介紹使用瘦黑,總結(jié)完事;心里賊不得勁
不能就這么簡簡單單了事奇唤,那么開始想到哪點(diǎn)幸斥,深入做哪點(diǎn)深入
大綱(后續(xù)會(huì)補(bǔ)充):
- 類底層代碼、類的本質(zhì)冻记?
- 類底層是如何調(diào)用方法睡毒?
- Runtime消息傳遞
- Runtime消息轉(zhuǎn)發(fā)
- Runtime起到了什么作用?
- Runtime實(shí)際應(yīng)用
類底層代碼冗栗、類的本質(zhì)演顾?
為了更好的認(rèn)識(shí)類是怎么工作的,我們將要將一段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
使用命令隅居,在當(dāng)前文件夾中會(huì)出現(xiàn)Game.cpp的文件
# clang -rewrite-objc Game.m
Game.cpp
由于生成的文件很龐大钠至,可以仔細(xì)去研讀,受益匪淺
研讀方式:如果按照從上往下的順序去研讀胎源,會(huì)很不理解棉钧,所以我的研讀方式從關(guān)鍵點(diǎn)切入首先理解關(guān)鍵的幾點(diǎn),然后在慢慢拋析
/*
* 顧名思義存放property的結(jié)構(gòu)體
* 當(dāng)我們使用perproty的時(shí)候涕蚤,會(huì)生成這樣一個(gè)結(jié)構(gòu)體
* 具體存儲(chǔ)的數(shù)據(jù)為
* 實(shí)際內(nèi)容:"Name","T@\"NSString\",C,N,VName"
* 原型:@property (copy,nonatomic)NSString *Name;
* 這個(gè)具體是怎么實(shí)現(xiàn)的宪卿,我會(huì)在后面繼續(xù)深入研究,本文主要來理解runtime的理解
**/
struct _prop_t {
const char *name; //名字
const char *attributes; //屬性
};
/*
*類中方法的結(jié)構(gòu)體,cmd和imp的關(guān)系是一一對應(yīng)的關(guān)系
*創(chuàng)建對象生成isa指針万栅,指向這個(gè)對象的結(jié)構(gòu)體時(shí)
*同時(shí)生成了一個(gè)表"Dispatch table"通過這個(gè)_cmd的編號找到對應(yīng)方法
*使用場景:
*例如方法交換佑钾,方法判斷。烦粒。休溶。
**/
struct _objc_method {
struct objc_selector * _cmd; //SEL 對應(yīng)著OC中的@selector()
const char *method_type; //方法的類型
void *_imp; //方法的地址
};
/*
* method_list_t 結(jié)構(gòu)體:
* 原型:
* - (void)GiveThisGameName:(NSString *)name;
* 實(shí)際存儲(chǔ)的方式:
* {(struct objc_selector *)"GiveThisGameName:", "v24@0:8@16", (void *)_I_Game_GiveThisGameName_}
* 其主要目的是存儲(chǔ)一個(gè)數(shù)組扰她,基本的數(shù)據(jù)類型是 _objc_method
* 擴(kuò)展:當(dāng)然這其中有你的屬性兽掰,自動(dòng)生成的setter、getter方法
**/
static struct _method_list_t {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[6];
}
/*
* 表示這個(gè)類中所遵守的協(xié)議對象
* 使用場景:
* 判斷類是否遵守這個(gè)協(xié)議徒役,從而動(dòng)態(tài)添加孽尽、重寫、交換某些方法忧勿,來達(dá)到某些目的
*
**/
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; // 實(shí)例方法
const struct method_list_t *class_methods; //類方法
const struct method_list_t *optionalInstanceMethods; //可選的實(shí)例方法
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; //擴(kuò)展的方法類型
};
/*
* 類的變量的結(jié)構(gòu)體
* 原型:
* NSString *Name;
* 存儲(chǔ)內(nèi)容:
* {(unsigned long int *)&OBJC_IVAR_$_Game$Name, "Name", "@\"NSString\"", 3, 8}
* 根據(jù)存儲(chǔ)內(nèi)容可以大概了解這些屬性的工作內(nèi)容
**/
struct _ivar_t {
unsigned long int *offset; // pointer to ivar offset location
const char *name; //名字
const char *type; //屬于什么變量
unsigned int alignment; //未知
unsigned int size; //大小
};
/*
* 這個(gè)就是類中的各種方法泻云、屬性艇拍、等等信息
* 底層也是一個(gè)結(jié)構(gòu)體
* 名稱、方法列表宠纯、協(xié)議列表卸夕、變量列表、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; //協(xié)議列表
const struct _ivar_list_t *ivars; //變量列表
const unsigned char *weakIvarLayout; //弱引用布局
const struct _prop_list_t *properties; //屬性列表
};
/*
* 類本身
* oc在創(chuàng)建類的時(shí)候都會(huì)創(chuàng)建一個(gè) _class_t的結(jié)構(gòu)體
* 我的理解是在runtime中的object-class結(jié)構(gòu)體在底層就會(huì)變成_class_t結(jié)構(gòu)體
**/
struct _class_t {
struct _class_t *isa; //元類的指針
struct _class_t *superclass; //父類的指針
void *cache; //緩存
void *vtable; //表信息廉白、未知
struct _class_ro_t *ro; //這個(gè)就是類中的各種方法个初、屬性、等等信息
};
/*
* 類擴(kuò)展的結(jié)構(gòu)體
* 在OC中寫的分類
**/
struct _category_t {
const char *name; //名稱
struct _class_t *cls; //這個(gè)是哪個(gè)類的擴(kuò)展
const struct _method_list_t *instance_methods; //實(shí)例方法列表
const struct _method_list_t *class_methods; //類方法列表
const struct _protocol_list_t *protocols; //協(xié)議列表
const struct _prop_list_t *properties; //屬性列表
};
上述是Object-C中類中基本的數(shù)據(jù)猴蹂,了解了類的定義院溺,我們基本可以這么理解,類就是多個(gè)結(jié)構(gòu)體組合的一個(gè)集合體磅轻,類中的行為珍逸、習(xí)慣、屬性抽象聋溜,按照機(jī)器能懂的數(shù)據(jù)存儲(chǔ)到我們底層的結(jié)構(gòu)體當(dāng)中谆膳,在我們需要使用的時(shí)候直接獲取使用。
那么就開始研究一下撮躁,類是如何使用漱病,類的基本使用過程以及過程中runtime所做的事情。
類底層是如何調(diào)用方法把曼?
了解了類的組成杨帽,那么類是通過什么樣的形式去獲取方法屬性并得到應(yīng)用?
在Object-C開發(fā)中我們經(jīng)常會(huì)說到,對象調(diào)用方法嗤军,其本質(zhì)就是想這個(gè)對象發(fā)送消息注盈,為什么會(huì)有這么一說?下面我們來驗(yàn)證一下
Object-C代碼
int main(int argc, char * argv[]) {
Game *game = [Game alloc];
[game init];
[game Play];
return 0;
}
底層代碼的實(shí)現(xiàn)
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
消息發(fā)送 -
objc_getClass
獲取對象 -
sel_registerName
獲取方法的SEL
因?yàn)槟壳爸攸c(diǎn)是objc_msgSend
型雳,其他的Runtime的方法會(huì)在后面繼續(xù)一一道來, So 一個(gè)對象調(diào)用其方法,在Object-C中就是向這個(gè)對象發(fā)送一條消息山害,消息的格式
objc_msgSend("對象","SEL","參數(shù)"...)
objc_msgSend( id self, SEL op, ... )
總結(jié)
Rumtime是Objective-C語言動(dòng)態(tài)的核心纠俭,Objective-C的對象一般都是基于Runtime的類結(jié)構(gòu),達(dá)到很多在編譯時(shí)確定方法推遲到了運(yùn)行時(shí)浪慌,從而達(dá)到動(dòng)態(tài)修改冤荆、確定、交換...屬性及方法