Objective-C運(yùn)行時(shí)編程指南

簡(jiǎn)介

Objective-C將很多靜態(tài)語(yǔ)言在編譯和鏈接時(shí)做的事放到了運(yùn)行時(shí)來(lái)處理。只要有可能,它做了一些動(dòng)態(tài)。這意味著該語(yǔ)言需要不只是一個(gè)編譯器,但也是一個(gè)運(yùn)行時(shí)系統(tǒng)來(lái)執(zhí)行的編譯代碼。運(yùn)行時(shí)系統(tǒng)作為一種Objective-C操作系統(tǒng)的; 是什么使該語(yǔ)言工作。

本文著眼于NSObject類(lèi)以及Objective-C程序的運(yùn)行時(shí)系統(tǒng)交互。尤其是,在運(yùn)行時(shí)動(dòng)態(tài)??加載新類(lèi),和轉(zhuǎn)發(fā)消息到其它的對(duì)象。它還提供了有關(guān)如何可以找到有關(guān)對(duì)象的信息,當(dāng)你的程序運(yùn)行的信息。

你應(yīng)該閱讀這個(gè)文件來(lái)獲得的Objective-C運(yùn)行系統(tǒng)是如何工作的理解以及如何利用它。通常情況下,你寫(xiě)一個(gè)Cocoa 應(yīng)用程序很少需要知道和理解這些。

1. 運(yùn)行時(shí)版本和平臺(tái)

Objective-C runtime 在不同平臺(tái)上有不同的版本

1.1 Legacy and Modern Versions

Objective-C runtime 有兩個(gè)版本—"modern"和"legacy"。modern版本在Objective-C 2.0時(shí)引入并且包含一些新特性。legacy版本接口描述在Objective-C 1.0文檔中。modern版本的接口描述在Objective-C Runtime Reference

最顯著的新特點(diǎn)是,modern運(yùn)行時(shí)的實(shí)例變量是"non-fragile"。

在legacy runtime模式下,如果你在類(lèi)中的布局改變實(shí)例變量,你必須從它繼承的類(lèi)重新編譯

在modern runtime模式下,如果你在類(lèi)中的布局改變實(shí)例變量, 你不必從它繼承的類(lèi)重新編譯

另外,modern runtime 支持實(shí)例變量合成為聲明的屬性。

1.2 Platforms

iPhone 應(yīng)用程序和OX v10.5的64位程序以及后來(lái)的都是用了 modern runtime

其他的程序(OS X 桌面32位程序)使用的是legacy runtime.

3. Interacting with the Runtime

Objective-C 程序的運(yùn)行時(shí)交互在三個(gè)不同層次:通過(guò)Objective-C源碼;通過(guò)Foundation框架NSObject類(lèi)中定義的方法;通過(guò)直接調(diào)用運(yùn)行時(shí)的函數(shù)

3.1 Objective-C Source Code

在大多數(shù)情況下,運(yùn)行時(shí)系統(tǒng)自動(dòng)的工作。你可以僅僅通過(guò)編寫(xiě)和編譯Objective-C源碼

當(dāng)你編譯的代碼中包含Objective-C類(lèi)和方法,編譯器會(huì)創(chuàng)建實(shí)現(xiàn)動(dòng)態(tài)語(yǔ)言特性的數(shù)據(jù)結(jié)構(gòu)和函數(shù)調(diào)用。數(shù)據(jù)結(jié)構(gòu)獲取類(lèi)和類(lèi)別的定義以及協(xié)議聲明中找到的信息,它們包括在Objective-C語(yǔ)言中定義一個(gè)類(lèi)和協(xié)議的對(duì)象,以及方法selectors, 實(shí)例變量模板,并且從源代碼中提取其他信息。主要的運(yùn)行時(shí)方法是發(fā)送消息,描述在Messaging。由源代碼消息表達(dá)式調(diào)用。

3.2 NSObject Methods

在Cocoa中的大多數(shù)對(duì)象是NSObject的子類(lèi),所以大多數(shù)對(duì)象繼承了它所定義的方法。(值得注意的是NSProxy類(lèi),?從Message Forwarding查看更多信息)。因此它的方法建立在每個(gè)固有的實(shí)例和對(duì)象。 然而,在少數(shù)情況下,NSObject類(lèi)僅僅定了如何去做的模板,它不提供是有必要的代碼本身。

