Objective - c Runtime編程指南

前言

此文檔為官方Runtime文檔的翻譯,主要是為了方便學(xué)習(xí),很多地方翻譯有誤,如有需要請選擇性閱讀。



介紹

重要提示:此文檔不再更新。有關(guān)蘋果SDKs的最新信息,請訪問文檔網(wǎng)站

Objective-C語言從編譯時間和鏈接時間到運行時推遲了盡可能多的決策。只要有可能,它就會動態(tài)地完成任務(wù)。這意味著該語言不僅需要編譯器,還需要運行時系統(tǒng)來執(zhí)行編譯代碼。運行時系統(tǒng)充當(dāng)Objective-C語言的一種操作系統(tǒng);這就是語言運作的原因。

本文檔介紹了NSObject類以及Objective-C程序如何與運行時系統(tǒng)交互。特別是,它檢查了在運行時動態(tài)加載新類并將消息轉(zhuǎn)發(fā)給其他對象的范例。它還提供有關(guān)在程序運行時如何查找有關(guān)對象的信息。

您應(yīng)該閱讀本文檔以了解Objective-C運行時系統(tǒng)的工作原理以及如何利用它。但是,通常情況下,您應(yīng)該沒有理由需要了解和理解這些材料來編寫Cocoa應(yīng)用程序。

目錄文件

本文檔包含以下章節(jié):

  • 一、runtime版本和平臺
  • 二、與runtime交互
  • 三、消息
  • 四、動態(tài)方法解析
  • 五、消息轉(zhuǎn)發(fā)
  • 六、類型編碼
  • 七、屬性聲明

參考

Objective-C運行時參考描述了Objective-C運行時支持庫的數(shù)據(jù)結(jié)構(gòu)和功能。 您的程序可以使用這些接口與Objective-C運行時系統(tǒng)進行交互。 例如,您可以添加類或方法,或獲取已加載類的所有類定義的列表。

使用Objective-C編程描述了Objective-C語言。

Objective-C發(fā)行說明描述了最新版OS X中Objective-C運行時的一些更改。

一、runtime版本和平臺

不同平臺上有不同版本的Objective-C runtime

1、'Legacy '和 'Modern' 兩個版本

Objective-C運行時有兩個版本 “過去”和“現(xiàn)在”。 Modern版本隨Objective-C 2.0一起推出,包含許多新功能。 Objective-C 1.0 參考中描述了Legacy版本的運行時的編程接口; Objective-C運行時參考中描述了Modern版本的運行時的編程接口。

最值得注意的新功能是Modern運行時中的實例變量是“健壯的”:

  • 在Legacy運行時中,如果更改類中實例變量的布局,則必須重新編譯從其繼承的類。
  • 在Modern運行時中,如果更改類中實例變量的布局,則不必重新編譯從其繼承的類。

此外,Modern運行時支持聲明屬性的實例變量合成(請參閱Objective-C編程語言中的聲明屬性)。

2、平臺

OS X v10.5及更高版本上的iPhone應(yīng)用程序和64位程序使用Modern版本的運行時。
其他程序(OS X桌面上的32位程序)使用運行時的Legacy版本。

二、與runtime交互

Objective-C程序在三個不同的情況下與運行時系統(tǒng)交互:
通過Objective-C源代碼;
通過Foundation框架的NSObject類中定義的方法;
通過直接調(diào)用運行時函數(shù)。

1、Objective-C源代碼

在大多數(shù)情況下,運行時系統(tǒng)會在后臺自動運行。 您只需編寫和編譯Objective-C源代碼即可使用它。

