Objective-C 2.0 運行時系統(tǒng)編程

1 概述

Objective-C語言將決定盡可能的從編譯和鏈接時推遲到運行時绒窑。只要有可能块茁,Objective-C總是使用動態(tài)的方式來解決問題粱哼。這意味著Objective-C語言不僅需要一個編譯器审编,同時也需要一個運行時系統(tǒng)來執(zhí)行編譯好的代碼剪决。這里的運行時系統(tǒng)扮演的角色類似于 Objective-C語言的操作系統(tǒng),Objective-C基于該系統(tǒng)來工作蔗候。
本文章將具體介紹NSObject類以及Objective-C程序是如何與運行時系統(tǒng)交互的怒允。特別地,本文章還給出來怎樣在運行時動態(tài)地加載新類和將消息轉(zhuǎn)發(fā)給其它對象的范例锈遥,同時也給出了怎樣在程序運行時獲取對象信息的方法纫事。
通常,如果僅僅寫一個Cocoa 程序所灸,程序員不需要知道和理解Objective-C運行時系統(tǒng)的底層細節(jié)丽惶,但這篇文章仍然值得推薦閱讀,以了解 Objective-C運行時系統(tǒng)的原理爬立,并能更好的利用 Objective-C的優(yōu)點钾唬。

2 參考

《Objective-C 2.0 運行時系統(tǒng)參考庫》描述了Objective-C運行庫的數(shù)據(jù)結(jié)構(gòu)和函數(shù)接口。程序可以通過這些接口來和Objective-C運行時系統(tǒng)交互侠驯。例如抡秆,您可以增加一個類或者方法,或者獲得所有類的定義列表等吟策。
《Objective-C 2.0 程序設(shè)計語言》介紹了Objective-C語言本身儒士。
《Objective-C 版本說明》給出了在最近版本的Mac OS X系統(tǒng)中關(guān)于Objective-C運行時系統(tǒng)的一些改動。

3 運行時系統(tǒng)的版本和平臺

在不同的平臺上Objective-C運行時系統(tǒng)的版本也不相同檩坚。

3.1早期版本和現(xiàn)行版本

Objective-C運行時系統(tǒng)有兩個已知版本:早期版本和現(xiàn)行版本乍桂。
現(xiàn)行版本主要是Objective-C 2.0 及與其相關(guān)的新特性。早期版本的編程接口見《Objective-C 1運行時系統(tǒng)參考庫》效床;現(xiàn)行版本的編程接口見《Objective-C 2.0 運行時系統(tǒng)參考庫》睹酌。
在現(xiàn)行版本中,最顯著的新特性就是實例變量是“健壯(non-fragile )的”:
1)在早期版本中剩檀,如果您改變類中實例變量的布局憋沿,您必須重新編譯該類的所有子類。
2)在現(xiàn)行版本中沪猴,如果您改變類中實例變量的布局辐啄,您無需重新編譯該類的任何子類。
此外运嗜,現(xiàn)行版本支持聲明property 的synthesis屬性(參考《Objective-C 2.0 程序設(shè)計語言》的“屬性”一節(jié))壶辜。

3.2平臺

iPhone 程序和Mac OS X 10.5及以后的系統(tǒng)中的64位程序使用的都是Objective-C運行時系統(tǒng)的現(xiàn)行版本。
其它情況(Mac OS X系統(tǒng)中的32位程序)使用的是早期版本担租。

4 和運行時系統(tǒng)的交互

Objective-C程序有三種途徑和運行時系統(tǒng)交互:
1)通過 Objective-C源代碼砸民;
2)通過 Foundation框架中類NSObject的方法;
3)通過直接調(diào)用運行時系統(tǒng)的函數(shù)。

4.1通過Objective-C源代碼

大部分情況下岭参,運行時系統(tǒng)在后臺自動運行反惕,您只需編寫和編譯Objective-C源代碼。
當您編譯Objective-C類和方法時演侯,編譯器為實現(xiàn)語言動態(tài)特性將自動創(chuàng)建一些數(shù)據(jù)結(jié)構(gòu)和函數(shù)姿染。這些數(shù)據(jù)結(jié)構(gòu)包含類定義和協(xié)議類定義中的信息,如在《Objective-C 2.0 程序設(shè)計語言》中“定義類”和“協(xié)議類”一節(jié)所討論的類的對象和協(xié)議類的對象秒际,方法選標悬赏,實例變量模板,以及其它來自于源代碼的信息娄徊。運行時系統(tǒng)的主要功能就是根據(jù)源代碼中的表達式發(fā)送消息舷嗡,如“消息”一節(jié)所述。

4.2通過類NSObject的方法

