1 概述
Objective-C語言將決定儘可能的從編譯和連結時推遲到執行時。只要有可能,Objective-C總是使用動態的方式來解決問題。這意味著Objective-C語言不僅需要一個編譯器,同時也需要一個執行時系統來執行編譯好的程式碼。這裡的執行時系統扮演的角色類似於 Objective-C語言的作業系統,Objective-C基於該系統來工作。
本文章將具體介紹NSObject類以及Objective-C程式是如何與執行時系統互動的。特別地,本文章還給出來怎樣在執行時動態地載入新類和將訊息轉發給其它物件的範例,同時也給出了怎樣在程式執行時獲取物件資訊的方法。
通常,如果僅僅寫一個Cocoa 程式,程式設計師不需要知道和理解Objective-C執行時系統的底層細節,但這篇文章仍然值得推薦閱讀,以瞭解 Objective-C執行時系統的原理,並能更好的利用 Objective-C的優點。
2 參考
《Objective-C 2.0 執行時系統參考庫》描述了Objective-C執行庫的資料結構和函式介面。程式可以通過這些介面來和Objective-C執行時系統互動。例如,您可以增加一個類或者方法,或者獲得所有類的定義列表等。
《Objective-C 2.0 程式設計語言》介紹了Objective-C語言本身。
《Objective-C 版本說明》給出了在最近版本的Mac OS X系統中關於Objective-C執行時系統的一些改動。
3 執行時系統的版本和平臺
在不同的平臺上Objective-C執行時系統的版本也不相同。
3.1早期版本和現行版本
Objective-C執行時系統有兩個已知版本:早期版本和現行版本。
現行版本主要是Objective-C 2.0 及與其相關的新特性。早期版本的程式設計介面見《Objective-C 1執行時系統參考庫》;現行版本的程式設計介面見《Objective-C 2.0 執行時系統參考庫》。
在現行版本中,最顯著的新特性就是例項變數是“健壯(non-fragile )的”:
1)在早期版本中,如果您改變類中例項變數的佈局,您必須重新編譯該類的所有子類。
2)在現行版本中,如果您改變類中例項變數的佈局,您無需重新編譯該類的任何子類。
此外,現行版本支援宣告property 的synthesis屬性(參考《Objective-C 2.0 程式設計語言》的“屬性”一節)。
3.2平臺
iPhone 程式和Mac OS X 10.5及以後的系統中的64位程式使用的都是Objective-C執行時系統的現行版本。
其它情況(Mac OS X系統中的32位程式)使用的是早期版本。
4 和執行時系統的互動
Objective-C程式有三種途徑和執行時系統互動:
1)通過 Objective-C原始碼;
2)通過 Foundation框架中類NSObject的方法;
3)通過直接呼叫執行時系統的函式。
4.1通過Objective-C原始碼
大部分情況下,執行時系統在後臺自動執行,您只需編寫和編譯Objective-C原始碼。
當您編譯Objective-C類和方法時,編譯器為實現語言動態特性將自動建立一些資料結構和函式。這些資料結構包含類定義和協議類定義中的資訊,如在《Objective-C 2.0 程式設計語言》中“定義類”和“協議類”一節所討論的類的物件和協議類的物件,方法選標,例項變數模板,以及其它來自於原始碼的資訊。執行時系統的主要功能就是根據原始碼中的表示式傳送訊息,如“訊息”一節所述。
4.2通過類NSObject的方法
Cocoa 程式中絕大部分類都是NSObject類的子類,所以大部分都繼承了NSObject類的方法,因而繼承了NSObject的行為。(NSProxy類是個例外;更多細節參考“訊息轉發”一節。)然而,某些情況下,NSObject類僅僅定義了完成某件事情的模板,而沒有提供所有需要的程式碼。
例如,NSObject類定義了description 方法,返回該類內容的字串表示。這主要是用來除錯程式——GDB中的print-object方法就是直接列印出該方法返回的字串。NSObject類中該方法的實現並不知道子類中的內容,所以它只是返回類的名字和物件的地址。NSObject的子類可以重新實現該方法以提供更多的資訊。例如,NSArray 類改寫了該方法來返回NSArray 類包含的每個物件的內容。
某些NSObject的方法只是簡單地從執行時系統中獲得資訊,從而允許物件進行一定程度的自我檢查。例如,
1 2 3 4 5 |
class 返回物件的類; isKindOfClass:和isMemberOfClass:檢查物件是否在指定的類繼承體系中; respondsToSelector:檢查物件能否響應指定的訊息; conformsToProtocol:檢查物件是否實現了指定協議類的方法; methodForSelector:返回指定方法實現的地址。 |
4.3通過執行時系統的函式
執行時系統是一個有公開介面的動態庫,由一些資料結構和函式的集合組成,這些資料結構和函式的宣告標頭檔案在/usr/include/objc 中。這些函式支援用純 C 的函式來實現Objective-C同樣的功能。還有一些函式構成了NSObject類方法的基礎。這些函式使得訪問執行時系統介面和提供開發工具成為可能。儘管大部分情況下它們在 Objective-C程式不是必須的,但是有時候對於 Objecitve-C程式來說某些函式是非常有用的。
這些函式的文件參見《Objective-C 2.0 執行時系統參考庫》。
5 訊息
本章節描述了程式碼的訊息表示式如何轉換為對objc_msgSend函式的呼叫,如何通過名字來指定一個方法,以及如何使用objc_msgSend函式。
5.1獲得方法地址
避免動態繫結的唯一辦法就是取得方法的地址,並且直接像函式呼叫一樣呼叫它。當一個方法會被連續呼叫很多次,而且您希望節省每次呼叫方法都要傳送訊息的開銷時,使用方法地址來呼叫方法就顯得很有效。
利用NSObject類中的methodForSelector:方法,您可以獲得一個指向方法實現的指標,並可以使用該指標直接呼叫方法實現。methodForSelector:返回的指標和賦值的變數型別必須完全一致,包括方法的引數型別和返回值型別都在型別識別的考慮範圍中。
下面的例子展示了怎麼使用指標來呼叫setFilled:的方法實現:
1 2 3 4 5 |
void (*setter)(id, SEL, BOOL); int i; setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)]; for ( i = 0; i |
方法指標的第一個引數是接收訊息的物件(self),第二個引數是方法選標(_cmd)。這兩個引數在方法中是隱藏引數,但使用函式的形式來呼叫方法時必須顯示的給出。
使用methodForSelector:來避免動態繫結將減少大部分訊息的開銷,但是這隻有在指定的訊息被重複傳送很多次時才有意義,例如上面的for 迴圈。
注意:methodForSelector:是Cocoa 執行時系統的提供的功能,而不是Objective-C語言本身的功能。
5.2 objc_msgSend函式
在Objective-C中,訊息是直到執行的時候才和方法實現繫結的。編譯器會把一個訊息表示式,
[receiver message]
轉換成一個對訊息函式objc_msgSend的呼叫。該函式有兩個主要引數:訊息接收者和訊息對應的方法名字——也就是方法選標:
objc_msgSend(receiver, selector)
同時接收訊息中的任意數目的引數:
objc_msgSend(receiver, selector, arg1, arg2, ...)
該訊息函式做了動態繫結所需要的一切:
1)它首先找到選標所對應的方法實現。因為不同的類對同一方法可能會有不同的實現,所以找到的方法實現依賴於訊息接收者的型別。
2)然後將訊息接收者物件(指向訊息接收者物件的指標)以及方法中指定的引數傳給找到的方法實現。
3)最後,將方法實現的返回值作為該函式的返回值返回。
注意:編譯器將自動插入呼叫該訊息函式的程式碼。您無須在程式碼中顯示呼叫該訊息函式。
訊息機制的關鍵在於編譯器為類和物件生成的結構。每個類的結構中至少包括兩個基本元素:
1)指向父類的指標。
2)類的方法表。方法表將方法選標和該類的方法實現的地址關聯起來。例如,setOrigin::的方法選標和setOrigin::的方法實現的地址關聯,display 的方法選標和display 的方法實現的地址關聯,等等。
當新的物件被建立時,其記憶體同時被分配,例項變數也同時被初始化。物件的第一個例項變數是一個指向該物件的類結構的指標,叫做isa。通過該指標,物件可以訪問它對應的類以及相應的父類。
注意:儘管嚴格來說這並不是 Obective-C 語言的一部分,但是在Objective-C執行時系統中物件需要有isa 指標。物件和結構體struct objc_object(在objc/objc.h 中定義)必須“一致”。然而,您很少需要建立您自己的根物件,因為從 NSObject或者NSProxy 繼承的物件都自動包括isa 變數。
類和物件的結構如下圖所示。
當物件收到訊息時,訊息函式首先根據該物件的 isa 指標找到該物件所對應的類的方法表,並從表中尋找該訊息對應的方法選標。如果找不到,objc_msgSend將繼續從父類中尋找,直到 NSObject類。一旦找到了方法選標, objc_msgSend則以訊息接收者物件為引數呼叫,呼叫該選標對應的方法實現。
這就是在執行時系統中選擇方法實現的方式。在物件導向程式設計中,一般稱作方法和訊息動態繫結的過程。
為了加快訊息的處理過程,執行時系統通常會將使用過的方法選標和方法實現的地址放入快取中。每個類都有一個獨立的快取,同時包括繼承的方法和在該類中定義的方法。訊息函式會首先檢查訊息接收者物件對應的類的快取(理論上,如果一個方法被使用過一次,那麼它很可能被再次使用)。如果在快取中已經有需要的方法選標,則訊息僅僅比函式呼叫慢一點點。如果程式執行了足夠長的時間,幾乎每個訊息都能在快取中找到方法實現。程式執行時,快取也將隨著新的訊息的增加而增加。
5.3使用隱藏的引數
當objc_msgSend找到方法對應的實現時,它將直接呼叫該方法實現,並將訊息中所有的引數都傳遞給方法實現,同時,它還將傳遞兩個隱藏的引數:
1)接收訊息的物件
2)方法選標
這些引數幫助方法實現獲得了訊息表示式的資訊。它們被認為是“隱藏”的原因是它們並沒有在定義方法的原始碼中宣告,而是在程式碼編譯時是插入方法的實現中的。
儘管這些引數沒有被顯示宣告,但在原始碼中仍然可以引用它們(就像可以引用訊息接收者物件的例項變數一樣)。在方法中可以通過 self來引用訊息接收者物件,通過選標_cmd來引用方法本身。在下面的例子中,_cmd指的是strange 方法,self指的收到strange 訊息的物件。
1 2 3 4 5 6 7 8 9 10 |
- strange { id target = getTheReceiver(); SEL method = getTheMethod(); if ( target == self || method == _cmd ) return nil; return [target performSelector:method]; } |
在這兩個引數中,self更有用一些。實際上,它是在方法實現中訪問訊息接收者物件的例項變數的途徑。
6 動態方法解析
本章節將描述怎樣動態地提供一個方法的實現。
6.1動態方法解析
有時候,程式設計師需要動態地提供一個方法的實現。例如,Objective-C中屬性(Property )( 參考《Objective-C 2.0 程式設計語言》中“屬性”小節)前的修飾符@dynamic
@dynamic propertyName;
表示編譯器須動態地生成該屬性對應地方法。
程式設計師可以通過實現resolveInstanceMethod:和resolveClassMethod:
來動態地實現給定選標的物件方法或者類方法。
Objective-C方法可以認為是至少有兩個引數self和_cmd的C 函式。您可以通過class_addMethod 方法將一個函式加入到類的方法中。例如,有如下的函式:
1 2 3 |
void dynamicMethodIMP(id self, SEL _cmd) { // implementation .... } |
程式設計師可以通過resolveInstanceMethod:將它作為類方法resolveThisMethodDynamically的實現:
1 2 3 4 5 6 7 8 9 10 |
@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 |
通常訊息轉發(見 “訊息轉發”)和動態方法解析是互不相干的。在進入訊息轉發機制之前,respondsToSelector:和instancesRespondToSelector: 會被首先呼叫。您可以在這兩個方法中為傳進來的選標提供一個IMP 。如果您實現了resolveInstanceMethod:方法,但是仍然希望正常的訊息轉發機制進行,您只需要返回NO即可。
6.2動態載入
Objective-C程式可以在執行時連結和載入新的類和範疇類。新載入的類和在程式啟動時載入的類並沒有區別。
動態載入可以用在很多地方。例如,系統配置中的模組就是被動態載入的。
在Cocoa 環境中,動態載入一般被用來對應用程式進行定製。您的程式可以在執行時載入其他程式設計師編寫的模組——和Interface Build 載入定製的調色盤以及系統配置程式載入定製的模組的類似。這些模組通過您許可的方式擴充套件您的程式,而您無需自己來定義或者實現。您提供了框架,而其它的程式設計師提供了實現。
儘管已經有一個執行時系統的函式來動態載入Mach-O檔案中的Objective-C模組(objc_loadModules,在objc/objc-load.h中定義),Cocoa 的NSBundle類為動態載入提供了一個更方便的介面——一個物件導向的,已經和相關服務整合的介面。關於NSBundle類的更多相關資訊請參考Foundation 框架中關於NSBundle類的文件。關於Mach-O檔案的有關資訊請參考《Mac OS X ABI Mach-O 檔案格式參考庫》。
7 訊息轉發
通常,給一個物件傳送它不能處理的訊息會得到出錯提示,然而,Objective-C執行時系統在丟擲錯誤之前,會給訊息接收物件傳送一條特別的訊息來通知該物件。
7.1訊息轉發
如果一個物件收到一條無法處理的訊息,執行時系統會在丟擲錯誤前,給該物件傳送一條forwardInvocation:訊息,該訊息的唯一引數是個NSInvocation型別的物件——該物件封裝了原始的訊息和訊息的引數。
程式設計師可以實現forwardInvocation:方法來對不能處理的訊息做一些預設的處理,也可以以其它的某種方式來避免錯誤被丟擲。如forwardInvocation:的名字所示,它通常用來將訊息轉發給其它的物件。
關於訊息轉發的作用,您可以考慮如下情景:假設,需要設計一個能夠響應negotiate 訊息的物件,並且能夠包括其它型別的物件對訊息的響應。通過在negotiate 方法的實現中將negotiate 訊息轉發給其它的物件這種方式可以很容易的達到這一目的。
更進一步,假設您希望您的物件和另外一個類的物件對 negotiate 的訊息的響應完全一致。一種可能的方式就是讓您的類繼承其它類的方法實現。然而,有時候這種方式不可行,因為您的類和其它類可能需要在不同的繼承體系中響應negotiate 訊息。
雖然您的類無法繼承其它類的negotiate 方法,您仍然可以提供一個方法實現,這個方法實現只是簡單的將negotiate 訊息轉發給其他類的物件,就好像從其它類那裡“借”來的實現一樣。如下所示:
1 2 3 4 5 6 |
- negotiate { if ( [someOtherObject respondsTo:@selector(negotiate)] ) return [someOtherObject negotiate]; return self; } |
這種方式顯得有欠靈活,特別是有很多訊息您都希望傳遞給其它物件時,您必須為每一種訊息提供方法實現。此外,這種方式不能處理未知的訊息。當您寫下程式碼時,所有您需要轉發的訊息的集合也必須確定。然而,實際上,這個集合會隨著執行時事件的發生,新方法或者新類的定義而變化。
forwardInvocation:訊息給這個問題提供了一個更特別的,動態的解決方案:當一個物件由於沒有相應的方法實現而無法響應某訊息時,執行時系統將通過 forwardInvocation:訊息通知該物件。每個物件都從NSObject類中繼承了forwardInvocation:方法。然而,NSObject中的方法實現只是簡單地呼叫了doesNotRecognizeSelector:。通過實現您自己的forwardInvocation:方法,您可以在該方法實現中將訊息轉發給其它物件。
要轉發訊息給其它物件,forwardInvocation:方法所必須做的有:
1)決定將訊息轉發給誰,並且
2)將訊息和原來的引數一塊轉發出去
訊息可以通過invokeWithTarget:方法來轉發:
1 2 3 4 5 6 |
- (void)forwardInvocation:(NSInvocation *)anInvocation { if ([someOtherObject respondsToSelector: [anInvocation selector]]) [anInvocation invokeWithTarget:someOtherObject]; else [super forwardInvocation:anInvocation]; } |
轉發訊息後的返回值將返回給原來的訊息傳送者。返回值可以是任何型別的,包括:id,結構體,浮點數等。
forwardInvocation:方法就像一個不能識別的訊息的分發中心,將這些訊息轉發給不同接收物件。或者它也可以像一個運輸站將所有的訊息都傳送給同一個接收物件。它可以將一個訊息翻譯成另外一個訊息,或者簡單的“吃掉”某些訊息,因此沒有響應也沒有錯誤。forwardInvocation:方法也可以對不同的訊息提供同樣的響應,這一切都取決於方法的具體實現。該方法所提供的是將不同的物件連結到訊息鏈的能力。
注意:forwardInvocation: 方法只有在訊息接收物件中無法正常響應訊息時才會被呼叫。所以,如果您希望您的物件將negotiate 訊息轉發給其它物件,您的物件不能有negotiate 方法。否則,forwardInvocation:將不可能會被呼叫。
更多訊息轉發的資訊,參考Foundation框架參考庫中NSInvocation類的文件。
7.2訊息轉發和多重繼承
訊息轉發很像繼承,並且可以用來在Objective-C程式中模擬多重繼承。如下圖所示, 一個物件通過轉發來響應訊息,看起來就像該物件從別的類那借來了或者“繼承”了方法實現一樣。
在上圖中,Warrior類的一個物件例項將negotiate 訊息轉發給Diplomat 類的一個例項。看起來,Warrior類似乎和Diplomat 類一樣,響應negotiate訊息,並且行為和Diplomat 一樣(儘管實際上是Diplomat類響應了該訊息)。
轉發訊息的物件看起來有兩個繼承體系分支——自己的和響應訊息的物件的。在上面的例子中,Warrior看起來同時繼承自Diplomat 和自己的父類。
訊息轉發提供了多重繼承的很多特性。然而,兩者有很大的不同:多重繼承是將不同的行為封裝到單個的物件中,有可能導致龐大的,複雜的物件。而訊息轉發是將問題分解到更小的物件中,但是又以一種對訊息傳送物件來說完全透明的方式將這些物件聯絡起來。
7.3訊息代理物件
訊息轉發不僅和繼承很像,它也使得以一個輕量級的物件(訊息代理物件)代表更多的物件進行訊息處理成為可能。
《Objective-C 2.0 程式設計語言》中“遠端訊息”一節中的代理類就是這樣一個代理物件。代理類負責將訊息轉發給遠端訊息接收物件的管理細節,保證訊息引數的傳輸等等。但是訊息類沒有進一步的複製遠端物件的功能,它只是將遠端物件對映到一個本地地址上,從而能夠接收其它應用程式的訊息。
同時也存在著其它型別的訊息代理物件。例如,假設您有個物件需要操作大量的資料——它可能需要建立一個複雜的圖片或者需要從磁碟上讀一個檔案的內容。建立一個這樣的物件是很費時的,您可能希望能推遲它的建立時間——直到它真正需要時,或者系統資源空閒時。同時,您又希望至少有一個預留的物件和程式中其它物件互動。
在這種情況下,您可以為該物件建立一個輕量的代理物件。該代理物件可以有一些自己的功能,例如響應資料查詢訊息,但是它主要的功能是代表某個物件,當時間到來時,將訊息轉發給被代表的物件。當代理物件的forwardInvocation:方法收到需要轉發給被代表的物件的訊息時,代理物件會保證所代表的物件已經存在,否則就建立它。所有發到被代表的物件的訊息都要經過代理物件,對程式來說,代理物件和被代表的物件是一樣的。
7.4訊息轉發和類繼承
儘管訊息轉發很“像”繼承,但它不是繼承。例如在NSObject類中,方法respondsToSelector:和isKindOfClass:只會出現在繼承鏈中,而不是訊息轉發鏈中。例如,如果向一個 Warrior類的物件詢問它能否響應negotiate 訊息,
1 2 |
if ( [aWarrior respondsToSelector:@selector(negotiate)] ) ... |
返回值是NO,儘管該物件能夠接收和響應negotiate。
大部分情況下,NO是正確的響應。但不是所有時候都是的。例如,如果您使用訊息轉發來建立一個代理物件以擴充套件某個類的能力,這裡的訊息轉發必須和繼承一樣,儘可能的對使用者透明。如果您希望您的代理物件看起來就像是繼承自它代表的物件一樣,您需要重新實現respondsToSelector:和isKindOfClass:方法:
1 2 3 4 5 6 7 8 9 10 |
- (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:方法也必須重新實現。如果您使用的是協議類,需要重新實現的還有conformsToProtocol:方法。類似地,如果物件需要轉發遠端訊息,則 methodSignatureForSelector:方法必須能夠返回實際響應訊息的方法的描述。例如,如果物件需要將訊息轉發給它所代表的物件,您可能需要如下的methodSignatureForSelector:實現:
1 2 3 4 5 6 7 |
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { NSMethodSignature* signature = [super methodSignatureForSelector:selector]; if (!signature) { signature = [surrogate methodSignatureForSelector:selector]; } return signature; } |
您也可以將訊息轉發的部分放在一段私有的程式碼裡,然後從forwardInvocation:呼叫它。
注意:訊息轉發是一個比較高階的技術,僅適用於沒有其它更好的解決辦法的情況。它並不是用來代替繼承的。如果您必須使用該技術,請確定您已經完全理解了轉發訊息的類和接收轉發訊息的類的行為。
本節中涉及的方法在Foundation框架參考庫中的NSObject類的文件中都有描述。關於invokeWithTarget:的具體資訊,請參考Foundation框架參考庫中NSInvocation類的文件。
8 型別編碼
為了和執行時系統協作,編譯器將方法的返回型別和引數型別都編碼成一個字串,並且和方法選標關聯在一起。這些編碼在別的上下文環境中同樣有用,所以您可以直接使@encode()編譯指令來得到具體的編碼。給定一個型別, @encode()將返回該型別的編碼字串。型別可以是基本型別例如整形,指標,結構體或者聯合體,也可以是一個類,就和 C 語言中的sizeof()操作符的引數一樣,可以是任何型別。
1 2 3 |
char *buf1 = @encode(int **); char *buf2 = @encode(struct key); char *buf3 = @encode(Rectangle); |
下表列出了這些型別編碼。注意,它們可能很多和您使用的物件編碼有一些重合。然而,這裡列出來的有些編碼是您寫編碼器的時候不會使用的,也有一些不是@encode()產生的,但是在您寫編碼器的時候是會使用的。(關於物件編碼的更多資訊,請參考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 *) |
@ | 物件(無論是靜態指定的還是通過id引用的) |
# | 類(Class) |
: | 方法選標(SEL) |
[array type] | 陣列 |
{name=type…} | 結構體 |
(name=type…) | 聯合體 |
bnum | num個bit的位域 |
^type | type型別的指標 |
? | 未知型別(其他時候,一般用來指函式指標) |
重要: Objective-C 不支援long double 型別。@encode(long double)和double 一樣,返回的字串都是d。
陣列的型別編碼以方括號來表示,緊接著左方括號的是陣列元素的數量,然後是資料元素的型別。例如,一個12個浮點數(floats)指標的陣列可以表示如下:
[12^f]
結構體和聯合體分別用大括號和小括號表示。括號中首先是結構體標籤,然後是一個“=”符號,接著是結構體中各個成員的編碼。例如,結構體
1 2 3 4 5 |
typedef struct example { id anObject; char *aString; int anInt; } Example; |
的編碼如下:
{example=@*i}
定義的型別名(Example)和結構體標籤(example)有同樣的編碼結果。指向結構體型別的指標的編碼同樣也包含了結構體內部資料成員的編碼資訊,如下所示:
^{example=@*i}
然而,更高層次的間接關聯就沒有了內部資料成員的編碼資訊:
^^{example}
物件的編碼類似結構體。例如, @encode()對NSObject編碼如下:
{NSObject=#}
NSObject類僅宣告瞭一個Class 型別的例項變數,isa。
注意:儘管有一些編碼無法從 @encode()的結果中直接得到,但是執行時系統會使用它們來表示協議類中方法的修飾符,這些編碼如表所示。
Objective-C方法編碼
編碼 | 含義 |
---|---|
r | const |
n | in |
N | inout |
o | out |
O | bycopy |
R | byref |
V | oneway |
9 屬性宣告
當編譯器遇到一個屬性(Property )宣告時(參考《Objective-C 2.0 程式設計語言》中的“屬性”小節),編譯器將產生一些描述性的後設資料與屬性所在的類或者協議類關聯。您可以通過函式訪問後設資料,這些函式支援在類或者協議類中通過名字來查詢,通過@encode獲得屬性的型別編碼,將屬性的特徵(Attribute )作為C字串的陣列返回等。每個類或者協議類都維護了一個宣告瞭的屬性列表。
9.1屬性型別和相關函式
屬性(Property )型別定義了對描述屬性的結構體objc_property 的不透明的控制程式碼。
1 |
typedef struct objc_property *Property; |
您可以使用函式class_copyPropertyList和protocol_copyPropertyList 來獲得類(包括範疇類)或者協議類中的屬性列表:
1 2 |
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount) |
例如,有如下的類宣告:
1 |
@interface Lender : NSObject { float alone;}@property float alone;@end |
可以像這樣獲得它的屬性:
1 2 |
id LenderClass = objc_getClass("Lender");unsigned int outCount; objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount); |
還可以通過property_getName函式獲得屬性的名字:
1 |
const char *property_getName(objc_property_t property) |
函式class_getProperty 和protocol_getProperty則在類或者協議類中返回具有給定名字的屬性的引用:
1 2 |
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函式可以獲得屬性的名字和@encode編碼。關於型別編碼的更多細節,參考“型別編碼”一節;關於屬性的型別編碼,見“屬性型別編碼”及“屬性特徵的描述範例”。
1 |
const char *property_getAttributes(objc_property_t property) |
綜合起來,您可以通過下面的程式碼得到一個類中所有的屬性。
1 2 3 4 |
id LenderClass = objc_getClass("Lender"); unsigned int outCount, i; objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount); for (i = 0; i |
9.2屬性型別編碼
property_getAttributes函式將返回屬性(Property)的名字,@encode 編碼,以及其它特徵(Attribute )。
1)property_getAttributes返回的字串以字母T 開始,接著是@encode 編碼和逗號。
2)如果屬性有readonly修飾,則字串中含有R 和逗號。
3)如果屬性有copy或者retain修飾,則字串分別含有C 或者&,然後是逗號。
4)如果屬性定義有定製的getter 和setter 方法,則字串中有G 或者S 跟著相應的方法名以及逗號(例如,GcustomGetter,ScustomSetter:,,)。
如果屬性是隻讀的,且有定製的get 訪問方法,則描述到此為止。
5)字串以V 然後是屬性的名字結束。
範例請參考 “屬性特徵的描述範例” 一節。
9.3屬性特徵的描述範例
給定如下定義:
1 2 3 4 |
enum FooManChu { FOO, MAN, CHU }; struct YorkshireTeaStruct { int pot; char lady; }; typedef struct YorkshireTeaStruct YorkshireTeaStructType; union MoneyUnion { float alone; double down; }; |
下表給出了屬性(Property )宣告以及property_getAttributes返回的相應的字串:
屬性宣告 | 屬性描述 |
---|---|
@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 附言
在此,執行時機制相關問題已經全部闡述。訊息傳送和轉發是Runtime的強大之處,通過它,您可以為程式增加很多動態的行為,雖然在實際開發中很少直接使用這些機制(如直接呼叫objc_msgSend),但瞭解它們有助於您更多地去了解底層的實現。其實在實際的編碼過程中,您也可以靈活地使用這些機制,去實現一些特殊的功能,如hook操作等。
原文:http://www.apple.com.cn/developer/Documentation/index.html