編譯包含Objective-C類和方法的代碼時,編譯器會創(chuàng)建實現(xiàn)該語言動態(tài)特性的數(shù)據(jù)結(jié)構(gòu)和函數(shù)調(diào)用。 數(shù)據(jù)結(jié)構(gòu)捕獲在類和類別定義以及協(xié)議聲明中找到的信息; 它們包括在Objective-C編程語言中定義類和協(xié)議中討論的類和協(xié)議對象,以及方法選擇器,實例變量模板和從源代碼中提取的其他信息。 主要運行時函數(shù)是發(fā)送消息的函數(shù),如Messaging中所述。 它由源代碼消息表達式調(diào)用。

2、NSObject方法

Cocoa中的大多數(shù)對象都是NSObject類的子類,因此大多數(shù)對象都繼承了它定義的方法。 (值得注意的例外是NSProxy類;有關(guān)更多信息,請參閱消息轉(zhuǎn)發(fā)。)因此,其方法建立了每個實例和每個類對象固有的行為。但是,在少數(shù)情況下,NSObject類只定義了應(yīng)該如何完成某事的模板;它本身并不提供所有必要的代碼。

例如,NSObject類定義一個描述實例方法,該方法返回描述類內(nèi)容的字符串。這主要用于調(diào)試--GDB print-object命令打印從此方法返回的字符串。 NSObject對此方法的實現(xiàn)不知道該類包含什么,因此它返回一個包含對象名稱和地址的字符串。 NSObject的子類可以實現(xiàn)此方法以返回更多詳細(xì)信息。例如,F(xiàn)oundation類NSArray返回它包含的對象的描述列表。

一些NSObject方法只是查詢運行時系統(tǒng)的信息。這些方法允許對象執(zhí)行內(nèi)省。這種方法的例子是類方法,它要求對象識別它的類; isKindOfClass:和isMemberOfClass:,它測試對象在繼承層次結(jié)構(gòu)中的位置; respondsToSelector:,表示對象是否可以接受特定消息; conformsToProtocol :,表示對象是否聲明實現(xiàn)特定協(xié)議中定義的方法;和methodForSelector :,它提供方法實現(xiàn)的地址。這些方法使對象能夠自省內(nèi)省。

3、runtime的功能

運行時系統(tǒng)是一個動態(tài)共享庫,其公共接口由位于/ usr / include / objc目錄中的頭文件中的一組函數(shù)和數(shù)據(jù)結(jié)構(gòu)組成。 其中許多函數(shù)允許您使用plain C來復(fù)制編寫Objective-C代碼時編譯器所執(zhí)行的操作。 其他構(gòu)成了通過NSObject類的方法導(dǎo)出的功能的基礎(chǔ)。 這些函數(shù)可以開發(fā)運行時系統(tǒng)的其他接口,并生成增強開發(fā)環(huán)境的工具; 在Objective-C中編程時不需要它們。 但是,在編寫Objective-C程序時,一些運行時函數(shù)有時可能會有用。 所有這些功能都記錄在Objective-C運行時參考中。

三、消息

本章介紹如何將消息表達式轉(zhuǎn)換為objc_msgSend函數(shù)調(diào)用,以及如何按名稱引用方法。 然后,它解釋了如何利用objc_msgSend,以及如何 -如果需要 - 您可以繞過動態(tài)綁定。

1、objc_msgSend函數(shù)

在Objective-C中,消息在運行時之前不會綁定到方法實現(xiàn)。 編譯器轉(zhuǎn)換消息表達式;

[receiver message]

對消息傳遞函數(shù)objc_msgSend的調(diào)用。 此函數(shù)將接收者和消息中提到的方法的名稱(即方法選擇器)作為其兩個主要參數(shù):

objc_msgSend(receiver, selector)

消息中傳遞的任何參數(shù)也會傳遞給objc_msgSend:

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