例如,NSObject類(lèi)定義了一個(gè)返回類(lèi)內(nèi)容描述字符串的實(shí)例方法description。這主要用于調(diào)試——GDB打印方法命令打印方法返回的字符串。NSObject對(duì)方法的實(shí)現(xiàn)并不知道類(lèi)包含,因此他返回一個(gè)描述對(duì)象的名稱(chēng)和地址的字符串。NSObject的子類(lèi)可以此實(shí)現(xiàn)方法的更多細(xì)節(jié)。例如,F(xiàn)oundation的類(lèi)NSArray返回它所包含對(duì)象列表的描述。

一些NSObject方法簡(jiǎn)單地查詢r(jià)untime system 的信息。這些方法允許對(duì)象進(jìn)行自我檢查。這類(lèi)方法的實(shí)例是類(lèi)方法,他要求一個(gè)對(duì)象以確定它的類(lèi),isKindOfClass:?和?isMemberOfClass:,它測(cè)試了類(lèi)在繼承層次對(duì)象的位置;respondsToSelector:,其指示對(duì)象是否接受一個(gè)特定的消息;conformsToProtocol:,其指示對(duì)象是否聲明實(shí)現(xiàn)一個(gè)特定協(xié)議中實(shí)現(xiàn)的方法;methodForSelector:其提供了一種方法的實(shí)現(xiàn)的地址。這樣的方法提供了對(duì)象內(nèi)省得能力。

3.3 Runtime Functions

runtime system 是一個(gè)公共接口包含一組函數(shù)和數(shù)據(jù)結(jié)構(gòu)的動(dòng)態(tài)分享庫(kù),頭文件目錄位置:/usr/include/objc。許多函數(shù)允許用純C去復(fù)制Objective-C 代碼的編譯器實(shí)現(xiàn)。其他基礎(chǔ)功能可以通過(guò)Objective-C 類(lèi)的方法來(lái)導(dǎo)出。這些功能使得能夠在runtime系統(tǒng)中開(kāi)發(fā)其他接口,并且增加開(kāi)發(fā)環(huán)境的工具;在Objective-C編程時(shí)他們不需要。然而,當(dāng)編寫(xiě)一個(gè)Objective-C 程序時(shí)的runtime 函數(shù)可能是有用的。所有這些功能文檔在Objective-C Runtime Reference.

4. Messaging

這章描述消息表達(dá)式轉(zhuǎn)換為objc_msgSend的函數(shù)調(diào)用,以及如何使用方法名來(lái)調(diào)用方法。它然后解釋了如何使用objc_msgSend優(yōu)勢(shì),以及如何繞過(guò)動(dòng)態(tài)綁定---繞過(guò)需要的話。?

4.1 The objc_msgSend Function

在Objective-C, 消息知道運(yùn)行時(shí)才綁定方法的實(shí)現(xiàn),編譯器轉(zhuǎn)換消息表達(dá)式:

[receiver message]

調(diào)用一個(gè)發(fā)送消息函數(shù),objc_msgSend。該函數(shù)需要接收器和在消息中提到的函數(shù)名, 這個(gè)方法選擇器——兩個(gè)主要參數(shù):

objc_msgSend(receiver, selector)

在消息中你也可以傳遞任何參數(shù)給objc_msgSend

objc_msgSend(receiver, selector, arg1, arg2, ...)

消息傳遞函數(shù)做必要的動(dòng)態(tài)綁定的一切:

它首先找到selector指向的程序(方法實(shí)現(xiàn))。由于同樣地方法能夠通過(guò)單獨(dú)類(lèi)的不同方式實(shí)現(xiàn),這個(gè)精確的過(guò)程依賴于類(lèi)的接收器。

然后,它調(diào)用程序,通過(guò)它的接受對(duì)象(它的數(shù)據(jù)指針),以及用于該方法指定的任何參數(shù)。

最后,它通過(guò)程序的返回值作為自身的返回值

發(fā)送消息的關(guān)鍵在于編譯器生成每個(gè)類(lèi)和對(duì)象的結(jié)構(gòu)體,每個(gè)類(lèi)結(jié)構(gòu)體包含 兩個(gè)基本元素:

一個(gè)指向父類(lèi)的指針

一個(gè)調(diào)度表。這個(gè)表具有特定類(lèi)對(duì)該方法標(biāo)識(shí)的地址及相關(guān)聯(lián)的方法選擇器條目,setOrigin::方法選擇器關(guān)聯(lián)setOrigin::方法地址(實(shí)現(xiàn)地址),選擇器的顯示方法與顯示的地址相關(guān)聯(lián),等等。