Cocoa 程序中絕大部分類都是NSObject類的子類嵌莉,所以大部分都繼承了NSObject類的方法茧妒,因而繼承了NSObject的行為浮禾。(NSProxy類是個例外;更多細節(jié)參考“消息轉(zhuǎn)發(fā)”一節(jié)驾讲。)然而可婶,某些情況下沿癞,NSObject類僅僅定義了完成某件事情的模板,而沒有提供所有需要的代碼矛渴。
例如椎扬,NSObject類定義了description 方法,返回該類內(nèi)容的字符串表示具温。這主要是用來調(diào)試程序——GDB中的print-object方法就是直接打印出該方法返回的字符串蚕涤。NSObject類中該方法的實現(xiàn)并不知道子類中的內(nèi)容,所以它只是返回類的名字和對象的地址铣猩。NSObject的子類可以重新實現(xiàn)該方法以提供更多的信息揖铜。例如,NSArray 類改寫了該方法來返回NSArray 類包含的每個對象的內(nèi)容达皿。
某些NSObject的方法只是簡單地從運行時系統(tǒng)中獲得信息天吓,從而允許對象進行一定程度的自我檢查。例如峦椰,

class 返回對象的類龄寞;
isKindOfClass:和isMemberOfClass:檢查對象是否在指定的類繼承體系中;
respondsToSelector:檢查對象能否響應(yīng)指定的消息汤功;
conformsToProtocol:檢查對象是否實現(xiàn)了指定協(xié)議類的方法物邑;
methodForSelector:返回指定方法實現(xiàn)的地址。 

4.3通過運行時系統(tǒng)的函數(shù)

運行時系統(tǒng)是一個有公開接口的動態(tài)庫,由一些數(shù)據(jù)結(jié)構(gòu)和函數(shù)的集合組成拂封,這些數(shù)據(jù)結(jié)構(gòu)和函數(shù)的聲明頭文件在/usr/include/objc 中茬射。這些函數(shù)支持用純 C 的函數(shù)來實現(xiàn)Objective-C同樣的功能。還有一些函數(shù)構(gòu)成了NSObject類方法的基礎(chǔ)冒签。這些函數(shù)使得訪問運行時系統(tǒng)接口和提供開發(fā)工具成為可能在抛。盡管大部分情況下它們在 Objective-C程序不是必須的,但是有時候?qū)τ?Objecitve-C程序來說某些函數(shù)是非常有用的萧恕。
這些函數(shù)的文檔參見《Objective-C 2.0 運行時系統(tǒng)參考庫》刚梭。

5 消息

本章節(jié)描述了代碼的消息表達式如何轉(zhuǎn)換為對objc_msgSend函數(shù)的調(diào)用,如何通過名字來指定一個方法票唆,以及如何使用objc_msgSend函數(shù)朴读。

5.1獲得方法地址

避免動態(tài)綁定的唯一辦法就是取得方法的地址,并且直接像函數(shù)調(diào)用一樣調(diào)用它走趋。當一個方法會被連續(xù)調(diào)用很多次衅金,而且您希望節(jié)省每次調(diào)用方法都要發(fā)送消息的開銷時,使用方法地址來調(diào)用方法就顯得很有效簿煌。
利用NSObject類中的methodForSelector:方法氮唯,您可以獲得一個指向方法實現(xiàn)的指針,并可以使用該指針直接調(diào)用方法實現(xiàn)姨伟。methodForSelector:返回的指針和賦值的變量類型必須完全一致惩琉,包括方法的參數(shù)類型和返回值類型都在類型識別的考慮范圍中。
下面的例子展示了怎么使用指針來調(diào)用setFilled:的方法實現(xiàn):

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);

方法指針的第一個參數(shù)是接收消息的對象(self)夺荒,第二個參數(shù)是方法選標(_cmd)瞒渠。這兩個參數(shù)在方法中是隱藏參數(shù),但使用函數(shù)的形式來調(diào)用方法時必須顯示的給出技扼。
使用methodForSelector:來避免動態(tài)綁定將減少大部分消息的開銷伍玖,但是這只有在指定的消息被重復(fù)發(fā)送很多次時才有意義,例如上面的for 循環(huán)剿吻。
注意:methodForSelector:是Cocoa 運行時系統(tǒng)的提供的功能私沮,而不是Objective-C語言本身的功能。

5.2 objc_msgSend函數(shù)