消息傳遞功能可以完成動態(tài)綁定所需的一切:

  • 它首先找到選擇器引用的過程(方法實現(xiàn))。 由于可以通過單獨的類以不同方式實現(xiàn)相同的方法,因此找到的精確過程取決于接收者的類。
  • 然后它調(diào)用該方法實現(xiàn),將接收對象(指向其數(shù)據(jù)的指針)以及為該方法指定的任何參數(shù)傳遞給它。
  • 最后,它將方法實現(xiàn)的返回值作為自己的返回值傳遞。
    (注意:編譯器生成對消息傳遞功能的調(diào)用。 你永遠不應(yīng)該直接在你編寫的代碼中調(diào)用它。)

消息傳遞的關(guān)鍵在于編譯器為每個類和對象構(gòu)建的結(jié)構(gòu)。 每個Class結(jié)構(gòu)都包括以下兩個基本要素:

  • 指向父類的指針。
  • 類調(diào)度表。 此表具有將方法選擇器與它們標(biāo)識的方法的特定于類的地址相關(guān)聯(lián)的條目。 setOrigin ::方法的選擇器與(實現(xiàn)的過程)setOrigin ::的地址相關(guān)聯(lián),顯示方法的選擇器與顯示的地址相關(guān)聯(lián),依此類推。

創(chuàng)建新對象時,將分配其內(nèi)存,并初始化其實例變量。 對象變量中的第一個是指向其類結(jié)構(gòu)的指針。 這個名為isa的指針使對象可以訪問它的類,并通過該類訪問它繼承的所有類。
(注意:雖然嚴(yán)格來說不是語言的一部分,但isa指針是對象使用Objective-C運行時系統(tǒng)所必需的。 在結(jié)構(gòu)定義的任何字段中,對象需要與結(jié)構(gòu)objc_object(在objc / objc.h中定義)“等效”。 但是,您很少(如果有的話)需要創(chuàng)建自己的根對象,并且從NSObject或NSProxy繼承的對象會自動擁有isa變量。)

類和對象結(jié)構(gòu)的這些元素如圖3-1所示。


圖3-1消息傳遞框架

當(dāng)消息發(fā)送到對象時,消息傳遞函數(shù)遵循對象的isa指向類結(jié)構(gòu)的指針,在該結(jié)構(gòu)中它查找調(diào)度表中的方法選擇器。如果它找不到那里的選擇器,objc_msgSend跟隨指向超類的指針并嘗試在其調(diào)度表中找到選擇器。連續(xù)失敗導(dǎo)致objc_msgSend爬上類層次結(jié)構(gòu),直到它到達NSObject類。一旦找到選擇器,該函數(shù)就會調(diào)用表中輸入的方法并將接收對象的數(shù)據(jù)結(jié)構(gòu)傳遞給它。

這是在運行時選擇方法實現(xiàn)的方式 - 或者在面向?qū)ο缶幊痰男g(shù)語中,方法動態(tài)地綁定到消息。

為了加速消息傳遞過程,運行時系統(tǒng)在使用時后會緩存方法的選擇器和地址。每個類都有一個單獨的緩存器,它可以包含繼承方法的選擇器以及類中定義的方法。在搜索調(diào)度表之前,消息傳遞例程首先檢查接收對象類的高速緩存(理論上可能會再次使用一次使用的方法)。如果方法選擇器在緩存中,則消息傳遞僅比函數(shù)調(diào)用稍慢。一旦程序運行了足夠長的時間來“預(yù)熱”其緩存,它發(fā)送的幾乎所有消息都會找到一個緩存的方法。隨著程序的運行,緩存會動態(tài)增長以容納新消息。

2、使用隱藏參數(shù)

當(dāng)objc_msgSend找到實現(xiàn)方法的過程時,它會調(diào)用該過程并將其傳遞給消息中的所有參數(shù)。 它還傳遞了兩個隱藏的參數(shù):

  • 接收對象
  • 方法的選擇器

這些參數(shù)為每個方法實現(xiàn)提供有關(guān)調(diào)用它的消息表達式的兩半的顯式信息。 它們被稱為“隱藏”,因為它們未在定義方法的源代碼中聲明。 它們在編譯代碼時插入到實現(xiàn)中。