當(dāng)創(chuàng)建一個(gè)對(duì)象時(shí),內(nèi)存被分配,并且它的實(shí)例變量被初始化。首先在類(lèi)結(jié)構(gòu)中對(duì)象的變量實(shí)際上是一個(gè)指針。這個(gè)指針叫做isa,給了類(lèi)的對(duì)象訪問(wèn)權(quán)限,并通過(guò)類(lèi),來(lái)找到所有他繼承的類(lèi)。

Figure 3-1Messaging Framework

當(dāng)一個(gè)消息被發(fā)送給你一個(gè)對(duì)象,這個(gè)消息傳遞函數(shù)遵循對(duì)象的isa指針的類(lèi)結(jié)構(gòu),其查找調(diào)度表中的方法選擇器。如果它不能在哪里找到方法選擇器,objc_msgSend找到父類(lèi)的指針,并試圖找到其在調(diào)度表中的方法選擇器。連續(xù)的失敗引起objc_msgSend追溯類(lèi)結(jié)構(gòu)至到NSObject類(lèi)。一旦定位到選擇器,函數(shù)調(diào)用調(diào)度表中的方法,并傳遞結(jié)束對(duì)象的數(shù)據(jù)結(jié)構(gòu)。

這種方法實(shí)現(xiàn)在運(yùn)行時(shí)被選中,或者在面向?qū)ο缶幊汤铮捶椒▌?dòng)態(tài)綁定到消息。為了加速消息處理過(guò)程,runtime system緩存了選擇器與方法地址當(dāng)被調(diào)用過(guò)。對(duì)于每一個(gè)類(lèi)有一個(gè)單獨(dú)的緩存,包含選擇器和繼承方法以及在類(lèi)中定義的方法。在搜索調(diào)度表前,消息例程首先檢查接受對(duì)象類(lèi)的緩存(理論上一個(gè)方法一旦被調(diào)用后很大可能被再次調(diào)用)。如果在緩存中存在這個(gè)方法選擇器,消息發(fā)送只比函數(shù)調(diào)用稍微慢一點(diǎn)。一旦一個(gè)程序以及運(yùn)行足夠長(zhǎng)的時(shí)候去"喚醒"它的緩存,幾乎所有發(fā)送的消息能夠找到緩存的方法。緩存的動(dòng)態(tài)增長(zhǎng)以適應(yīng)程勛運(yùn)行的新信息。

4.2 Using Hidden Arguments

當(dāng)objc_msgSend找到方法實(shí)現(xiàn)的過(guò)程中,他調(diào)用程序并且傳遞在消息中所有的參數(shù)。它還傳遞兩個(gè)隱藏的參數(shù)

接受對(duì)象(The receiving object)

方法選擇器(The selector for the method)

這些參數(shù)給每個(gè)方法實(shí)現(xiàn)關(guān)于方法表達(dá)式調(diào)用的準(zhǔn)確信息。他們說(shuō)的"hidden",因?yàn)樗麄儧](méi)有定義在方法的源碼中,而是代碼編譯時(shí)被插入進(jìn)來(lái)的。

盡管這些參數(shù)沒(méi)有被明確聲明,源碼任然可以指向他們(正如它可以指向接受對(duì)象的實(shí)例變量)。一個(gè)方法可以指向接收對(duì)象的本身,并且它自己的選擇器_cmd。在下面的例子中_cmd指向strange方法的選擇器,并且self接收strange消息。

- strange{

id? target = getTheReceiver();

SEL method = getTheMethod();

if ( target == self || method == _cmd )

return nil;

return [target performSelector:method];

}

self是兩個(gè)參數(shù)中比較有用的信息,實(shí)際上,這樣接收對(duì)象實(shí)例變量使變量在方法中更精確。

4.3 Getting a Method Address

避免動(dòng)態(tài)綁定的唯一方式是獲取函數(shù)地址并且直接調(diào)用它,就像他是一個(gè)函數(shù)。在一個(gè)特定方法罕見(jiàn)的被多次執(zhí)行的情況下是釋放的,這樣就避免了消息調(diào)用的每一次開(kāi)銷(xiāo)。

在NSObject類(lèi)中定義的methodForSelector:方法,你可以獲取一個(gè)指向?qū)崿F(xiàn)方法的指針,然后用這個(gè)指針來(lái)調(diào)用程序。methodForSelector: 方法返回的指針必須強(qiáng)制轉(zhuǎn)換為正確類(lèi)型,無(wú)論在返回值還是在參數(shù)類(lèi)型中都應(yīng)包括。