在Objective-C中和橙,消息是直到運行的時候才和方法實現(xiàn)綁定的仔燕。編譯器會把一個消息表達式,
[receiver message]
轉(zhuǎn)換成一個對消息函數(shù)objc_msgSend的調(diào)用魔招。該函數(shù)有兩個主要參數(shù):消息接收者和消息對應(yīng)的方法名字——也就是方法選標:
objc_msgSend(receiver, selector)
同時接收消息中的任意數(shù)目的參數(shù):
objc_msgSend(receiver, selector, arg1, arg2, ...)
該消息函數(shù)做了動態(tài)綁定所需要的一切:
1)它首先找到選標所對應(yīng)的方法實現(xiàn)晰搀。因為不同的類對同一方法可能會有不同的實現(xiàn),所以找到的方法實現(xiàn)依賴于消息接收者的類型办斑。
2)然后將消息接收者對象(指向消息接收者對象的指針)以及方法中指定的參數(shù)傳給找到的方法實現(xiàn)外恕。
3)最后杆逗,將方法實現(xiàn)的返回值作為該函數(shù)的返回值返回。
注意:編譯器將自動插入調(diào)用該消息函數(shù)的代碼鳞疲。您無須在代碼中顯示調(diào)用該消息函數(shù)罪郊。
消息機制的關(guān)鍵在于編譯器為類和對象生成的結(jié)構(gòu)。每個類的結(jié)構(gòu)中至少包括兩個基本元素:
1)指向父類的指針尚洽。
2)類的方法表悔橄。方法表將方法選標和該類的方法實現(xiàn)的地址關(guān)聯(lián)起來。例如腺毫,setOrigin::的方法選標和setOrigin::的方法實現(xiàn)的地址關(guān)聯(lián)癣疟,display 的方法選標和display 的方法實現(xiàn)的地址關(guān)聯(lián),等等潮酒。
當新的對象被創(chuàng)建時睛挚,其內(nèi)存同時被分配,實例變量也同時被初始化急黎。對象的第一個實例變量是一個指向該對象的類結(jié)構(gòu)的指針扎狱,叫做isa。通過該指針勃教,對象可以訪問它對應(yīng)的類以及相應(yīng)的父類淤击。
注意:盡管嚴格來說這并不是 Obective-C 語言的一部分,但是在Objective-C運行時系統(tǒng)中對象需要有isa 指針荣回。對象和結(jié)構(gòu)體struct objc_object(在objc/objc.h 中定義)必須“一致”。然而戈咳,您很少需要創(chuàng)建您自己的根對象心软,因為從 NSObject或者NSProxy 繼承的對象都自動包括isa 變量。
類和對象的結(jié)構(gòu)如下圖所示著蛙。


當對象收到消息時删铃,消息函數(shù)首先根據(jù)該對象的 isa 指針找到該對象所對應(yīng)的類的方法表,并從表中尋找該消息對應(yīng)的方法選標踏堡。如果找不到猎唁,objc_msgSend將繼續(xù)從父類中尋找,直到 NSObject類顷蟆。一旦找到了方法選標诫隅, objc_msgSend則以消息接收者對象為參數(shù)調(diào)用,調(diào)用該選標對應(yīng)的方法實現(xiàn)帐偎。
這就是在運行時系統(tǒng)中選擇方法實現(xiàn)的方式逐纬。在面向?qū)ο缶幊讨校话惴Q作方法和消息動態(tài)綁定的過程削樊。
為了加快消息的處理過程豁生,運行時系統(tǒng)通常會將使用過的方法選標和方法實現(xiàn)的地址放入緩存中兔毒。每個類都有一個獨立的緩存,同時包括繼承的方法和在該類中定義的方法甸箱。消息函數(shù)會首先檢查消息接收者對象對應(yīng)的類的緩存(理論上育叁,如果一個方法被使用過一次,那么它很可能被再次使用)芍殖。如果在緩存中已經(jīng)有需要的方法選標豪嗽,則消息僅僅比函數(shù)調(diào)用慢一點點。如果程序運行了足夠長的時間围小,幾乎每個消息都能在緩存中找到方法實現(xiàn)昵骤。程序運行時,緩存也將隨著新的消息的增加而增加肯适。

5.3使用隱藏的參數(shù)

當objc_msgSend找到方法對應(yīng)的實現(xiàn)時变秦,它將直接調(diào)用該方法實現(xiàn),并將消息中所有的參數(shù)都傳遞給方法實現(xiàn)框舔,同時蹦玫,它還將傳遞兩個隱藏的參數(shù):
1)接收消息的對象
2)方法選標
這些參數(shù)幫助方法實現(xiàn)獲得了消息表達式的信息。它們被認為是“隱藏”的原因是它們并沒有在定義方法的源代碼中聲明刘绣,而是在代碼編譯時是插入方法的實現(xiàn)中的樱溉。
盡管這些參數(shù)沒有被顯示聲明,但在源代碼中仍然可以引用它們(就像可以引用消息接收者對象的實例變量一樣)纬凤。在方法中可以通過 self來引用消息接收者對象福贞,通過選標_cmd來引用方法本身。在下面的例子中停士,_cmd指的是strange 方法挖帘,self指的收到strange 消息的對象。

- strange  {
    id  target = getTheReceiver();
    SEL method = getTheMethod();

    if ( target == self || method == _cmd )
        return nil;

    return [target performSelector:method];

}

在這兩個參數(shù)中恋技,self更有用一些拇舀。實際上,它是在方法實現(xiàn)中訪問消息接收者對象的實例變量的途徑蜻底。

6 動態(tài)方法解析

本章節(jié)將描述怎樣動態(tài)地提供一個方法的實現(xiàn)骄崩。

6.1動態(tài)方法解析

有時候,程序員需要動態(tài)地提供一個方法的實現(xiàn)薄辅。例如要拂,Objective-C中屬性(Property )( 參考《Objective-C 2.0 程序設(shè)計語言》中“屬性”小節(jié))前的修飾符@dynamic
@dynamic propertyName;
表示編譯器須動態(tài)地生成該屬性對應(yīng)地方法。
程序員可以通過實現(xiàn)resolveInstanceMethod:和resolveClassMethod:來動態(tài)地實現(xiàn)給定選標的對象方法或者類方法站楚。
Objective-C方法可以認為是至少有兩個參數(shù)self和_cmd的C 函數(shù)宇弛。您可以通過class_addMethod 方法將一個函數(shù)加入到類的方法中。例如源请,有如下的函數(shù):

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}