雖然這些參數(shù)沒有顯式聲明,但源代碼仍然可以引用它們(就像它可以引用接收對象的實例變量一樣)。 方法將接收對象稱為self,并將其自身的選擇器稱為_cmd。 在下面的示例中,_cmd引用strange方法的選擇器,并指向接收strange消息的對象的self。

- strange
{
    id  target = getTheReceiver();
    SEL method = getTheMethod();
 
    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}

self是兩個論點中更有用的。 實際上,它是接收對象的實例變量可用于方法定義的方式。

3、獲取方法地址

規(guī)避動態(tài)綁定的唯一方法是獲取方法的地址并直接調(diào)用它,就好像它是一個函數(shù)一樣。 這種情況可能適用于極少數(shù)情況下,特定方法將連續(xù)多次執(zhí)行,并且您希望每次執(zhí)行該方法時都避免消息傳遞的開銷。

使用NSObject類,methodForSelector:中定義的方法,您可以請求指向?qū)崿F(xiàn)方法的過程的指針,然后使用指針調(diào)用該過程。 methodForSelector:返回的指針必須小心地轉(zhuǎn)換為正確的函數(shù)類型。 返回和參數(shù)類型都應(yīng)包含在強制轉(zhuǎn)換中。

下面的示例顯示了如何調(diào)用實現(xiàn)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);

傳遞給過程的前兩個參數(shù)是接收對象(self)和方法選擇器(_cmd)。 這些參數(shù)隱藏在方法語法中,但在將方法作為函數(shù)調(diào)用時必須使其顯式化。

使用methodForSelector:繞過動態(tài)綁定可以節(jié)省消息傳遞所需的大部分時間。 但是,只有在特定消息重復(fù)多次的情況下,節(jié)省才會顯著,如上面所示的for循環(huán)。

注意,methodForSelector:由Cocoa運行時系統(tǒng)提供; 它不是Objective-C語言本身的一個特性。

四、動態(tài)方法解析

本章介紹如何動態(tài)提供方法的實現(xiàn)。

1、動態(tài)方法解析

在某些情況下,您可能希望動態(tài)提供方法的實現(xiàn)。 例如,Objective-C聲明的屬性功能(參見Objective-C編程語言中的聲明屬性)包含@dynamic指令:

@dynamic propertyName;

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

您可以實現(xiàn)方法resolveInstanceMethod:和resolveClassMethod:分別為實例和類方法動態(tài)提供給定選擇器的實現(xiàn)。

Objective-C方法只是一個C函數(shù),至少需要兩個參數(shù) - self和_cmd。 您可以使用函數(shù)class_addMethod將函數(shù)作為方法添加到類中。 因此,給出以下功能:

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

您可以使用解析InstanceMethod將其動態(tài)添加到類中作為方法(動態(tài)調(diào)用resolveThis方法):如下所示:

@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ā)機制啟動之前動態(tài)解析方法。如果調(diào)用respondsToSelector:或instancesRespondToSelector:,則動態(tài)方法解析器有機會首先為選擇器提供IMP。 如果您實現(xiàn)resolveInstanceMethod:但希望通過轉(zhuǎn)發(fā)機制實際轉(zhuǎn)發(fā)特定選擇器,則為這些選擇器返回NO。

2、動態(tài)加載

Objective-C程序可以在運行時加載和鏈接新類和類別。新代碼被合并到程序中,并且與開始時加載的類和類別完全相同。

動態(tài)加載可用于執(zhí)行許多不同的操作。例如,系統(tǒng)首選項應(yīng)用程序中的各種模塊是動態(tài)加載的。

在Cocoa環(huán)境中,動態(tài)加載通常用于允許自定義應(yīng)用程序。其他人可以編寫程序在運行時加載的模塊 - 就像Interface Builder加載自定義調(diào)色板并且OS X系統(tǒng)首選項應(yīng)用程序加載自定義首選項模塊一樣??杉虞d模塊擴展了應(yīng)用程序的功能。他們以您允許的方式為其做出貢獻,但無法預(yù)測或定義自己。您提供框架,但其他人提供代碼。