下面的例子展示了thesetFilled:方法實(shí)現(xiàn)是怎么被調(diào)用的:

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ò)程的前兩個(gè)參數(shù)接受對(duì)象(self)和方法選擇器(_cmd)。這些參數(shù)在方法語(yǔ)法中被隱藏但是方法做為一個(gè)函數(shù)調(diào)用時(shí)必須明確。

使用methodForSelector:來(lái)規(guī)避動(dòng)態(tài)綁定大部分有消息通知所需的時(shí)間。然而,這種節(jié)省只有在一個(gè)特定消息被重復(fù)使用多次,如上面for 循環(huán)展示的。

需要注意的是methodForSelector:由Cocoa runtime system 提供。它不是Objective-C語(yǔ)言本身提供的。

5. Dynamic Method Resolution

本章描述了如何提供一個(gè)方法的動(dòng)態(tài)實(shí)現(xiàn)。

5.1 動(dòng)態(tài)方法解析(Dynamic Method Resolution)

某些情況下可能需要提供一個(gè)方法的動(dòng)態(tài)實(shí)現(xiàn)。例如,Objective-C的屬性聲明功能(參見(jiàn)Objective-C語(yǔ)言的屬性聲明)包括 @dynamic 指令:

@dynamic propertyName;

它告訴編譯器,與屬性關(guān)聯(lián)的方法是動(dòng)態(tài)的提供的。

你可以實(shí)現(xiàn)方法resolveInstanceMethod:resolveClassMethod: 去為實(shí)例和類(lèi)方法動(dòng)態(tài)提供一個(gè)給定的程序?qū)崿F(xiàn)。

一個(gè)Objective-C方法根本是至少有兩個(gè)參數(shù)(self和_cmd)的C函數(shù)。你可以用class_addMethod方法給類(lèi)方法添加一個(gè)功能,因此,給出以下功能:

void dynamicMethodIMP(id self, SEL _cmd) {

// implementation ....

}

你可以使用方法resolveInstanceMethod:向類(lèi)中添加一個(gè)方法(方法 resolveThisMethodDynamically),如下:

@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ā))和動(dòng)態(tài)方法解析,在很大程度上正交。一個(gè)類(lèi)在轉(zhuǎn)發(fā)生效前有機(jī)會(huì)動(dòng)態(tài)解決一個(gè)方法。如果respondsToSelector:?或者instancesRespondToSelector:被調(diào)用時(shí), 動(dòng)態(tài)的解析時(shí)首先提供一個(gè)選擇器的IMP的選擇。如果要實(shí)現(xiàn)resolveInstanceMethod:但要對(duì)于特殊選擇器去轉(zhuǎn)發(fā),你返回No對(duì)于這些。

5.2 Dynamic Loading

一個(gè)Objective-C在運(yùn)行時(shí)可以載人并鏈接一個(gè)新類(lèi)。新的代碼并入程序和相同的處理,已在開(kāi)始時(shí)加載雷和類(lèi)別。

動(dòng)態(tài)loading可以用來(lái)做很多不同的事情。例如在系統(tǒng)預(yù)置應(yīng)用程序中的各種模塊是動(dòng)態(tài)加載。

在Cocoa環(huán)境下,動(dòng)態(tài)載入通常運(yùn)行應(yīng)用程序進(jìn)行定制。其他人的寫(xiě)的模塊可以在運(yùn)行時(shí)載入你的程序——就像Interface Builder載入自定義調(diào)色板和OS X系統(tǒng)加載自定義的喜好模塊。可擴(kuò)展模塊可以擴(kuò)展你的應(yīng)用程序功能。它們有助于在你允許膽沒(méi)有預(yù)料或自定義的方式。 你提供框架,其他人提供代碼。

盡管在Mach-O(objc_loadModules,在objc/objc-load.h 定義)文件有運(yùn)行時(shí)函數(shù)來(lái)執(zhí)行Objective-C模塊的動(dòng)態(tài)加載,Cocoa的NSBundle類(lèi)提供了一個(gè)更方便的接口用于動(dòng)態(tài)加載——一個(gè)面向?qū)ο蟛⒓闪讼嚓P(guān)服務(wù)。在Foundation framework 參考中NSBundle類(lèi)規(guī)范中關(guān)于一個(gè)NSBundle類(lèi)及其使用參考。?

6 消息轉(zhuǎn)發(fā)(Message Forwarding)