程序員可以通過resolveInstanceMethod:將它作為類方法resolveThisMethodDynamically的實現(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

通常消息轉(zhuǎn)發(fā)(見 “消息轉(zhuǎn)發(fā)”)和動態(tài)方法解析是互不相干的枪芒。在進入消息轉(zhuǎn)發(fā)機制之前彻况,respondsToSelector:和instancesRespondToSelector: 會被首先調(diào)用。您可以在這兩個方法中為傳進來的選標提供一個IMP 舅踪。如果您實現(xiàn)了resolveInstanceMethod:方法纽甘,但是仍然希望正常的消息轉(zhuǎn)發(fā)機制進行,您只需要返回NO即可抽碌。

6.2動態(tài)加載

Objective-C程序可以在運行時鏈接和載入新的類和范疇類悍赢。新載入的類和在程序啟動時載入的類并沒有區(qū)別。
動態(tài)加載可以用在很多地方货徙。例如左权,系統(tǒng)配置中的模塊就是被動態(tài)加載的。
在Cocoa 環(huán)境中痴颊,動態(tài)加載一般被用來對應(yīng)用程序進行定制赏迟。您的程序可以在運行時加載其他程序員編寫的模塊——和Interface Build 載入定制的調(diào)色板以及系統(tǒng)配置程序載入定制的模塊的類似。這些模塊通過您許可的方式擴展您的程序蠢棱,而您無需自己來定義或者實現(xiàn)锌杀。您提供了框架,而其它的程序員提供了實現(xiàn)泻仙。
盡管已經(jīng)有一個運行時系統(tǒng)的函數(shù)來動態(tài)加載Mach-O文件中的Objective-C模塊(objc_loadModules糕再,在objc/objc-load.h中定義),Cocoa 的NSBundle類為動態(tài)加載提供了一個更方便的接口——一個面向?qū)ο蟮挠褡呀?jīng)和相關(guān)服務(wù)集成的接口突想。關(guān)于NSBundle類的更多相關(guān)信息請參考Foundation 框架中關(guān)于NSBundle類的文檔。關(guān)于Mach-O文件的有關(guān)信息請參考《Mac OS X ABI Mach-O 文件格式參考庫》究抓。

7 消息轉(zhuǎn)發(fā)

通常猾担,給一個對象發(fā)送它不能處理的消息會得到出錯提示,然而漩蟆,Objective-C運行時系統(tǒng)在拋出錯誤之前垒探,會給消息接收對象發(fā)送一條特別的消息來通知該對象妓蛮。

7.1消息轉(zhuǎn)發(fā)

如果一個對象收到一條無法處理的消息怠李,運行時系統(tǒng)會在拋出錯誤前,給該對象發(fā)送一條forwardInvocation:消息蛤克,該消息的唯一參數(shù)是個NSInvocation類型的對象——該對象封裝了原始的消息和消息的參數(shù)捺癞。
程序員可以實現(xiàn)forwardInvocation:方法來對不能處理的消息做一些默認的處理,也可以以其它的某種方式來避免錯誤被拋出构挤。如forwardInvocation:的名字所示髓介,它通常用來將消息轉(zhuǎn)發(fā)給其它的對象。
關(guān)于消息轉(zhuǎn)發(fā)的作用筋现,您可以考慮如下情景:假設(shè)唐础,需要設(shè)計一個能夠響應(yīng)negotiate 消息的對象箱歧,并且能夠包括其它類型的對象對消息的響應(yīng)。通過在negotiate 方法的實現(xiàn)中將negotiate 消息轉(zhuǎn)發(fā)給其它的對象這種方式可以很容易的達到這一目的一膨。
更進一步呀邢,假設(shè)您希望您的對象和另外一個類的對象對 negotiate 的消息的響應(yīng)完全一致。一種可能的方式就是讓您的類繼承其它類的方法實現(xiàn)豹绪。然而价淌,有時候這種方式不可行,因為您的類和其它類可能需要在不同的繼承體系中響應(yīng)negotiate 消息瞒津。
雖然您的類無法繼承其它類的negotiate 方法蝉衣,您仍然可以提供一個方法實現(xiàn),這個方法實現(xiàn)只是簡單的將negotiate 消息轉(zhuǎn)發(fā)給其他類的對象巷蚪,就好像從其它類那里“借”來的實現(xiàn)一樣病毡。如下所示:

- negotiate {
    if ( [someOtherObject respondsTo:@selector(negotiate)] )
        return [someOtherObject negotiate];

    return self;
}