雖然有一個運行時函數(shù)可以在Mach-O文件中執(zhí)行Objective-C模塊的動態(tài)加載(objc_loadModules,在objc / objc-load.h中定義),但Cocoa的NSBundle類為動態(tài)加載提供了一個非常方便的接口 - 一個是對象 - 面向并與相關(guān)服務(wù)集成。有關(guān)NSBundle類及其用法的信息,請參閱Foundation框架參考中的NSBundle類規(guī)范。有關(guān)Mach-O文件的信息,請參閱OS X ABI Mach-O文件格式參考。

五、消息轉(zhuǎn)發(fā)

將消息發(fā)送到不能處理該消息的類對象是錯誤的。 但是,在報錯之前,運行時系統(tǒng)為接收對象提供了處理消息的第二次機會。

1、轉(zhuǎn)發(fā)

如果向未處理該消息的對象發(fā)送消息,則在報錯之前,運行時會向?qū)ο蟀l(fā)送一個forwardInvocation:消息,其中NSInvocation對象作為其唯一參數(shù) - NSInvocation對象封裝原始消息和傳遞的參數(shù)用它。

您可以實現(xiàn)forwardInvocation:方法來為消息提供默認(rèn)響應(yīng),或以其他方式避免錯誤。顧名思義,forwardInvocation:通常用于將消息轉(zhuǎn)發(fā)給另一個對象。

要查看轉(zhuǎn)發(fā)的范圍和意圖,請設(shè)想以下方案:首先,假設(shè)您正在設(shè)計一個可以響應(yīng)名為negotiate的消息的對象,并且您希望其響應(yīng)包含另一種對象的響應(yīng)。您可以通過將negotiate消息傳遞給您實現(xiàn)的negotiate方法主體中的其他對象來輕松完成此操作。

更進一步,假設(shè)您希望對象對negotiate消息的響應(yīng)完全是在另一個類中實現(xiàn)的響應(yīng)。實現(xiàn)此目的的一種方法是讓您的類從其他類繼承該方法。但是,可能無法以這種方式安排事情。您的類和實現(xiàn)negotiate的類可能位于繼承層次結(jié)構(gòu)的不同分支中,這可能是有充分理由的。

即使你的類不能繼承negotiate方法,你仍然可以通過實現(xiàn)一個簡單地將消息傳遞給另一個類的實例的方法版本來“借用”它:

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

這種做事方式可能會有點麻煩,特別是如果有許多消息要將對象傳遞給另一個對象。您必須實現(xiàn)一種方法來涵蓋您希望從其他類借用的每種方法。而且,在您編寫代碼時,您可能無法處理您可能想要轉(zhuǎn)發(fā)的完整消息集。該集合可能取決于運行時的事件,并且可能會在將來實現(xiàn)新方法和類時發(fā)生變化。

forwardInvocation:消息提供的第二次機會為這個問題提供了一個不那么特別的解決方案,一個是動態(tài)的而不是靜態(tài)的。它的工作方式如下:當(dāng)一個對象無法響應(yīng)消息,因為它沒有與消息中的選擇器匹配的方法時,運行時系統(tǒng)通過向?qū)ο蟀l(fā)送一個forwardInvocation:消息來通知該對象。每個對象都從NSObject類繼承一個forwardInvocation:方法。但是,NSObject的方法版本只調(diào)用doesNotRecognizeSelector:。通過重寫NSObject的版本并實現(xiàn)自己的版本,您可以利用forwardInvocation:消息提供的機會將消息轉(zhuǎn)發(fā)給其他對象。
要轉(zhuǎn)發(fā)消息,所有forwardInvocation:方法需要做的是:

  • 確定消息的去向,以及
  • 用它的原始參數(shù)發(fā)送它。

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

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