發(fā)送一個(gè)消息給你個(gè)對(duì)象卻不處理消息是錯(cuò)誤的。然而,在公布錯(cuò)誤前,runtime system系統(tǒng)給接受對(duì)象第二次機(jī)會(huì)來(lái)處理消息。

6.1 轉(zhuǎn)發(fā)(Forwarding)

如果你發(fā)送一個(gè)消息到一個(gè)不處理消息的對(duì)象,在公布錯(cuò)誤前,runtime發(fā)送這個(gè)對(duì)象一個(gè)NSInvocation對(duì)象做文藝參數(shù)的forwardInvocation:消息。 ?該NSInvocation對(duì)象封裝原始消息和它已通過(guò)的參數(shù)。

你可以實(shí)現(xiàn)這個(gè)forwardInvocation:方法,得到一個(gè)消息的默認(rèn)反饋,去避免某些其他方式的錯(cuò)誤。正如名字所暗示的,forwardInvocation:通常用于轉(zhuǎn)發(fā)消息到另一個(gè)對(duì)象。

要查看轉(zhuǎn)發(fā)的范圍和目的,想想一下以下場(chǎng)景:假設(shè),首先你正設(shè)計(jì)一個(gè)對(duì)象,能對(duì)一個(gè)所謂談判的消息做出反應(yīng),并且希望包含對(duì)另一個(gè)對(duì)象的反饋。你可以在negotiate方法的實(shí)現(xiàn)很容易的發(fā)送anegotiate消息給其他對(duì)象。

更進(jìn)一步,假設(shè)你希望你對(duì)anegotiate消息的響應(yīng)在另一個(gè)類(lèi)中實(shí)現(xiàn)響應(yīng)。你的類(lèi)繼承自其他類(lèi)會(huì)很容易坐到這一點(diǎn)。然而,它可能無(wú)法以這種方式安排事情。可能有好的理由,為什么你的類(lèi)和實(shí)現(xiàn)negotiate的類(lèi)在繼承層次的不同分支。

即使你的類(lèi)不能繼承negotiate方法,你仍然可以"借用"它,通過(guò)實(shí)現(xiàn)一個(gè)簡(jiǎn)單地將消息傳遞到其他類(lèi)的一個(gè)實(shí)例方法來(lái)實(shí)現(xiàn):

- (id)negotiate{

? ? ?if ( [someOtherObject respondsTo:@selector(negotiate)] )

? ? ? ? ? ? ? ? ? ? ? ? return [someOtherObject negotiate];

? ? ? ?return self;

}

這種做事方式可能變得有點(diǎn)麻煩,尤其你有一些消息需要從你的對(duì)象傳遞給其他對(duì)象。你必須實(shí)現(xiàn)一個(gè)方法來(lái)覆蓋每一個(gè)你想從其他類(lèi)借用的方法。此外,你在寫(xiě)代碼時(shí)處理你你想要轉(zhuǎn)發(fā)的全套消息是不可能的。這組依賴于runtime時(shí)的事件,當(dāng)一個(gè)新的類(lèi)或者方法被實(shí)現(xiàn)時(shí)可能會(huì)改變。

由forwardInvocation:提供第二次機(jī)會(huì):消息提供了對(duì)于這個(gè)問(wèn)題的解決,?而另一個(gè)是動(dòng)態(tài)的而不是靜態(tài)的。它的工作原理是這樣的:當(dāng)一個(gè)對(duì)象不能對(duì)消息做出響應(yīng)因?yàn)樵谙⒅羞x擇器沒(méi)有匹配到方法, runtime system通過(guò)發(fā)送一個(gè)forwardInvocation:消息給對(duì)象。每一個(gè)從NSObject繼承的類(lèi)都有forwardInvocation:方法。然而,NSObject的版本方法只是簡(jiǎn)單地調(diào)用doesNotRecognizeSelector:。通過(guò)重寫(xiě)NSObject的版本并且實(shí)現(xiàn),你可以利用該forwardInvocation:獲得優(yōu)勢(shì):消息被提供轉(zhuǎn)發(fā)給其他對(duì)象。

要轉(zhuǎn)發(fā)一個(gè)消息,forwardInvocation:方法需要做的如下:

確定該消息應(yīng)該去哪

帶上原來(lái)的參數(shù)發(fā)送它

消息可以用invokeWithTarget:方法發(fā)送:

- (void)forwardInvocation:(NSInvocation *)anInvocation

{

? ? ?if ([someOtherObject respondsToSelector:[anInvocation selector]])

? ? ? ? ? ? ? ? ? ? [anInvocation invokeWithTarget:someOtherObject];

? ? ?else

? ? ? ? ? ? ? ? ? ? ?[super forwardInvocation:anInvocation];

}