這種方式顯得有欠靈活,特別是有很多消息您都希望傳遞給其它對象時钓辆,您必須為每一種消息提供方法實現(xiàn)剪验。此外,這種方式不能處理未知的消息前联。當您寫下代碼時功戚,所有您需要轉(zhuǎn)發(fā)的消息的集合也必須確定。然而似嗤,實際上啸臀,這個集合會隨著運行時事件的發(fā)生,新方法或者新類的定義而變化烁落。
forwardInvocation:消息給這個問題提供了一個更特別的乘粒,動態(tài)的解決方案:當一個對象由于沒有相應(yīng)的方法實現(xiàn)而無法響應(yīng)某消息時,運行時系統(tǒng)將通過 forwardInvocation:消息通知該對象伤塌。每個對象都從NSObject類中繼承了forwardInvocation:方法灯萍。然而,NSObject中的方法實現(xiàn)只是簡單地調(diào)用了doesNotRecognizeSelector:每聪。通過實現(xiàn)您自己的forwardInvocation:方法旦棉,您可以在該方法實現(xiàn)中將消息轉(zhuǎn)發(fā)給其它對象。
要轉(zhuǎn)發(fā)消息給其它對象药薯,forwardInvocation:方法所必須做的有:
1)決定將消息轉(zhuǎn)發(fā)給誰绑洛,并且
2)將消息和原來的參數(shù)一塊轉(zhuǎn)發(fā)出去
消息可以通過invokeWithTarget:方法來轉(zhuǎn)發(fā):

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([someOtherObject respondsToSelector: [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}

轉(zhuǎn)發(fā)消息后的返回值將返回給原來的消息發(fā)送者。返回值可以是任何類型的童本,包括:id真屯,結(jié)構(gòu)體,浮點數(shù)等穷娱。
forwardInvocation:方法就像一個不能識別的消息的分發(fā)中心绑蔫,將這些消息轉(zhuǎn)發(fā)給不同接收對象运沦。或者它也可以像一個運輸站將所有的消息都發(fā)送給同一個接收對象配深。它可以將一個消息翻譯成另外一個消息茶袒,或者簡單的“吃掉”某些消息,因此沒有響應(yīng)也沒有錯誤凉馆。forwardInvocation:方法也可以對不同的消息提供同樣的響應(yīng)薪寓,這一切都取決于方法的具體實現(xiàn)。該方法所提供的是將不同的對象鏈接到消息鏈的能力澜共。
注意:forwardInvocation: 方法只有在消息接收對象中無法正常響應(yīng)消息時才會被調(diào)用向叉。所以,如果您希望您的對象將negotiate 消息轉(zhuǎn)發(fā)給其它對象嗦董,您的對象不能有negotiate 方法母谎。否則,forwardInvocation:將不可能會被調(diào)用京革。
更多消息轉(zhuǎn)發(fā)的信息奇唤,參考Foundation框架參考庫中NSInvocation類的文檔。

7.2消息轉(zhuǎn)發(fā)和多重繼承

消息轉(zhuǎn)發(fā)很像繼承匹摇,并且可以用來在Objective-C程序中模擬多重繼承咬扇。如下圖所示, 一個對象通過轉(zhuǎn)發(fā)來響應(yīng)消息廊勃,看起來就像該對象從別的類那借來了或者“繼承”了方法實現(xiàn)一樣懈贺。



在上圖中,Warrior類的一個對象實例將negotiate 消息轉(zhuǎn)發(fā)給Diplomat 類的一個實例坡垫∷蟛樱看起來,Warrior類似乎和Diplomat 類一樣冰悠,響應(yīng)negotiate消息堡妒,并且行為和Diplomat 一樣(盡管實際上是Diplomat類響應(yīng)了該消息)。
轉(zhuǎn)發(fā)消息的對象看起來有兩個繼承體系分支——自己的和響應(yīng)消息的對象的溉卓。在上面的例子中皮迟,Warrior看起來同時繼承自Diplomat 和自己的父類。
消息轉(zhuǎn)發(fā)提供了多重繼承的很多特性的诵。然而万栅,兩者有很大的不同:多重繼承是將不同的行為封裝到單個的對象中佑钾,有可能導(dǎo)致龐大的西疤,復(fù)雜的對象。而消息轉(zhuǎn)發(fā)是將問題分解到更小的對象中休溶,但是又以一種對消息發(fā)送對象來說完全透明的方式將這些對象聯(lián)系起來代赁。

7.3消息代理對象

消息轉(zhuǎn)發(fā)不僅和繼承很像扰她,它也使得以一個輕量級的對象(消息代理對象)代表更多的對象進行消息處理成為可能。
《Objective-C 2.0 程序設(shè)計語言》中“遠程消息”一節(jié)中的代理類就是這樣一個代理對象芭碍。代理類負責(zé)將消息轉(zhuǎn)發(fā)給遠程消息接收對象的管理細節(jié)徒役,保證消息參數(shù)的傳輸?shù)鹊取5窍㈩悰]有進一步的復(fù)制遠程對象的功能窖壕,它只是將遠程對象映射到一個本地地址上忧勿,從而能夠接收其它應(yīng)用程序的消息。
同時也存在著其它類型的消息代理對象瞻讽。例如鸳吸,假設(shè)您有個對象需要操作大量的數(shù)據(jù)——它可能需要創(chuàng)建一個復(fù)雜的圖片或者需要從磁盤上讀一個文件的內(nèi)容。創(chuàng)建一個這樣的對象是很費時的速勇,您可能希望能推遲它的創(chuàng)建時間——直到它真正需要時晌砾,或者系統(tǒng)資源空閑時。同時烦磁,您又希望至少有一個預(yù)留的對象和程序中其它對象交互养匈。
在這種情況下,您可以為該對象創(chuàng)建一個輕量的代理對象都伪。該代理對象可以有一些自己的功能呕乎,例如響應(yīng)數(shù)據(jù)查詢消息,但是它主要的功能是代表某個對象陨晶,當時間到來時楣嘁,將消息轉(zhuǎn)發(fā)給被代表的對象。當代理對象的forwardInvocation:方法收到需要轉(zhuǎn)發(fā)給被代表的對象的消息時珍逸,代理對象會保證所代表的對象已經(jīng)存在逐虚,否則就創(chuàng)建它。所有發(fā)到被代表的對象的消息都要經(jīng)過代理對象谆膳,對程序來說叭爱,代理對象和被代表的對象是一樣的。

