Runtime 運(yùn)行時(shí)
參考文章:
玉令天下的博客
特酷吧
南峰子的技術(shù)博客
顧 鵬
Objective-C是動(dòng)態(tài)語言,所以需要編譯器的同時(shí)也需要一個(gè)運(yùn)行時(shí)系統(tǒng)來動(dòng)態(tài)創(chuàng)建對(duì)象、傳遞消息。在你需要的時(shí)候還能對(duì)其進(jìn)行擴(kuò)展,解決問題。Objective-C的Runtime是一個(gè)運(yùn)行時(shí)的庫(Runtime Library),主要使用C和匯編寫的庫,為C添加了面向?qū)ο蟮哪芰Σ?chuàng)造了Objective-C。這個(gè)運(yùn)行時(shí)系統(tǒng)就像一個(gè)操作系統(tǒng)一樣:它讓所有的工作正常運(yùn)行。
有動(dòng)態(tài)類型(Dynamic typing),動(dòng)態(tài)綁定(Dynamic binding)和動(dòng)態(tài)加載(Dynamic loading)
Runtime有兩個(gè)版本:Modern和Legacy。Object—C2.0采用的是Modern版本的Runtime,只能在iOS和OS X10.5之后的64位程序上運(yùn)行。32位程序采用Legacy版本的Runtime。它們的區(qū)別在于更改實(shí)例變量時(shí),Legary需要重新編譯其子類,而Modern`則不需要。
封裝,在這個(gè)庫中,對(duì)象可以用C語言中的結(jié)構(gòu)體表示,而方法可以用C函數(shù)來實(shí)現(xiàn),這些結(jié)構(gòu)體和函數(shù)被runtime封裝后,可以創(chuàng)建檢查修改類對(duì)象和他們的方法。
找出方法的最終執(zhí)行代碼:當(dāng)程序執(zhí)行object doSomething時(shí),會(huì)向消息接受者發(fā)送一條消息,runtime會(huì)根據(jù)消息接受者是否能響應(yīng)該消息而做出不同的反應(yīng)。
在編譯時(shí)你寫的Objective-C 函數(shù)調(diào)用的語法都會(huì)被翻譯成一個(gè)C的函數(shù)調(diào)用,例如我們發(fā)送一個(gè)消息
[receiver message]會(huì)被編譯器轉(zhuǎn)化為[objc_msgSend(receiver,selector)]如果有參數(shù)的話則為[objc_msgSend(receiver,selector,arg1,arg2)],如果消息的接受者能夠找到對(duì)應(yīng)的方法,就執(zhí)行此方法,如果沒有,要么消息被轉(zhuǎn)發(fā),或是動(dòng)態(tài)添加此消息,以上都沒有的話程序就會(huì)crash。從Object-C是動(dòng)態(tài)語言理解的話就是,
[receiver message]在編譯的時(shí)候只是知道我將要向receiver發(fā)送一個(gè)message但是還沒發(fā)送甚至不知道是否有這個(gè)消息以及這個(gè)消息有沒有具體實(shí)現(xiàn),只有在程序 運(yùn)行時(shí)才回去查找實(shí)現(xiàn),所以說Runtime對(duì)于Object-C來說非常重要,但其實(shí)通過終端我們是可以看到轉(zhuǎn)換后的樣子的。
打開終端進(jìn)入工程目錄
找到工程的.main文件層級(jí) 輸入命令 clang -rewrite-objc main.m
文件夾內(nèi)就可以找到.cpp文件,打開可以看到很多C的代碼所以有人說OC是假的面向?qū)ο?/p>
- isMemberOfClass 只判斷是否屬于當(dāng)前類的 實(shí)例 至于父類是什么關(guān)系它不管
- isKindOfClass 則能判斷繼承關(guān)系
- respondsToSelector 判斷實(shí)例是否有這樣方法
- instancesRespondToSelector 判斷類是否有這個(gè)方法不能用在類的對(duì)象
Class為指向類的結(jié)構(gòu)體指針
typedef struct objc_class *Class;,該類的結(jié)構(gòu)體包含:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; // isa指針
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息,默認(rèn)為0
long info OBJC2_UNAVAILABLE; // 類信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
long instance_size OBJC2_UNAVAILABLE; // 該類的實(shí)例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
isa 指針指向objc_class結(jié)構(gòu)體的指針
- 每一個(gè)類的實(shí)例對(duì)象都有一個(gè)isa指針指向它的類,而每個(gè)類也有個(gè)isa指針指向它的元類 [metaClass] 元類里保存了類方法的列表。
NSObject *objc = [[NSObject alloc]init];
objc->isa; // 警告:會(huì)提示你用 **object_getClass(objc);**替換
- 當(dāng)實(shí)例對(duì)象調(diào)用實(shí)例方法時(shí),isa指針會(huì)到該實(shí)例方法的類
Subclass(class)中去查詢是否有此實(shí)例方法,如果沒有則會(huì)去它的父類Superclass(class)中去查找,一層一層向上查找。 - 當(dāng)調(diào)用類方法時(shí),isa指針會(huì)去該類元類中去查找
Subclass(meta)如果沒有則會(huì)去到元類的父類中去查找Superclass(meta) - 萬物皆對(duì)象,元類也不例外,元類也有它的isa指針,元類的isa指針只指向元類的根 根元類Root Class(meta) 同理根源類也是對(duì)象,它的isa指針指向 自己本身 因?yàn)楦愐呀?jīng)是最頂層的根類就像NSObject或NSProxy,它的父類則指向nil。
上面所述的三條總結(jié)起來就如下圖:

為什么[NSObject foo]可以調(diào)用實(shí)例方法?

Method 方法的類型
typedef struct objc_method *Method;
objc_method結(jié)構(gòu)體如下
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
-
method_name很顯然是方法名 類型是SEL稍后解釋。 -
method_typeschar類型的指針用來存儲(chǔ)參數(shù)類型和返回值類型。 -
method_impIMP類型 是個(gè)函數(shù)指針 稍后解釋。
SEL
等同于Object-C中selector,selector是方法選擇器
typedef struct objc_selector *SEL;SEL是指向objc_selector的指針,不同類中相同名字的方法所對(duì)應(yīng)的方法選擇器是相同的,即使方法名字相同而變量類型不同也會(huì)導(dǎo)致它們具有相同的方法選擇器 因?yàn)椴煌惖膶?shí)例對(duì)象用相同的方法選擇器時(shí),會(huì)在各自的消息選擇、實(shí)現(xiàn)地址、方法鏈表中根據(jù) selector 去查找具體的方法實(shí)現(xiàn)IMP。這也是動(dòng)態(tài)的過程,也就是說在運(yùn)行之前甚至編譯的時(shí)候我們都不知道最終會(huì)執(zhí)行哪行代碼。
IMP
IMP在objc中的定義為`typedef id (*IMP)(id, SEL, ...);
前面也提到它是個(gè) 函數(shù)指針上面SEL里提到只有在編譯的時(shí)候才會(huì)知道最終實(shí)現(xiàn)哪行代碼,就是因?yàn)榫幾g時(shí)IMP這個(gè)函數(shù)指針指向了最終實(shí)現(xiàn)的代碼。方法名相同的時(shí)候通過IMP的參數(shù)類型就能將它們區(qū)分開找到特定的那個(gè)方法的實(shí)現(xiàn)。
Cache
[receiver message]當(dāng)對(duì)象接收message時(shí)isa指針會(huì)去對(duì)應(yīng)的對(duì)象中去查找該message,就相當(dāng)于遍歷MethodList,但是只有message這一個(gè)方法是我們所需要的,每次接收message時(shí)就要遍歷一遍MethodList,這樣太低效。所以引入了cache,將我們使用過的方法放到緩存中下次調(diào)用時(shí)先去cache中查找如果沒有再去MethodList中遍歷。objc_cache結(jié)構(gòu)體指針:
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
南峰子的技術(shù)博客對(duì)objc_cache結(jié)構(gòu)體描述的很詳細(xì):
- mask:一個(gè)整數(shù),指定分配的緩存bucket的總數(shù)。在方法查找過程中,Objective-C runtime使用這個(gè)字段來確定開始線性查找數(shù)組的索引位置。指向方法selector的指針與該字段做一個(gè)AND位操作(index = (mask & selector))。這可以作為一個(gè)簡單的hash散列算法。
- occupied:一個(gè)整數(shù),指定實(shí)際占用的緩存bucket的總數(shù)。
- buckets:指向Method數(shù)據(jù)結(jié)構(gòu)指針的數(shù)組。這個(gè)數(shù)組可能包含不超過mask+1個(gè)元素。需要注意的是,指針可能是NULL,表示這個(gè)緩存bucket沒有被占用,另外被占用的bucket可能是不連續(xù)的。這個(gè)數(shù)組可能會(huì)隨著時(shí)間而增長
Ivar 實(shí)例變量的類型
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE; // 名稱
char *ivar_type OBJC2_UNAVAILABLE; // 類型
int ivar_offset OBJC2_UNAVAILABLE; // 偏移量
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
id是一個(gè)objc_object類型指針
typedef struct objc_object *id;
引入頭文件
#import <objc/runtime.h>
#import <objc/message.h>
`#import <objc/runtime.h>`
`#import <objc/message.h>`
Class cls = [per class];// 獲取自己的類
const char *className = class_getName(cls); // 獲取類名
NSLog(@"%s",className);
// 2. meta-class 元類
// 1>當(dāng)我們調(diào)用實(shí)例方法時(shí),系統(tǒng)會(huì)去類的列表里找對(duì)應(yīng)名字的方法
// 2>當(dāng)我們調(diào)用類方法時(shí),系統(tǒng)會(huì)去meta-class(元類)的列表中找對(duì)應(yīng)名字的方法
// 尋找對(duì)象的元類 object_getClass(填入的是類對(duì)象)
Class metaClass = object_getClass(cls);
BOOL isMetaClass = class_isMetaClass(metaClass);// 判斷是不是元類
NSLog(@"是否是元類%d",isMetaClass);
// 3. 獲取屬性列表 如果有屬性count就有值
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(cls,&count);
// 循環(huán)遍歷類獲取其屬性
for (int i = 0; i < count; i++) {
Ivar ivar = ivarList[i];
NSLog(@"Person類的成員變量列表下標(biāo)%d以及屬性名%s",i,ivar_getName(ivar)); // _name _sex _age _hobby
}
// 4.獲取屬性列表
unsigned int outCount = 0;
objc_property_t *properList = class_copyPropertyList(cls, &outCount);
for (int i = 0; i < outCount; i++) {
objc_property_t property = properList[i];
unsigned int attributeCount = 0;
objc_property_attribute_t *attribute = property_copyAttributeList(property,&attributeCount); // 屬性列表內(nèi)部
for (int j = 0 ; j < attributeCount; j++) {
NSLog(@"%s : %s",attribute[j].name,attribute[j].value);
}
NSLog(@"Person類的屬性列表下標(biāo)%d以及屬性名%s",i,property_getName(property));
}
// 方法列表
unsigned int methCount= 0;
Method *methList = class_copyMethodList(cls,&methCount);
for (int i = 0; i < methCount; i++) {
Method meth = methList[i];
NSLog(@"Person方法名%@",NSStringFromSelector(method_getName(meth)));
}