被轉(zhuǎn)發(fā)消息的返回值被返回給原始的發(fā)送者。所有類(lèi)型的返回值可以被傳遞給發(fā)送者,包括ids,structures和雙精度浮點(diǎn)數(shù)。

一個(gè)forwardInvocation:方法可以作為未識(shí)別消息的集散地,分發(fā)他們到不同的接收器。或者它可以作為一個(gè)傳輸站,發(fā)送所有消息到同一個(gè)目的地。它可以轉(zhuǎn)發(fā)一個(gè)消息到另一個(gè),或者干脆"吞"一些消息,所以沒(méi)有返回值也沒(méi)有錯(cuò)誤。一個(gè)forwardInvocation:可以合并多個(gè)消息到一個(gè)當(dāng)以響應(yīng)。forwardInvocation:做的是實(shí)現(xiàn)。但是,它提供了一種為轉(zhuǎn)發(fā)鏈接對(duì)象開(kāi)辟程序設(shè)計(jì)的可能性。

關(guān)于 forwarding 和 invocations更多信息, 查看Foundation framework索引的NSInvocation類(lèi)

6.2 轉(zhuǎn)發(fā)和多繼承(Forwarding and Multiple Inheritance)

轉(zhuǎn)發(fā)模仿了多繼承,可以向在Objective-C程序中的對(duì)多繼承的一些效果。如圖5-1所示,一個(gè)對(duì)象響應(yīng)一個(gè)消息聽(tīng)過(guò)借用或繼承在其他類(lèi)中實(shí)現(xiàn)的方法。

Figure 5-1Forwarding

在這個(gè)例子中,一個(gè)Warrior類(lèi)的實(shí)例變量轉(zhuǎn)發(fā)一個(gè)anegotiate消息給一個(gè)Diplomat類(lèi)的實(shí)例。Warrior會(huì)出現(xiàn)像Diplomat類(lèi)的negotiate。它將返回一個(gè)反饋給negotiate消息,并且為所有實(shí)際目的做出回應(yīng)(盡管做出反饋的是Diplomat類(lèi))。

轉(zhuǎn)發(fā)消息的對(duì)象從兩個(gè)繼承體系"繼承"——它自己的分支和響應(yīng)消息的對(duì)象。在上面的例子中,它看起來(lái)像Warrior繼承自Diplomat類(lèi)和他的父類(lèi)。

轉(zhuǎn)發(fā)提供了大多數(shù)希望從多重繼承中想要的功能。然而,兩者之間有一個(gè)重要的區(qū)別:多重繼承在單一對(duì)象中集成不同能力。它趨向于大型,多層面的對(duì)象。而另一方面,轉(zhuǎn)發(fā)分配不同的職責(zé)給不同的對(duì)象。它分解問(wèn)題為更小的對(duì)象,但在某種程度上是透明的的消息轉(zhuǎn)發(fā)到相關(guān)聯(lián)對(duì)象。

6.3 替代對(duì)象(Surrogate Objects)

轉(zhuǎn)發(fā)不僅模仿多重繼承,這也使你開(kāi)發(fā)出輕量級(jí)的對(duì)象,代表或"掩蓋"較大幅度的對(duì)象。其他對(duì)象的替代對(duì)象和漏斗消息。

在Objective-C編程語(yǔ)言中"Remote Messaging"代理是這樣一個(gè)替代品。代理轉(zhuǎn)發(fā)消息到一個(gè)遠(yuǎn)程接收器,確保參數(shù)值被復(fù)制并通過(guò)連接檢索,等等。但它不會(huì)嘗試做出更多地行為,它不會(huì)復(fù)制遠(yuǎn)程對(duì)象的功能而只是簡(jiǎn)單地給出遠(yuǎn)程對(duì)象的地址,可以在另一個(gè)應(yīng)用程序接受對(duì)象的地方。

其他種類(lèi)的替代對(duì)象也是可能的。舉個(gè)例子,你有一個(gè)操作大量數(shù)據(jù)對(duì)象——或許創(chuàng)建一個(gè)復(fù)雜的圖片或讀取磁盤(pán)文件的內(nèi)容。設(shè)置這個(gè)對(duì)象可能非常耗時(shí),所以你選擇懶加載——在它需要或者系統(tǒng)資源閑置時(shí)。同時(shí),你需要這個(gè)對(duì)象需要至少一個(gè)占位符,以便應(yīng)用中的其他對(duì)象正常工作。