7.4消息轉(zhuǎn)發(fā)和類繼承

盡管消息轉(zhuǎn)發(fā)很“像”繼承漱病,但它不是繼承买雾。例如在NSObject類中,方法respondsToSelector:和isKindOfClass:只會出現(xiàn)在繼承鏈中杨帽,而不是消息轉(zhuǎn)發(fā)鏈中漓穿。例如,如果向一個 Warrior類的對象詢問它能否響應(yīng)negotiate 消息注盈,

if ( [aWarrior respondsToSelector:@selector(negotiate)] )
    ...

返回值是NO晃危,盡管該對象能夠接收和響應(yīng)negotiate。
大部分情況下,NO是正確的響應(yīng)僚饭。但不是所有時候都是的震叮。例如,如果您使用消息轉(zhuǎn)發(fā)來創(chuàng)建一個代理對象以擴展某個類的能力鳍鸵,這里的消息轉(zhuǎn)發(fā)必須和繼承一樣苇瓣,盡可能的對用戶透明。如果您希望您的代理對象看起來就像是繼承自它代表的對象一樣偿乖,您需要重新實現(xiàn)respondsToSelector:和isKindOfClass:方法:

- (BOOL)respondsToSelector:(SEL)aSelector {
    if ( [super respondsToSelector:aSelector] )
        return YES;
    else {
        /* Here, test whether the aSelector message can     *
         * be forwarded to another object and whether that  *
         * object can respond to it. Return YES if it can.  */
    }
    return NO;
}

除了respondsToSelector:和isKindOfClass:方法外击罪,instancesRespondToSelector:方法也必須重新實現(xiàn)。如果您使用的是協(xié)議類贪薪,需要重新實現(xiàn)的還有conformsToProtocol:方法外邓。類似地,如果對象需要轉(zhuǎn)發(fā)遠程消息古掏,則 methodSignatureForSelector:方法必須能夠返回實際響應(yīng)消息的方法的描述损话。例如,如果對象需要將消息轉(zhuǎn)發(fā)給它所代表的對象槽唾,您可能需要如下的methodSignatureForSelector:實現(xiàn):

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
       signature = [surrogate methodSignatureForSelector:selector];
    }
    return signature;
}

您也可以將消息轉(zhuǎn)發(fā)的部分放在一段私有的代碼里丧枪,然后從forwardInvocation:調(diào)用它。
注意:消息轉(zhuǎn)發(fā)是一個比較高級的技術(shù)庞萍,僅適用于沒有其它更好的解決辦法的情況拧烦。它并不是用來代替繼承的。如果您必須使用該技術(shù)钝计,請確定您已經(jīng)完全理解了轉(zhuǎn)發(fā)消息的類和接收轉(zhuǎn)發(fā)消息的類的行為恋博。
本節(jié)中涉及的方法在Foundation框架參考庫中的NSObject類的文檔中都有描述。關(guān)于invokeWithTarget:的具體信息私恬,請參考Foundation框架參考庫中NSInvocation類的文檔债沮。

8 類型編碼

為了和運行時系統(tǒng)協(xié)作,編譯器將方法的返回類型和參數(shù)類型都編碼成一個字符串本鸣,并且和方法選標關(guān)聯(lián)在一起疫衩。這些編碼在別的上下文環(huán)境中同樣有用,所以您可以直接使@encode()編譯指令來得到具體的編碼荣德。給定一個類型闷煤, @encode()將返回該類型的編碼字符串。類型可以是基本類型例如整形涮瞻,指針鲤拿,結(jié)構(gòu)體或者聯(lián)合體,也可以是一個類署咽,就和 C 語言中的sizeof()操作符的參數(shù)一樣近顷,可以是任何類型。

char *buf1 = @encode(int **);
char *buf2 = @encode(struct key);
char *buf3 = @encode(Rectangle);

下表列出了這些類型編碼。注意幕庐,它們可能很多和您使用的對象編碼有一些重合。然而家淤,這里列出來的有些編碼是您寫編碼器的時候不會使用的异剥,也有一些不是@encode()產(chǎn)生的,但是在您寫編碼器的時候是會使用的絮重。(關(guān)于對象編碼的更多信息冤寿,請參考Foundation框架參考庫中的NSCoder類文檔。)
Objective-C類型編碼

