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_types
char類型的指針用來存儲(chǔ)參數(shù)類型和返回值類型恶迈。 -
method_imp
IMP類型 是個(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)));
}