在這種情況下,你可以在最初創(chuàng)建時(shí)不全部生成,而是一個(gè)輕量級(jí)的替代。這個(gè)對(duì)象做一些事情,如有關(guān)數(shù)據(jù)答題,但大多數(shù)只是一個(gè)多對(duì)象的地址,當(dāng)時(shí)間觸發(fā)轉(zhuǎn)發(fā)消息給它。當(dāng)代理的forwardInvocation:方法首先收到發(fā)往另一個(gè)對(duì)象的消息,確保對(duì)象存在如果不存在創(chuàng)建它。大對(duì)象的所有消息都通過(guò)替代,這樣,對(duì)于程序的其他部分而言,替代和大對(duì)象是一樣的。

6.4 轉(zhuǎn)發(fā)和繼承(Forwarding and Inheritance)

雖然轉(zhuǎn)發(fā)模仿繼承,但NSObject類(lèi)從不混淆兩者。像respondsToSelector: ?和 isKindOfClass:方法只在繼承層次的,從來(lái)沒(méi)有在轉(zhuǎn)發(fā)鏈上。例如一個(gè)Warrior對(duì)象被詢問(wèn)是否響應(yīng)一個(gè)negotiate消息。

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

...

結(jié)果是NO,雖然能夠無(wú)誤的收到negotiate消息并回應(yīng)它,從某種意義上說(shuō),消息被轉(zhuǎn)發(fā)給Diplomat(見(jiàn)圖5-1)。

在很多情況下,NO是正確地答案。但它也可能不是。如果你用轉(zhuǎn)發(fā)去設(shè)置一個(gè)代理對(duì)象或者擴(kuò)展一個(gè)類(lèi)的功能,轉(zhuǎn)發(fā)機(jī)制夜巡應(yīng)該是透明的繼承。如果你轉(zhuǎn)發(fā)消息的對(duì)象繼承了這些行為,你需要重新實(shí)現(xiàn)respondsToSelector:?和 isKindOfClass:方法,包括轉(zhuǎn)發(fā)算法:

- (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:方法也應(yīng)該反映轉(zhuǎn)發(fā)算法。如果協(xié)議被使用,conformsToProtocol:方法同樣應(yīng)該添加到列表中。同樣,如果一個(gè)對(duì)象轉(zhuǎn)發(fā)接受到得任何遠(yuǎn)程消息,它應(yīng)當(dāng)有一個(gè)methodSignatureForSelector:轉(zhuǎn)發(fā)消息反饋的方法的準(zhǔn)確描述。例如,如果一個(gè)對(duì)象能夠轉(zhuǎn)發(fā)消息到其代理,你需要將methodSignatureForSelector:實(shí)現(xiàn)如下:

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector

{

? ? ? ?NSMethodSignature* signature = [super methodSignatureForSelector:selector];

? ? ? if (!signature) {

? ? ? ? ? ? ? ? ? ? signature = [surrogate methodSignatureForSelector:selector];

? ? ? }

? ? ? ?return signature;

}

你可能考慮把在私有代碼中考慮轉(zhuǎn)發(fā)算法并擁有這些方法,包含并調(diào)用forwardInvocation:

7. 類(lèi)型編碼(Type Encodings)

為了幫助runtime system,編譯器編碼了返回值和參數(shù)類(lèi)型為字符串的方法,并且把字符串與方法選擇器關(guān)聯(lián)。它使用的編碼方案可以在上下文中使用編譯指令@encode()是公開(kāi)可用的。當(dāng)給定一個(gè)類(lèi)型,@encode()返回一個(gè)類(lèi)型的字符串編碼。類(lèi)型可以是基礎(chǔ)類(lèi)型,如int,pointer,structure或union,或類(lèi)名——實(shí)際上,任何類(lèi)型可以被C的sizeof()操作符使用。

char *buf1 = @encode(int **);

char *buf2 = @encode(struct key);

char *buf3 = @encode(Rectangle);

下表列出了類(lèi)型編碼。需要注意的是其實(shí)很多時(shí)候?yàn)榱舜鏅n和分發(fā)的目的編碼對(duì)象,不過(guò),這里有你編寫(xiě)編碼器時(shí)不能使用的編碼列表,并有你希望使用但不能被@encode()編碼器生成。


Table 6-1?Objective-C type encodings
Table 6-1Objective-C type encodings