編碼 含義
c char
i int
s short
l long青伤,在64位程序中督怜,l為32位
q long long
C unsigned char
I unsigned int
S unsigned short
L unsigned long
Q unsigned long long
f float
d double
B C++標準的bool或者C99標準的_Bool
v void
* 字符串(char *)
@ 對象(無論是靜態(tài)指定的還是通過id引用的)
# 類(Class)
: 方法選標(SEL)
[array type] 數(shù)組
{name=type…} 結(jié)構(gòu)體
(name=type…) 聯(lián)合體
bnum num個bit的位域
^type type類型的指針
? 未知類型(其他時候,一般用來指函數(shù)指針)

重要: Objective-C 不支持long double 類型狠角。@encode(long double)和double 一樣号杠,返回的字符串都是d。
數(shù)組的類型編碼以方括號來表示丰歌,緊接著左方括號的是數(shù)組元素的數(shù)量姨蟋,然后是數(shù)據(jù)元素的類型。例如立帖,一個12個浮點數(shù)(floats)指針的數(shù)組可以表示如下:
[12^f]
結(jié)構(gòu)體和聯(lián)合體分別用大括號和小括號表示眼溶。括號中首先是結(jié)構(gòu)體標簽,然后是一個“=”符號晓勇,接著是結(jié)構(gòu)體中各個成員的編碼堂飞。例如,結(jié)構(gòu)體

typedef struct example {
    id   anObject;
    char *aString;
    int  anInt;
} Example;

的編碼如下:
{example=@*i}
定義的類型名(Example)和結(jié)構(gòu)體標簽(example)有同樣的編碼結(jié)果绑咱。指向結(jié)構(gòu)體類型的指針的編碼同樣也包含了結(jié)構(gòu)體內(nèi)部數(shù)據(jù)成員的編碼信息绰筛,如下所示:
^{example=@*i}
然而,更高層次的間接關(guān)聯(lián)就沒有了內(nèi)部數(shù)據(jù)成員的編碼信息:
^^{example}
對象的編碼類似結(jié)構(gòu)體描融。例如别智, @encode()對NSObject編碼如下:
{NSObject=#}
NSObject類僅聲明了一個Class 類型的實例變量,isa稼稿。
注意:盡管有一些編碼無法從 @encode()的結(jié)果中直接得到薄榛,但是運行時系統(tǒng)會使用它們來表示協(xié)議類中方法的修飾符,這些編碼如表所示让歼。
Objective-C方法編碼

編碼 含義
r const
n in
N inout
o out
O bycopy
R byref
V oneway

9 屬性聲明

當編譯器遇到一個屬性(Property )聲明時(參考《Objective-C 2.0 程序設(shè)計語言》中的“屬性”小節(jié))敞恋,編譯器將產(chǎn)生一些描述性的元數(shù)據(jù)與屬性所在的類或者協(xié)議類關(guān)聯(lián)。您可以通過函數(shù)訪問元數(shù)據(jù)谋右,這些函數(shù)支持在類或者協(xié)議類中通過名字來查找硬猫,通過@encode獲得屬性的類型編碼,將屬性的特征(Attribute )作為C字符串的數(shù)組返回等。每個類或者協(xié)議類都維護了一個聲明了的屬性列表啸蜜。

9.1屬性類型和相關(guān)函數(shù)

屬性(Property )類型定義了對描述屬性的結(jié)構(gòu)體objc_property 的不透明的句柄坑雅。

typedef struct objc_property *Property;

您可以使用函數(shù)class_copyPropertyList和protocol_copyPropertyList 來獲得類(包括范疇類)或者協(xié)議類中的屬性列表:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

例如,有如下的類聲明:

@interface Lender : NSObject { float alone;}@property float alone;@end

可以像這樣獲得它的屬性:

id LenderClass = objc_getClass("Lender");unsigned int outCount;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);

還可以通過property_getName函數(shù)獲得屬性的名字:

const char *property_getName(objc_property_t property)

函數(shù)class_getProperty 和protocol_getProperty則在類或者協(xié)議類中返回具有給定名字的屬性的引用:

objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)

通過property_getAttributes函數(shù)可以獲得屬性的名字和@encode編碼衬横。關(guān)于類型編碼的更多細節(jié)裹粤,參考“類型編碼”一節(jié);關(guān)于屬性的類型編碼蜂林,見“屬性類型編碼”及“屬性特征的描述范例”遥诉。

const char *property_getAttributes(objc_property_t property)

綜合起來,您可以通過下面的代碼得到一個類中所有的屬性噪叙。

id LenderClass = objc_getClass("Lender");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) {
    objc_property_t property = properties[i];
    fprintf(stdout,  "%s  %s\n",  property_getName(property), property_getAttributes(property));
}

9.2屬性類型編碼

property_getAttributes函數(shù)將返回屬性(Property)的名字矮锈,@encode 編碼,以及其它特征(Attribute )睁蕾。
1)property_getAttributes返回的字符串以字母T 開始苞笨,接著是@encode 編碼和逗號。
2)如果屬性有readonly修飾子眶,則字符串中含有R 和逗號猫缭。
3)如果屬性有copy或者retain修飾,則字符串分別含有C 或者&壹店,然后是逗號猜丹。
4)如果屬性定義有定制的getter 和setter 方法,則字符串中有G 或者S 跟著相應(yīng)的方法名以及逗號(例如硅卢,GcustomGetter射窒,ScustomSetter:,将塑,)脉顿。
如果屬性是只讀的,且有定制的get 訪問方法点寥,則描述到此為止艾疟。
5)字符串以V 然后是屬性的名字結(jié)束。
范例請參考 “屬性特征的描述范例” 一節(jié)敢辩。
9.3屬性特征的描述范例
給定如下定義:

