一,RunTime介紹
Runtime基本上是用C和匯編寫的寞缝,這個庫使得C語言有了面向?qū)ο蟮哪芰Α?br>
在Runtime中符欠,對象可以用C語言中的結(jié)構(gòu)體表示,而方法可以用C函數(shù)來實現(xiàn)衙荐,另外再加上了一些額外的特性捞挥。這些結(jié)構(gòu)體和函數(shù)被runtime函數(shù)封裝后,讓OC的面向?qū)ο缶幊套優(yōu)榭赡堋?/p>
找出方法的最終執(zhí)行代碼:當程序執(zhí)行[object doSomething]時忧吟,會向消息接收者(object)發(fā)送一條消息(doSomething)砌函,runtime會根據(jù)消息接收者是否能響應(yīng)該消息而做出不同的反應(yīng)。
二 運行時的數(shù)據(jù)類型
Objective-C類是由Class類型來表示的,它實際上是一個指
向objc_class結(jié)構(gòu)體的指針讹俊。
class的實質(zhì)
objc_object
objc_object是表示一個類的實例的結(jié)構(gòu)體
它的定義如下(objc/objc.h):
struct objc_object{
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
可以看出該結(jié)構(gòu)體只有一個字體垦沉,即指向該對象的的isa指針,當我們向該對象發(fā)消息時仍劈,運行時庫會根據(jù)isa指針找到該實例對象所屬的類厕倍,在該類和其父類的類方法列表中找到與消息對應(yīng)的selector指向的方法并運行該方法。
Category
Category是表示一個指向分類的結(jié)構(gòu)體的指針贩疙,其定義如下:
typedef struct objc_category *Category
struct objc_category{
char *category_name? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE; // 分類名
char *class_name? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;? // 分類所屬的類名
struct objc_method_list *instance_methods? OBJC2_UNAVAILABLE;? // 實例方法列表
struct objc_method_list *class_methods? ? ? OBJC2_UNAVAILABLE; // 類方法列表
struct objc_protocol_list *protocols? ? ? ? OBJC2_UNAVAILABLE; // 分類所實現(xiàn)的協(xié)議列表
}
這個結(jié)構(gòu)體主要包含了分類定義的實例方法與類方法绑青,其中instance_methods列表是objc_class中方法列表的一個子集,而class_methods列表是元類方法列表的一個子集屋群。
可發(fā)現(xiàn)闸婴,類別中沒有ivar成員變量指針,也就意味著:類別中不能夠添加實例變量和屬性
RunTime 關(guān)聯(lián)對象
1.設(shè)置關(guān)聯(lián)值
參數(shù)說明:
object:與誰關(guān)聯(lián)芍躏,通常是傳self
key:唯一鍵邪乍,在獲取值時通過該鍵獲取,通常是使用static
const void *來聲明
value:關(guān)聯(lián)所設(shè)置的值
policy:內(nèi)存管理策略对竣,比如使用copy
void objc_setAssociatedObject(id object, const void *key, id value, objc _AssociationPolicy policy)
2.獲取關(guān)聯(lián)值
參數(shù)說明:
object:與誰關(guān)聯(lián)庇楞,通常是傳self,在設(shè)置關(guān)聯(lián)時所指定的與哪個對象關(guān)聯(lián)的那個對象
key:唯一鍵否纬,在設(shè)置關(guān)聯(lián)時所指定的鍵
id objc_getAssociatedObject(id object, const void *key)
3.取消關(guān)聯(lián)
void objc_removeAssociatedObjects(id object)
關(guān)聯(lián)策略
使用場景:
可以在類別中添加屬性
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy){
OBJC_ASSOCIATION_ASSIGN = 0,? ? ? ? ? ? // 表示弱引用關(guān)聯(lián)吕晌,通常是基本數(shù)據(jù)類型
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,? // 表示強引用關(guān)聯(lián)對象,是線程安全的
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,? ? // 表示關(guān)聯(lián)對象copy临燃,是線程安全的
OBJC_ASSOCIATION_RETAIN = 01401,? ? ? ? // 表示強引用關(guān)聯(lián)對象睛驳,不是線程安全的
OBJC_ASSOCIATION_COPY = 01403? ? ? ? ? ? // 表示關(guān)聯(lián)對象copy,不是線程安全的
};
四膜廊、方法與消息
1乏沸、SEL
SEL又叫選擇器,是表示一個方法的selector的指針爪瓜,其定義如下:
typedef struct objc_selector *SEL蹬跃;
方法的selector用于表示運行時方法的名字。Objective-C在編譯時铆铆,會依據(jù)每一個方法的名字蝶缀、參數(shù)序列,生成一個唯一的整型標識(Int類型的地址)薄货,這個標識就是SEL翁都。
兩個類之間,只要方法名相同菲驴,那么方法的SEL就是一樣的荐吵,每一個方法都對應(yīng)著一個SEL骑冗。所以在Objective-C同一個類(及類的繼承體系)中赊瞬,不能存在2個同名的方法先煎,即使參數(shù)類型不同也不行
通過下面三種方法可以獲取SEL:
a、sel_registerName函數(shù)
b巧涧、Objective-C編譯器提供的@selector()
c薯蝎、NSSelectorFromString()方法
2、IMP
IMP實際上是一個函數(shù)指針谤绳,指向方法實現(xiàn)的地址占锯。
其定義如下:
id (*IMP)(id, SEL,...)
第一個參數(shù):是指向self的指針(如果是實例方法,則是類實例的內(nèi)存地址缩筛;如果是類方法消略,則是指向元類的指針)
第二個參數(shù):是方法選擇器(selector)
接下來的參數(shù):方法的參數(shù)列表。
前面介紹過的SEL就是為了查找方法的最終實現(xiàn)IMP的瞎抛。由于每個方法對應(yīng)唯一的SEL艺演,因此我們可以通過SEL方便快速準確地獲得它所對應(yīng)的IMP,查找過程將在下面討論桐臊。取得IMP后胎撤,我們就獲得了執(zhí)行這個方法代碼的入口點,此時断凶,我們就可以像調(diào)用普通的C語言函數(shù)一樣來使用這個函數(shù)指針了伤提。
3、Method
Method用于表示類定義中的方法认烁,則定義如下:
typedef struct objc_method *Method
struct objc_method{
SEL method_name? ? ? OBJC2_UNAVAILABLE; // 方法名
char *method_types? OBJC2_UNAVAILABLE;
IMP method_imp? ? ? OBJC2_UNAVAILABLE; // 方法實現(xiàn)
}
我們可以看到該結(jié)構(gòu)體中包含一個SEL和IMP肿男,實際上相當于在SEL和IMP之間作了一個映射。有了SEL却嗡,我們便可以找到對應(yīng)的IMP次伶,從而調(diào)用方法的實現(xiàn)代碼。
4方法調(diào)用過程
在OC中消息直到運行時才綁定到方法實現(xiàn)上稽穆,編譯器會將消息表達式轉(zhuǎn)化為消息的函數(shù)調(diào)用冠王,即objc_msgSend。這個函數(shù)將消息接收者和方法名作為其基礎(chǔ)參數(shù)舌镶,如以下所示
objc_msgSend(receiver, selector)柱彻,
如果消息還有其他參數(shù),則表達方式
objc_msgSend(receiver, selector, arg1, arg2,...)
這個消息的函數(shù)內(nèi)部完成了一下事件:
a.首先找到selector指向方法的實現(xiàn)餐胀,因為同一個方法在不同類有不同的實現(xiàn)哟楷,所以必須依賴消息接受類來找到方法實現(xiàn)
b.運行方法實現(xiàn),并將接受消息的對象和參數(shù)傳給他
c.最后將方法實現(xiàn)的返回值作為自己的返回值
消息執(zhí)行順序:當消息發(fā)送給一個對象否灾,現(xiàn)在運行時系統(tǒng)的緩存中找卖擅,如果找到則執(zhí)行方法,如果沒找到,則通過對象的isa指向找到對象的結(jié)構(gòu)體惩阶,在該結(jié)構(gòu)體的方法列表中找挎狸,還沒找到則通過該結(jié)構(gòu)體isa指針找到其父類結(jié)構(gòu)體,在父類結(jié)構(gòu)體方法列表中查找断楷,以此類推一直查找到根源類锨匆。
5、消息轉(zhuǎn)發(fā)
當一個對象能接收一個消息時冬筒,就會走正常的方法調(diào)用流程恐锣。但如果一個對象無法接收指定消息時,又會發(fā)生什么事呢舞痰?默認情況下土榴,如果是以[object message]的方式調(diào)用方法,如果object無法響應(yīng)message消息時响牛,編譯器會報錯鞭衩。但如果是以perform…的形式來調(diào)用,則需要等到運行時才能確定object是否能接收message消息娃善。如果不能论衍,則程序崩潰。
當一個對象無法接收某一消息時聚磺,就會啟動所謂“消息轉(zhuǎn)發(fā)(message forwarding)”機制坯台,通過這一機制,我們可以告訴對象如何處理未知的消息瘫寝。默認情況下蜒蕾,對象接收到未知的消息,會導(dǎo)致程序崩潰焕阿,通過控制臺咪啡,我們可以看到異常信息:
這段異常信息實際上是由NSObject的“doesNotRecognizeSelector”方法拋出的。不過暮屡,我們可以采取一些措施撤摸,讓我們的程序執(zhí)行特定的邏輯,而避免程序的崩潰褒纲。
1>准夷、動態(tài)方法解析
2>、備用接收者
3>莺掠、完整轉(zhuǎn)發(fā)
動態(tài)方法解析
對象在接收到未知的消息時衫嵌,首先會調(diào)用所屬類的類方法
+resolveInstanceMethod:(實例方法)或者
+resolveClassMethod:(類方法)。
在這個方法中彻秆,我們有機會為該未知消息新增一個“處理方法”楔绞,通過運行時class_addMethod函數(shù)動態(tài)添加到類里面就可以了结闸。
這種方案更多的是為了實現(xiàn)@dynamic屬性。
備用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector
如果在上一步無法處理消息酒朵,則Runtime會繼續(xù)調(diào)以下方法:
如果一個對象實現(xiàn)了這個方法桦锄,并返回一個非nil的結(jié)果,則這個對象會作為消息的新接收者耻讽,且消息會被分發(fā)到這個對象。當然這個對象不能是self自身帕棉,否則就是出現(xiàn)無限循環(huán)针肥。當然,如果我們沒有指定相應(yīng)的對象來處理aSelector香伴,則應(yīng)該調(diào)用父類的實現(xiàn)來返回結(jié)果慰枕。
這一步合適于我們只想將消息轉(zhuǎn)發(fā)到另一個能處理該消息的對象上。但這一步無法對消息進行處理即纲,如操作消息的參數(shù)和返回值具帮。
完整消息轉(zhuǎn)發(fā)
如果在上一步還不能處理未知消息,則唯一能做的就是啟用完整的消息轉(zhuǎn)發(fā)機制了低斋。
我們首先要通過,指定方法簽名蜂厅,若返回nil,則表示不處理膊畴。
如下代碼:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if ([NSStringFromSelector(aSelector) isEqualToString:@"testInstanceMethod"]){
return [NSMethodSignature signatureWithObjcTypes:"v@:"];
}
return [super methodSignatureForSelector: aSelector];
}
若返回方法簽名掘猿,則會進入下一步調(diào)用以下方法,對象會創(chuàng)建一個表示消息的NSInvocation對象唇跨,把與尚未處理的消息有關(guān)的全部細節(jié)都封裝在anInvocation中稠通,包括selector,目標(target)和參數(shù)买猖。
我們可以在forwardInvocation方法中選擇將消息轉(zhuǎn)發(fā)給其它對象改橘。我們可以通過anInvocation對象做很多處理,比如修改實現(xiàn)方法玉控,修改響應(yīng)對象等.
如下所示:
- (void)forwardInvovation:(NSInvocation)anInvocation
{
[anInvocation invokeWithTarget:_helper];
[anInvocation setSelector:@selector(run)];
[anInvocation invokeWithTarget:self];
}