數(shù)組的類(lèi)型編碼是一個(gè)封閉的方括號(hào);數(shù)組中元素的數(shù)目在數(shù)據(jù)類(lèi)型前可以馬上指定。 例如一個(gè)12個(gè)指針的浮點(diǎn)數(shù)組可以編碼為:

[12^f]

結(jié)構(gòu)體是一個(gè)特殊的括號(hào)和括號(hào)里的unions。結(jié)構(gòu)體標(biāo)簽被列在第一位,接著是等號(hào)和在隊(duì)列中的結(jié)構(gòu)體字段編碼。例如,這個(gè)結(jié)構(gòu)體:

typedef struct example {

? ? ? id? anObject;

? ? ?char *aString;

? ? ?int? anInt;

} Example;

將被像這樣編碼:

{example=@*i}

同樣地編碼結(jié)果是否已定義的類(lèi)型名(Example)或者結(jié)果體標(biāo)識(shí)(example)被傳遞給@encode()。結(jié)構(gòu)體指針編碼對(duì)于結(jié)構(gòu)體字段攜帶了相同的信息:

^{example=@*i}

不過(guò),另一方面它間接消除了內(nèi)部規(guī)則:

^^{example}

對(duì)象被當(dāng)做結(jié)構(gòu)對(duì)待。例如NSObject類(lèi)用@encode()編碼得到:

{NSObject=#}

NSObject類(lèi)聲明是一個(gè)變量實(shí)例,isa。請(qǐng)注意盡管@encode()指令不直接返回他們,當(dāng)他們?cè)趨f(xié)議中聲明方法用runtime system用額外的編碼如表6-2。

Table 6-2 ?Objective-C method encodings

8. 聲明屬性(Declared Properties)

當(dāng)編譯器遇到屬性聲明時(shí)(參見(jiàn)Objective-C語(yǔ)言屬性聲明),它通常會(huì)生成一個(gè)與封閉類(lèi),類(lèi)別或協(xié)議相關(guān)聯(lián)的描述性元數(shù)據(jù)。你可以通過(guò)類(lèi)或協(xié)議支持查找屬性的函數(shù)來(lái)訪問(wèn)元數(shù)據(jù),獲得屬性類(lèi)型作為@encode字符串,并復(fù)制屬性列表的屬性作為一個(gè)C字符串?dāng)?shù)組。為每一個(gè)類(lèi)和協(xié)議聲明可使用的屬性列表。

8.1 屬性類(lèi)型和功能(Property Type and Functions)

Property結(jié)構(gòu)體定義了一個(gè)不透明的句柄屬性描述符。

typedef struct objc_property *Property;

?你可以使用class_copyPropertyList? 和protocol_copyPropertyList ?去檢索一個(gè)類(lèi)(包括加載的類(lèi)別)和一個(gè)協(xié)議相關(guān)聯(lián)的屬性數(shù)組。

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

例如,給出以下類(lèi)聲明:

@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ù)發(fā)現(xiàn)屬性名:

const char *property_getName(objc_property_t property)

你可以用class_getProperty和protocol_getProperty得到一個(gè)類(lèi)或協(xié)議給定名稱(chēng)的屬性參考:

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ù)來(lái)發(fā)現(xiàn)名稱(chēng)和屬性@encode類(lèi)型的字符串。對(duì)于編碼字符串的細(xì)節(jié),查看Type Encodings,該字符串的詳細(xì)吸吸,查看Property Type String?和?Property Attribute Description Examples

const char *property_getAttributes(objc_property_t property)

把這些一起使用起來(lái),你可以打印一個(gè)類(lèi)相關(guān)聯(lián)的屬性的了列表用如下代碼:

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

}

8.2 屬性類(lèi)型字符串(Property Type String)

你可以使用property_getAttributes 函數(shù)去發(fā)現(xiàn)名字,屬性的@encode字符串, 和屬性的其他屬性。字符串在使用@encode類(lèi)型是在開(kāi)始使用T和一個(gè)逗號(hào),在結(jié)束時(shí)跟實(shí)例變量的名字和一個(gè)V這些中間,屬性被指定使用如下描述,以逗號(hào)分隔:

Table 7-1Declared property type encodings

8.3 屬性類(lèi)型描述示例(Property Attribute Description Examples)

給出如下定義:

enum FooManChu { FOO, MAN, CHU };

struct YorkshireTeaStruct { int pot; char lady; };

typedef struct YorkshireTeaStruct YorkshireTeaStructType;

union MoneyUnion { float alone; double down; };

下表顯示了實(shí)力型聲明和property_getAttributes:返回的響應(yīng)字符串:

Introduction

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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