轉(zhuǎn)發(fā)的消息返回值將返回給原始發(fā)送方。 可以將所有類型的返回值傳遞給發(fā)送方,包括id,結(jié)構(gòu)和雙精度浮點數(shù)。

forwardInvocation:方法可以充當(dāng)無法識別的消息的分發(fā)中心,將它們分配給不同的接收者。 或者它可以是轉(zhuǎn)移站,將所有消息發(fā)送到同一目的地。 它可以將一條消息翻譯成另一條消息,或者只是“吞下”一些消息,這樣就沒有響應(yīng)也沒有錯誤。 forwardInvocation:方法還可以將多個消息合并到一個響應(yīng)中。 什么forwardInvocation:取決于實現(xiàn)者。 但是,它為轉(zhuǎn)發(fā)鏈中的對象鏈接提供了機會,這為程序設(shè)計開辟了可能性。
(注意:forwardInvocation:方法只有在它們不調(diào)用標(biāo)稱接收器中的現(xiàn)有方法時才會處理消息。 例如,如果您希望對象將消息轉(zhuǎn)發(fā)到另一個對象,則它不能擁有自己的協(xié)商方法。 如果是,則消息永遠不會到達forwardInvocation:)

有關(guān)轉(zhuǎn)發(fā)和調(diào)用的更多信息,請參閱Foundation框架參考中的NSInvocation類規(guī)范。

2、轉(zhuǎn)發(fā)和多重繼承

轉(zhuǎn)發(fā)模仿繼承,并可用于向Objective-C程序提供多重繼承的一些效果。 如圖5-1所示,通過轉(zhuǎn)發(fā)消息來響應(yīng)消息的對象似乎借用或“繼承”另一個類中定義的方法實現(xiàn)。


圖5-1轉(zhuǎn)發(fā)

在此圖示中,Warrior類的實例將協(xié)商消息轉(zhuǎn)發(fā)給Diplomat類的實例。戰(zhàn)士似乎會像外交官一樣進行談判。它似乎會回應(yīng)談判的信息,并且出于所有實際目的,它確實做出了回應(yīng)(雖然它確實是一個外交官正在做的工作)。

轉(zhuǎn)發(fā)消息的對象因此從繼承層次結(jié)構(gòu)的兩個分支“繼承”方法 - 它自己的分支和響應(yīng)消息的對象的分支。在上面的例子中,似乎Warrior類繼承自Diplomat以及它自己的超類。

轉(zhuǎn)發(fā)提供了您通常希望從多個繼承中獲得的大多數(shù)功能。但是,兩者之間存在重要差異:多重繼承在單個對象中組合了不同的功能。它傾向于大型,多方面的對象。另一方面,轉(zhuǎn)發(fā)將不同的對象分配給不同的對象。它將問題分解為較小的對象,但以對郵件發(fā)件人透明的方式關(guān)聯(lián)這些對象。

3、代理對象

轉(zhuǎn)發(fā)不僅模仿多重繼承,還可以開發(fā)代表或“覆蓋”更多實質(zhì)對象的輕量級對象。代理人代表另一個對象并向其傳達消息。

在Objective-C編程語言中的“遠程消息傳遞”中討論的代理就是這樣的代理。代理負(fù)責(zé)將消息轉(zhuǎn)發(fā)到遠程接收器的管理細(xì)節(jié),確保通過連接復(fù)制和檢索參數(shù)值,等等。但它并沒有嘗試做太多其他事情;它不會復(fù)制遠程對象的功能,而只是給遠程對象一個本地地址,一個可以在另一個應(yīng)用程序中接收消息的地方。

