Runtime 是一個(gè)比較底層的C語(yǔ)言的API傀顾,可以翻譯為“運(yùn)行時(shí)”靡羡。作為使用運(yùn)行時(shí)機(jī)制的OC語(yǔ)言的底層犀盟,它在程序運(yùn)行時(shí)把OC語(yǔ)言轉(zhuǎn)換成了runtime的C語(yǔ)言代碼拣度。學(xué)習(xí)并理解runtime是OC學(xué)習(xí)歷程中的不可或缺的一大塊兒碎绎。
一、消息機(jī)制
調(diào)用方法的本質(zhì)就是發(fā)送消息抗果。
發(fā)送消息常見(jiàn)的有四個(gè)方法:
-
objc_msgSend
向一個(gè)類(lèi)的實(shí)例發(fā)送消息筋帖,返回id類(lèi)型數(shù)據(jù)。(這也是最常用的一個(gè)發(fā)送消息的方法) -
objc_msgSend_stret
向一個(gè)類(lèi)的實(shí)例發(fā)送消息冤馏,返回結(jié)構(gòu)體類(lèi)型數(shù)據(jù)日麸。 -
objc_msgSendSuper
向一個(gè)類(lèi)的實(shí)例的父類(lèi)發(fā)送消息,返回id類(lèi)型數(shù)據(jù)逮光。 -
objc_msgSendSuper_stret
向一個(gè)類(lèi)的實(shí)例的父類(lèi)發(fā)送消息代箭,返回結(jié)構(gòu)體類(lèi)型的數(shù)據(jù)。
在OC語(yǔ)言中涕刚,方法的真正實(shí)現(xiàn)是在程序運(yùn)行的時(shí)候綁定的嗡综,假如一個(gè)方法只有聲明,沒(méi)有實(shí)現(xiàn)杜漠,調(diào)用后在編譯階段是不會(huì)出錯(cuò)的极景,真正報(bào)錯(cuò)是在運(yùn)行的時(shí)候。
[receiver message]
以上方法在運(yùn)行時(shí)會(huì)被轉(zhuǎn)化為
//receiver是方法的調(diào)用者碑幅,selector是方法名
objc_msgSend(receiver, selector)
//如果有參數(shù)
objc_msgSend(receiver, selector, arg1, arg2, ...)
發(fā)送消息的原理
objc_msgSend為了完成動(dòng)態(tài)綁定戴陡,進(jìn)行了以下三步:
- 首先它要先根據(jù)方法名找到方法的具體實(shí)現(xiàn)程序,因?yàn)槎鄳B(tài)性沟涨,同一個(gè)方法在不同的類(lèi)里面可以有不同的實(shí)現(xiàn)恤批,所以查找主要依靠尋找receiver所在的類(lèi)。
- 傳遞參數(shù)裹赴,調(diào)用該方法的實(shí)現(xiàn)程序喜庞。
- 把該程序的返回值作為方法自己的返回值诀浪。
//runtime中對(duì)類(lèi)的定義
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
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;
#endif
} OBJC2_UNAVAILABLE;
//runtime中對(duì)實(shí)例的定義
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
如上runtime中對(duì)類(lèi)的定義,每一個(gè)類(lèi)都有指向父類(lèi)的指針(super_class
)和一個(gè)方法調(diào)度表(objc_method_list **methodLists
:根據(jù)方法名SEL查找該方法的具體實(shí)現(xiàn)的地址IMP)延都,當(dāng)向一個(gè)對(duì)象發(fā)送消息的時(shí)候雷猪,該對(duì)象通過(guò)isa指針找到該對(duì)象的類(lèi)(實(shí)際上,實(shí)例的定義里面也只有這個(gè)指針晰房,沒(méi)有別的了)求摇,在類(lèi)的調(diào)度表查找該方法名,當(dāng)找不到的時(shí)候殊者,通過(guò)指向父類(lèi)的指針找到該類(lèi)的父類(lèi)与境,然后在該類(lèi)的父類(lèi)中繼續(xù)查找該方法名,這樣遞歸查找一直到NSObject類(lèi)為止(NSProxy類(lèi)除外猖吴,它不屬于NSObject子類(lèi))摔刁。如果查找到該方法名,根據(jù)調(diào)度表找到該方法的實(shí)現(xiàn)的地址進(jìn)行調(diào)用海蔽。如下圖所示
為了加速發(fā)送消息的進(jìn)程共屈,runtime系統(tǒng)會(huì)把使用過(guò)的方法名和對(duì)應(yīng)的內(nèi)存地址緩存起來(lái),每個(gè)類(lèi)都有一個(gè)單獨(dú)的緩存空間党窜,其中包含自己類(lèi)的方法和繼承自父類(lèi)的方法拗引。在查找調(diào)度表之前,runtime系統(tǒng)會(huì)首先在緩存中進(jìn)行查找幌衣。
使用隱藏的參數(shù)
當(dāng)objc_msgSend找到方法的實(shí)現(xiàn)程序時(shí)寺擂,它調(diào)用這個(gè)程序并傳遞所有方法的參數(shù)給它,這其中還包含兩個(gè)隱藏的參數(shù):
- 消息的接收對(duì)象
- 調(diào)用方法的方法名(selector)
這兩個(gè)參數(shù)雖然沒(méi)有在方法中進(jìn)行定義泼掠,但是你可以很方便地使用它們。消息的接收對(duì)象通過(guò)self來(lái)引用垦细,方法名通過(guò)_cmd來(lái)引用择镇。
- strange
{
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd )
return nil;
return [target performSelector:method];
}
獲取方法的地址
避免動(dòng)態(tài)綁定的唯一方法就是直接獲得方法的地址然后把它當(dāng)做函數(shù)一樣來(lái)調(diào)用。當(dāng)一個(gè)方法被連續(xù)多次執(zhí)行括改,而你又不想每次都用消息機(jī)制造成額外的開(kāi)支腻豌,這種辦法就是一個(gè)合適的使用時(shí)機(jī)。
下面的例子展示了如何節(jié)省開(kāi)支多次調(diào)用setFilled:方法
void (*setter)(id, SEL, BOOL);
int i;
setter = (void (*)(id, SEL, BOOL))[target
methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
setter(targetList[i], @selector(setFilled:), YES);
通過(guò)methodForSelector:
方法嘱能,你可以請(qǐng)求得到指向?qū)崿F(xiàn)該方法的程序的指針吝梅,然后通過(guò)這個(gè)指針調(diào)用該程序。值的注意的是惹骂,參數(shù)和返回值要正確聲明苏携,而且參數(shù)中id和SEL要進(jìn)行顯式聲明。
二对粪、動(dòng)態(tài)方法
假如你想動(dòng)態(tài)地為方法提供實(shí)現(xiàn)右冻,OC使用@dynamic
實(shí)現(xiàn)了這個(gè)特性装蓬。
@dynamic propertyName;
這樣就會(huì)通知編譯器和這個(gè)屬性相關(guān)的方法將會(huì)動(dòng)態(tài)提供。你可以通過(guò)方法resolveInstanceMethod:
和resolveClassMethod:
分別為類(lèi)方法和實(shí)例方法動(dòng)態(tài)地提供實(shí)現(xiàn)纱扭。
一個(gè)OC的方法其實(shí)就是由C語(yǔ)言的函數(shù)再加上至少兩個(gè)參數(shù)(self和_cmd)組成的牍帚。
你可以把一個(gè)函數(shù)通過(guò)class_addMethod
作為方法添加到一個(gè)類(lèi)中去。給定以下一個(gè)函數(shù):
void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
你可以通過(guò)resolveInstanceMethod:
這個(gè)方法把上面的函數(shù)以方法名(resolveThisMethodDynamically
)動(dòng)態(tài)地添加到一個(gè)類(lèi)(MyClass)里面乳蛾。具體實(shí)現(xiàn)方式如下:
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end
這其中暗赶,class_addMethod
這個(gè)方法有四個(gè)參數(shù),第一個(gè)是要添加方法的類(lèi)肃叶,第二個(gè)是要添加的方法名蹂随,第三個(gè)是這個(gè)方法的實(shí)現(xiàn)函數(shù)的指針(值的注意的是,這個(gè)函數(shù)必須顯式地把self
和_cmd
這兩個(gè)參數(shù)寫(xiě)出來(lái))被环,第四個(gè)是方法的參數(shù)數(shù)組糙及,在這里它是用的類(lèi)型編碼的方式進(jìn)行表示的,因?yàn)榉椒ㄒ欢ê?code>self和_cmd
這兩個(gè)參數(shù)筛欢,所以字符數(shù)組的第二個(gè)和第三個(gè)字符一定是"@:",第一個(gè)字符代表返回值浸锨,這里為空用“v”來(lái)表示。相關(guān)知識(shí)點(diǎn)請(qǐng)見(jiàn)下文版姑。
三柱搜、類(lèi)型編碼
為了使runtime系統(tǒng)更加簡(jiǎn)潔,編譯器把每個(gè)方法的返回值和參數(shù)的類(lèi)型都分別使用一個(gè)字符來(lái)編碼剥险,然后再把它們關(guān)聯(lián)到方法選擇器(selector)上聪蘸。因?yàn)檫@種編碼方案在其它環(huán)境中也很實(shí)用,所以我們可以很方便地使用@encode()
編譯器指令來(lái)自定義類(lèi)似的編碼表制。
char *buf1 = @encode(int **);
char *buf2 = @encode(struct key);
char *buf3 = @encode(Rectangle);
一般來(lái)說(shuō)健爬,不管是基本類(lèi)型,還是指針么介,或者結(jié)構(gòu)體娜遵,或者聯(lián)合體,甚至可以是類(lèi)名壤短,只要這個(gè)類(lèi)型能夠作為C語(yǔ)言中
sizeof()
的參數(shù)设拟,那么它就能被進(jìn)行編碼。
下表便是已經(jīng)定義了的類(lèi)型編碼久脯,使用@encode()
編譯器指令自定義編碼的時(shí)候一定要避開(kāi)這些字符纳胧。
特別注意,OC不支持long double
類(lèi)型帘撰,因此@encode(long double)
會(huì)返回字符“d"跑慕,意義為double
。
結(jié)構(gòu)體的類(lèi)型編碼是按照結(jié)構(gòu)體內(nèi)部的類(lèi)型的順序來(lái)表示的摧找,比如
typedef struct example {
id anObject;
char *aString;
int anInt;
} Example;
會(huì)被編碼為:
{example=@*i}
由第一章內(nèi)容可以得知相赁,類(lèi)的實(shí)例的定義是一個(gè)只包含isa指針的結(jié)構(gòu)體相寇,所以[NSObject class]
會(huì)被編碼為
{ NSObject=# }
具體應(yīng)用方面,上一章class_addMethod
最后一個(gè)參數(shù)就是使用的類(lèi)型編碼來(lái)表示的函數(shù)返回值和參數(shù)的類(lèi)型钮科。
參考:《Objective-C Runtime Programing Guide》
文章會(huì)不定期進(jìn)行增添和更新唤衫,歡迎訂閱和收藏!