enum FooManChu { FOO, MAN, CHU };
struct YorkshireTeaStruct { int pot; char lady; };
typedef struct YorkshireTeaStruct YorkshireTeaStructType;
union MoneyUnion { float alone; double down; };

下表給出了屬性(Property )聲明以及property_getAttributes返回的相應(yīng)的字符串:

屬性聲明 屬性描述
@property char charDefault; Tc,VcharDefault
@property double doubleDefault; Td,VdoubleDefault
@property enum FooManChuenumDefault; Ti,VenumDefault
@property float floatDefault; Tf,VfloatDefault
@property int intDefault; Ti,VintDefault
@property long longDefault; Tl,VlongDefault
@property short shortDefault; Ts,VshortDefault
@property singed singedDefault; Ti,VsingedDefault
@property struct YorkshireTeaStruct structDefault; T{ YorkshireTeaStruct =”pot”i”lady”c},VstructDefault
@property YorkshireTeaStructType typedefDefault; T{ YorkshireTeaStruct =”pot”i”lady”c},VtypedefDefault
@property union MoneyUnion unionDefault; T(MoneyUnion=”alone”f”down”d),VunionDefault
@property unsigned unsignedDefault; TI,VunsignedDefault
@property int (*functionPointerDefault)(char *); T^?,VfunctionPointerDefault
@property void *voidPointerDefault; T^v,VvoidPointerDefault
@property id idDefault; T@,VidDefault
@property int *intPointer; T^i,VintPointer
@property int intSynthEquals;In the implementation block: @synthesize intSynthEquals = _intSynthEquals; Ti,V_intSynthEquals
@property (getter=intGetFoo, setter=intSetFoo:) int intSetterGetter; Ti,GintGetFoo,SintSetFoo:,VintSetterGetter
@property (readonly) int intReadonly; Ti,R,VintReadonly
@property (getter=isIntReadOnlyGetter, readonly) int intReadonlyGetter; Ti,R,GisIntReadOnlyGetter
@property (readwrite) int intReadwrite; Ti,VintReadwrite
@property (assign) int intAssign; Ti,VintAssign
@property (retain) id idRetain; T@,&,VidRetain
@property (copy) id idCopy; T@,C,VidCopy
@property (nonatomic) int intNonatomic; Ti,VintNonatomic
@property(nonatomic, readonly, copy) id idReadonlyCopyNonatomic; T@,R,C,VidReadonlyCopyNonatomic
@property(nonatomic, readonly, retain) id idReadonlyRetainNonatomic; T@,R,&,VidReadonlyRetainNonatomic

10 附言

在此蔽莱,運行時機制相關(guān)問題已經(jīng)全部闡述。消息發(fā)送和轉(zhuǎn)發(fā)是Runtime的強大之處戚长,通過它盗冷,您可以為程序增加很多動態(tài)的行為,雖然在實際開發(fā)中很少直接使用這些機制(如直接調(diào)用objc_msgSend)同廉,但了解它們有助于您更多地去了解底層的實現(xiàn)仪糖。其實在實際的編碼過程中柑司,您也可以靈活地使用這些機制,去實現(xiàn)一些特殊的功能锅劝,如hook操作等攒驰。

原文:http://www.apple.com.cn/developer/Documentation/index.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市故爵,隨后出現(xiàn)的幾起案子玻粪,更是在濱河造成了極大的恐慌,老刑警劉巖稠集,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奶段,死亡現(xiàn)場離奇詭異饥瓷,居然都是意外死亡剥纷,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門呢铆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晦鞋,“玉大人,你說我怎么就攤上這事棺克∮贫猓” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵娜谊,是天一觀的道長确买。 經(jīng)常有香客問我,道長纱皆,這世上最難降的妖魔是什么湾趾? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮派草,結(jié)果婚禮上搀缠,老公的妹妹穿的比我還像新娘。我一直安慰自己近迁,他們只是感情好,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拆火,像睡著了一般循未。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搏存,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天缴罗,我揣著相機與錄音,去河邊找鬼祭埂。 笑死面氓,一個胖子當著我的面吹牛兵钮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播舌界,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼掘譬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了呻拌?” 一聲冷哼從身側(cè)響起葱轩,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎藐握,沒想到半個月后靴拱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡猾普,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年袜炕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片初家。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡偎窘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出溜在,到底是詐尸還是另有隱情陌知,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布掖肋,位于F島的核電站仆葡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏志笼。R本人自食惡果不足惜沿盅,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望籽腕。 院中可真熱鬧嗡呼,春花似錦、人聲如沸皇耗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽郎楼。三九已至万伤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間呜袁,已是汗流浹背敌买。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留阶界,地道東北人虹钮。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓聋庵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親芙粱。 傳聞我的和親對象是個殘疾皇子祭玉,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容