其他種類的替代物也是可能的。例如,假設(shè)您有一個操縱大量數(shù)據(jù)的對象 - 也許它會創(chuàng)建一個復(fù)雜的圖像或讀取磁盤上文件的內(nèi)容。設(shè)置此對象可能非常耗時,因此您更喜歡懶惰地執(zhí)行此操作 - 在確實需要時或系統(tǒng)資源暫時空閑時。同時,您至少需要此對象的占位符才能使應(yīng)用程序中的其他對象正常運行。

在這種情況下,你最初可以創(chuàng)建,而不是完整的對象,但它是一個輕量級的代理。這個對象可以自己做一些事情,比如回答有關(guān)數(shù)據(jù)的問題,但大多數(shù)情況下它只是為較大的對象保留一個位置,并且當(dāng)時間到來時,將消息轉(zhuǎn)發(fā)給它。當(dāng)代理的forwardInvocation:方法首先接收發(fā)往另一個對象的消息時,它將確保該對象存在并且如果不存在則創(chuàng)建它。較大對象的所有消息都通過代理,因此,就程序的其余部分而言,代理和較大的對象將是相同的。

4、轉(zhuǎn)發(fā)和繼承

雖然轉(zhuǎn)發(fā)模仿繼承,但NSObject類從不混淆這兩者。 像respondsToSelector:和isKindOfClass這樣的方法:只查看繼承層次結(jié)構(gòu),而不是轉(zhuǎn)發(fā)鏈。 例如,如果詢問Warrior對象是否響應(yīng)協(xié)商消息,

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

答案是否定的,即使它可以毫無錯誤地接收談判消息并在某種意義上通過將它們轉(zhuǎn)發(fā)給外交官來回應(yīng)它們。 (見圖5-1)

在許多情況下,NO是正確的答案。 但它可能不是。 如果使用轉(zhuǎn)發(fā)來設(shè)置代理對象或擴展類的功能,則轉(zhuǎn)發(fā)機制可能應(yīng)該像繼承一樣透明。 如果您希望對象的行為就像它們真正繼承了它們轉(zhuǎn)發(fā)消息的對象的行為一樣,那么您需要重新實現(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é)議,則同樣應(yīng)將conformsToProtocol:方法添加到列表中。 類似地,如果一個對象轉(zhuǎn)發(fā)它收到的任何遠程消息,它應(yīng)該有一個methodSignatureForSelector版本:它可以返回最終響應(yīng)轉(zhuǎn)發(fā)消息的方法的準(zhǔn)確描述; 例如,如果一個對象能夠?qū)⑾⑥D(zhuǎn)發(fā)給它的代理,那么您將實現(xiàn)methodSignatureForSelector:如下所示:

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

您可以考慮將轉(zhuǎn)發(fā)算法放在私有代碼中的某個位置,并使用所有這些方法(forwardInvocation:included)來調(diào)用它。
(注意:這是一項高級技術(shù),僅適用于無法提供其他解決方案的情況。 它不是作為繼承的替代品。 如果必須使用此技術(shù),請確保完全了解執(zhí)行轉(zhuǎn)發(fā)的類的行為以及要轉(zhuǎn)發(fā)的類)

本節(jié)中提到的方法在Foundation框架參考中的NSObject類規(guī)范中進行了描述。 有關(guān)invokeWithTarget:的信息,請參閱Foundation框架參考中的NSInvocation類規(guī)范。

六、類型編碼

為了協(xié)助運行時系統(tǒng),編譯器對字符串中每個方法的返回和參數(shù)類型進行編碼,并將字符串與方法選擇器相關(guān)聯(lián)。 它使用的編碼方案在其他上下文中也很有用,因此可以使用@encode()編譯器指令公開使用。 當(dāng)給定類型規(guī)范時,@ encode()返回編碼該類型的字符串。 類型可以是基本類型,例如int,指針,標(biāo)記結(jié)構(gòu)或聯(lián)合,或類名 - 實際上,任何類型都可以用作C sizeof()運算符的參數(shù)。

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

下表列出了類型代碼。 請注意,它們中的許多重疊與編碼對象時用于存檔或分發(fā)的代碼重疊。 但是,此處列出的代碼在編寫編碼器時無法使用,并且在編寫不是由@encode()生成的編碼器時可能需要使用代碼。 (有關(guān)編碼對象以進行存檔或分發(fā)的詳細(xì)信息,請參閱Foundation Framework參考中的NSCoder類規(guī)范)


表6-1 Objective-C類型編碼

重要說明:Objective-C不支持long double類型。 @encode(long double)返回d,這與double的編碼相同。

數(shù)組的類型代碼用方括號括起來; 數(shù)組中元素的數(shù)量是在數(shù)組類型之前的開括號之后立即指定的。 例如,一個包含12個浮點指針的數(shù)組將被編碼為:

[12^f]

結(jié)構(gòu)在括號內(nèi)指定,括號內(nèi)的聯(lián)合。 首先列出結(jié)構(gòu)標(biāo)記,然后是等號,并按順序列出結(jié)構(gòu)字段的代碼。 例如,結(jié)構(gòu)

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

編碼如下:

{example=@*i}

相同的編碼會導(dǎo)致定義的類型名稱(示例)或結(jié)構(gòu)標(biāo)記(示例)是否傳遞給@encode()。 結(jié)構(gòu)指針的編碼帶有關(guān)于結(jié)構(gòu)字段的相同數(shù)量的信息:

^{example=@*i}

但是,另一個間接級別會刪除內(nèi)部類型規(guī)范:

^^{example}

對象被視為結(jié)構(gòu)。 例如,將NSObject類名傳遞給@encode()會產(chǎn)生以下編碼:

{NSObject=#}

NSObject類只聲明一個類型為Class的實例變量isa。

請注意,盡管@encode()指令不返回它們,但運行時系統(tǒng)使用表6-2中列出的其他編碼來表示類型限定符,當(dāng)它們用于在協(xié)議中聲明方法時。


表6-2 Objective-C方法編碼

七、屬性聲明

當(dāng)編譯器遇到屬性聲明時(請參閱Objective-C編程語言中的聲明屬性),它會生成與封閉類,類別或協(xié)議關(guān)聯(lián)的描述性元數(shù)據(jù)。 您可以使用支持在類或協(xié)議上按名稱查找屬性,將屬性類型作為@encode字符串獲取,以及將屬性列表作為C字符串?dāng)?shù)組復(fù)制的函數(shù)來訪問此元數(shù)據(jù)。 每個類和協(xié)議都有一個聲明的屬性列表。

1、屬性類型和功能

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

typedef struct objc_property *Property;

您可以使用函數(shù)class_copyPropertyList和protocol_copyPropertyList分別檢索與類關(guān)聯(lián)的屬性數(shù)組(包括已加載的類別)和協(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ù)來發(fā)現(xiàn)屬性的名稱:

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ù)來發(fā)現(xiàn)屬性的名稱和@encode類型字符串。 有關(guān)編碼類型字符串的詳細(xì)信息,請參閱類型編碼; 有關(guān)此字符串的詳細(xì)信息,請參閱屬性類型字符串和屬性屬性描述示例。

const char *property_getAttributes(objc_property_t property)

將這些放在一起,您可以使用以下代碼打印與類關(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));
}
2、屬性類型字符串

您可以使用property_getAttributes函數(shù)來發(fā)現(xiàn)名稱,屬性的@encode類型字符串以及屬性的其他屬性。

該字符串以T開頭,后跟@encode類型和逗號,并以V結(jié)尾,后跟后備實例變量的名稱。 在這些屬性之間,屬性由以下描述符指定,以逗號分隔:

表7-1聲明的屬性類型編碼

有關(guān)示例,請參見下面屬性屬性描述示例。

3、屬性屬性描述示例

鑒于這些定義:

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

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



最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容