原文位址:http://blog.csdn.net/syd_programming/article/details/49340955
(表格不再翻譯,原文位址:Objective-C Runtime Programming Guide)
介紹
Objective-C将許多決策從便宜時期和連結時期延後到運作時期。隻要可能,它都動态的做很多事情。這意味着它不僅需要一個編譯器,還需要一個運作時系統來執行編譯好的代碼。對于Objective-C來說,這個運作時系統就好像一個作業系統,使objective-c能夠正常工作。
本文探究NSObject類,以及Objective-C程式如何和運作時系統互動。
通過閱讀本文,你應該了解Objective-C的運作時系統如何工作,以及如何利用它。盡管對于寫一個Cocoa程式而言,你可能并不需要了解本文。
本文的組織
本文包括以下幾章:
- 運作時系統的版本和平台
- 和運作時系統互動
- 發送消息
- 動态方法解析
- 消息轉發
- 類型編碼
- 聲明的屬性
參見
Objective-C運作時指南描述了Objective-C運作時庫的資料結構和函數。你的程式可以使用這些接口和Objective-C運作時系統進行互動。例如,你可以對一個已經加載的類添加類和方法,或者擷取類定義清單。
Runtime的版本和平台
在不同的平台上有很多不同的Objective-C的Runtime的版本。
過去和現代的版本
Objective-C Runtime有2個版本——現代版本和過去版本。現代版本是Objective-C 2.0引入的,包括一系列的新特性。過去版本的程式設計接口在《Objective-C 1 Runtime Reference》中進行了描述,現代版本的程式設計接口在《Objective-C Runtime Reference》。
最值得注意的新特性是在現代runtime中執行個體變量是“non-fragile”的:
- 在過去版本中,如果你改變一個類中執行個體變量的布局,你必須重新編譯這個類的子類;
- 在現代版本中,如果你改變一個類的執行個體變量的布局,你不需要重新編譯這個類的子類;
另外,現代runtime支援 declared properties 的執行個體變量合成(詳情請參閱《The Objective-C Programming Language》)。
平台
iphone程式和運作在OSX v10.5及以上版本的64位的程式使用現代版的runtime,其它程式(OSX桌面的32位程式)使用過去版本的runtime。
和Runtime進行互動
Objective-C程式和Runtime的互動可以分為3個層次:通過Objective-C源代碼;通過Foundation framework 的 NSObject類的方法;通過直接調用Runtime函數。
Objective-C 源代碼
通常情況下,Runtime系統在螢幕背後自動運作,你僅僅通過寫和編譯Objective-C代碼來使用它。當你編譯帶有Objective-C的類和方法的代碼時,編譯器會建立實作語言動态特性的資料結構和函數調用。這些資料結構會捕獲類、category、protocol聲明中的資訊,包括我們在《Defining a Class and Protocols in The Objective-C Programming Language》中讨論的類和protocol對象、selector、執行個體變量模闆,以及源代碼中的其他内容。主要的Runtime函數是那些能發送消息的函數,我們會在Messaging一節重點講解,它被源代碼的消息表達式調用。
NSObject方法
Cocoa中大部分對象都是NSObject的子類,是以大部分對象繼承了它的方法。(值得注意的一個例外是NSProxy類,詳情見《消息轉發》這一節。)是以它的方法的行為也能夠被每一個對象和每一個類執行個體所繼承。然而,在一些情況下,NSObject類僅僅定義了一個模闆,并沒有提供代碼。
例如說,NSObject類定義了一個description執行個體方法,這個方法傳回描述類内容的字元串。它主要用于調試——GDB的 print-object 會列印description方法傳回的字元串。NSObject并不知道這個類的内容,是以它隻傳回名字和對象的位址,NSObject的子類能夠傳回更多細節。例如, NSArray的description方法傳回它包括的對象的描述清單。
一些NSObject方法僅僅查詢Runtime的資訊。這些方法允許對象進行檢討。一個例子是class方法,它 向對象詢問它的類,還有isKindOfClass:方法和isMemberOfClass:方法,它們檢測對象在類層次中的位置,respondsToSelector:檢測對象是否響應某個消息,conformsToProtocol:檢測對象是否實作了特定協定的方法。這些方法給予了對象自省能力。
Runtime方法
Runtime系統是一個動态共享庫,它有公共接口,它的頭檔案位于 /usr/include/objc的檔案夾裡,有方法和資料結構的集合。當你寫Objective-C代碼時,這裡很多方法允許你使用C代碼重制編譯器做的事情。其它方法形成了功能基礎,這些功能通過NSObject類的方法導出。寫Objective-C程式時,一些Runtime方法是很有用的。《Objective-C Runtime Reference》對這些所有的方法進行了介紹。
消息
這一章将會講解消息傳遞怎樣會轉化為objc_msgSend方法調用,以及你怎樣通過名字來引用方法,然後講解你怎樣利用objc_msgSend方法,以及在你需要的時候怎樣避免動态綁定。
objc_msgSend方法
在Objective-C中,直到運作時消息才會被綁定到方法實作。編譯器會把一個消息表達式
<code class="hljs json has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">[receiver message]</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
轉化為一次對消息函數——objc_msgSend的調用。objc_msgSend函數将消息的接收者和消息中的方法名作為參數。任何在消息中傳遞的參數也同樣會被objc_msgSend處理:
<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">objc_msgSend(receiver, selector, arg1, arg2, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
消息函數為動态綁定做了一切:
- 它首先找到選擇器引用的過程(方法實作)。由于相同的方法也可以被不同的類實作,是以它最終找到的過程依賴于接收者的類。
- 然後它會調用這個過程,傳給它接收的對象(指向它的資料的指針),以及這個方法需要的其它參數。
- 最後,它将過程的傳回值作為自己的傳回值。
注意:編譯器會生成對消息函數的調用,你不應該在自己的代碼中直接調用它。
發送消息的關鍵在于編譯器為每個類和對象生成的結構,每個類結構包括2個基本元素:
- 指向superclass的指針。
- 類的分發表。這個表中的條目是方法的選擇器和特定類的方法位址的映射。例如setOrigin::的選擇器就和setOrigin::的實作的位址進行了關聯,display的選擇器和display的位址進行了關聯,等等。
當一個新對象建立以後,記憶體會被配置設定,它的執行個體變量會被初始化。這其中有一個指針指向它的類結構,這個指針就是 isa 指針,使得對象能夠通路它的類,并且通過這個類它能夠通路它的所有父類。
注意:嚴格來說,isa指針并不是語言本身的一部分,但它是對象和Objective-C Runtime系統協作所必需的。對象需要和objc_object結構各個字段都相等。然而,你很少需要建立你自己的root對象,繼承自NSObject和NSProxy的對象自動有一個isa變量。
當一個消息發送給一個對象時,消息函數随着isa指針在其類的消息分發表中查找方法選擇器。如果找不到選擇器,objc_msgSend就會跟随指向父類的指針并在其方法分發表中嘗試查找選擇器,如果一直失敗objc_msgSend會沿着繼承體系不斷向上,直到到達NSObject類。一旦找到選擇器,消息函數會調用該方法并将接收對象的資料結構傳遞給它。
這就是運作時選擇方法實作的方式,也就是面向對象程式設計術語中方法被動态綁定到消息的方式。
為了加速消息的處理,Runtime系統會緩存選擇器和使用過的方法位址。每個類都有一個獨立的緩存,它能存儲從父類繼承來的方法以及自己獨特的方法的選擇器。在搜尋分發表之前,消息路由會檢查接收消息的對象的類的緩存(理論上如果一個方法被使用過,那麼它可能會被再次使用)。如果方法選擇器在緩存中,那麼這個消息處理僅僅比方法調用慢一點點。一旦一個程式運作很長時間去逐漸建立這個緩存,那麼幾乎所有的消息都會找到一個緩存的方法。随着程式的運作,緩存會動态增長,以容納新的消息。
使用隐藏參數
當objc_msgSend發現實作一個方法的過程時,它會調用這個過程,并通過消息傳遞給它所有的參數,同時也會傳遞2個隐藏參數:
- 接收對象
-
方法選擇器
這些參數會給方法實作關于和兩部分的明确的資訊,之是以說是隐藏,是因為他們沒有在方法的源代碼中明确的聲明。它們是在編譯的時候插入實作中的。
盡管這些參數沒有被明确聲明,源代碼仍然能夠引用他們(就好像它能夠引用接收對象執行個體變量一樣)。一個方法用self來引用接收對象,用_cmd來引用選擇器。在下面的例子中,_cmd引用stange方法的選擇器,self引用接收stange消息的對象。
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">- strange
{
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">id</span> target = getTheReceiver();
SEL method = getTheMethod();
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ( target == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span> || method == _cmd )
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">nil</span>;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> [target performSelector:method];
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul>
self更有用,方法定義通過它使得接收對象的執行個體變量可用。
擷取一個方法的位址
唯一避免動态綁定的方法是擷取方法的位址然後直接調用它,就好像它是函數一樣。在一些罕見情況下,你可能想要特定的方法按順序執行很多次但又想避免每次方法執行時發消息的開銷,這可能是唯一合适的方法。
利用NSObject類中定義的methodForSelector:方法,你能夠查詢實作一個方法的函數指針,然後你可以用這個指針調用這個函數。methodForSelector:方法傳回的函數指針必須被小心地轉換成合适的類型,包括傳回值和參數。
下面這個例子顯示了實作setFilled:方法的函數是如何被調用的:
<code class="hljs oxygene has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">void <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">(*setter)(id, SEL, BOOL);
int i;
setter = (void (*)</span>(id, SEL, BOOL))[target
methodForSelector:@<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">selector</span>(setFilled:)];
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> ( i = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span> ; i < <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1000</span> ; i++ )
setter(targetList[i], @<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">selector</span>(setFilled:), YES);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul>
傳遞給函數的前兩個參數是接收對象(self)和方法選擇器(_cmd)。這些參數在方法文法中是隐藏的,但當方法作為函數調用時必須是顯式的。
發消息時使用methodForSelector:避免動态綁定可以節約很多時間,但是這種節約隻有當特定的消息被重複很多次的時候才比較明顯,正如上面的for循環顯示的那樣。
注意,methodForSelector: 是Cocoa runtime系統提供的,不是Objective-C語言的特性。
動态方法解析
本章講解如何為一個方法動态提供實作。
動态方法解析
在某些場合你可能希望動态地給一個方法提供實作,例如說Objective-C語言的@dynamic關鍵字:
<code class="hljs css has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-at_rule" style="box-sizing: border-box;">@<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">dynamic</span> propertyName</span>;</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
它告訴編譯器這個方法将會被動态提供。
你能夠實作resolveInstanceMethod:和resolveClassMethod: 分别為一個執行個體對象和類動态地提供方法。
一個Objective-C方法是一個至少包括2個參數——self和_cmd的C函數。你可以利用函數class_addMethod将一個函數添加到類中,成為類的方法。對于如下函數:
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> dynamicMethodIMP(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">id</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span>, SEL _cmd) {
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// implementation ....</span>
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>
你可以使用resolveInstanceMethod:把它添加類中成為類的方法,例如:
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@implementation</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">MyClass</span></span>
+ (<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">BOOL</span>)resolveInstanceMethod:(SEL)aSEL
{
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (aSEL == <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@selector</span>(resolveThisMethodDynamically)) {
class_addMethod([<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span> class], aSEL, (IMP) dynamicMethodIMP, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"[email protected]:"</span>);
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">YES</span>;
}
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> [<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span> resolveInstanceMethod:aSEL];
}
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>
轉發方法(也叫消息轉發)和動态消息解析很大程度上是強相關的。在轉發機制啟動前,類有機會動态解析方法。如果使用respondsToSelector:或者instancesRespondToSelector: ,首先動态解析器可以為選擇器提供一個IMP。如果你實作了 resolveInstanceMethod:但隻想要特定的選擇器被通過轉發機制轉發,你可以針對那些不想轉發的選擇器傳回NO。
動态加載
Objective-C程式能夠在運作時加載和連結新的類和類别(catogery)。新代碼将和程式開始時加載的類和類别一樣很好的融入到程式中。
動态加載被用來做很多不同的事情。例如在系統偏好中的那些子產品就是動态加載的。
在Cocoa環境中,動态加載通常被用來允許程式定制化。你的程式在運作時可以加載别人寫的一些子產品——就好像Interface Builder加載定制的工具箱以及OSX系統偏好程式加載定制化偏好子產品。
被加載的程式能夠擴充原來程式的功能。他們以一種你允許但又不能預期和定義它的行為的方式提供這些子產品。你定義架構,他們實作代碼。
盡管在Mach-O檔案(objc_loadModules, 在objc/objc-load.h 中定義)中有方法執行Objective-C子產品的動态加載,Cocoa的NSBundle類為動态綁定提供了一個更友善的接口——它是面向對象的并且和相關類內建。參閱Foundation framework reference的NSBundle類的說明以擷取NSBundle類及其用法。關于Mach-O的資訊請參閱《OS X ABI Mach-O File Format Reference 》。
消息轉發
向一個對象發送它不能處理的消息會發生錯誤,然而,在聲明出錯之前,運作時系統給接收消息的對象
一個機會來處理消息。
轉發
當你向一個對象發送它不能處理的消息時,在它聲明出錯之前,runtime會給它發送forwardInvocation: 消息,這個消息有唯一的參數———一個NSInvocation對象,NSInvocation對象封裝了消息本身以及消息帶的參數。
你可以實作 forwardInvocation: 方法來給出一個該消息的預設響應,或者通過其他方式避免錯誤的産生。就像它的字面意思一樣,forwardInvocation: 通常用來将消息轉發給其它對象。
關于轉發的範圍和意圖,想象下面的場景:首先,假設你設計了一個對象,能夠響應 negotiate 消息,你想要它的響應包括另外一個對象的響應。你可以很輕松的實作:你隻需要在你實作的 negotiate 方法中将 negotiate 消息傳遞給另外的對象。
更進一步,假設你想要你的對象對于 negotiate 消息的響應在其他類中實作的。一個辦法是使你的對象的類繼承自其他類,然而這有時候不可能,例如你的類和實作 negotiate 的類在不同的繼承體系中。
盡管你的類不能繼承 negotiate 方法,你仍然可以通過實作一個方法,在這個方法中僅僅需要将消息傳遞給那個類對象,來“借用”這個 negotiate 方法。
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">- (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">id</span>)negotiate
{
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ( [someOtherObject respondsTo:<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@selector</span>(negotiate)] )
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> [someOtherObject negotiate];
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">self</span>;
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul>
這種方式有點麻煩,尤其是當你有很多消息要轉發給其他對象的時候。你必須去實作一個方法去覆寫每一個你想要從其他對象那裡“借”的方法。然而,想要覆寫每一個消息,處理每一種情況是不可能的,而且它依賴于運作時時間,它可能會随着以後實作的方法和類而改變。
forwardInvocation: 提供了一種特别的解決方案,它是動态而非靜态的。當一個對象由于缺少比對消息選擇器的方法而不能響應消息的時候,運作時系統會給它發送一個 forwardInvocation: 消息。每一個對象都從NSObject類繼承了forwardInvocation: 方法。然而,這個方法的NSObject版本僅僅調用了doesNotRecognizeSelector: 方法。通過重寫NSObject版本的這個方法,你就可以利用forwardInvocation: 消息提供的時機将你的消息轉發給其他對象。
要轉發一個消息,forwardInvocation:需要做的是:
1. 決定消息應該流向哪裡;
2. 将原始參數傳遞給它
這個消息必須要能被invokeWithTarget: 發送:
<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">- (<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span>)forwardInvocation:(NSInvocation *)anInvocation
{
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span>
[<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span> forwardInvocation:anInvocation];
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>
被轉發消息的傳回值會傳回給原始的發送者。所有類型的傳回值都能夠被傳遞給發送者,包括ids、結構和雙精度浮點型數字。
forwardInvocation: 的作用就好像一個無法識别的消息的分發中心,将消息分發給不同的接收者。或者是一個轉化中心,發送所有的消息到相同的目的地。它能将一個消息翻譯成另外一個,也能夠“吞噬”消息,沒有響應也沒有錯誤。forwardInvocation:也能将很多消息合并成一個響應。forwardInvocation: 如何執行取決于實作者。它提供的這樣的機制使得在轉發鍊中連結對象成為了可能。
注意:隻有當接收者沒有調用已有方法時forwardInvocation: 才會處理消息。也就是說如果你想讓你的對象轉發negotiate消息給其他對象,那麼它就不能有一個negotiate方法。如果它有的話,消息将永遠不能到達forwardInvocation:。
更多關于消息轉發和調用的資訊,請參與《 Foundation framework reference》中NSInvocation類的說明。
轉發和多重繼承
轉發模拟繼承,轉發能夠借用一些多重繼承的效果。一個對象通過轉發響應了一個消息,就好像從其它對象那裡借用或者繼承了一個方法實作一樣。
在上圖中,一個Warrior對象轉發negotiate消息給一個Diplomat對象,Warrior對象就好像Diplomat對象一樣,看起來它能夠響應negotiate消息了,在所有實際情況下它都能夠響應(盡管它是一個Diplomat對象)。
轉發消息的對象進而從繼承體系的兩個分支——它自己所在的分支和消息響應者所在的分支“繼承”了這個方法。在上面的例子中,看起來好像Warrior對象同時繼承自Diplomat類和它自己的超類。
消息轉發提供了你想要從多重繼承獲得的大多數特性。然而兩者有一個顯著地不同:多重繼承将不同的功能組合在一個對象中,它側重于大、多層面的對象。而轉發側重于将不同的能力配置設定給不同的對象。它将問題分解為小對象,以一種對消息發送者透明的方式将這些對象聯系起來。
代理對象
轉發不僅模仿多重繼承,而且也使得開發代表或覆寫更多實體對象的輕量級對象成為可能。代理站在另外那個對象的角度,并且向它傳遞消息。
代理使得向遠端對象轉發消息成為可能,參數可以正确的拷貝和擷取。但它不會拷貝遠端對象的功能,僅僅給遠端對象一個本地位址,通過這個本地位址它能夠接收消息。
其他類别的代理對象也是可能的。例如你有一個對象,這個對象有大量資料——它可能建立了一個複雜的圖像或者讀取了磁盤上檔案的内容。建立這個對象很耗時,是以你可能傾向于稍後建立——當需要它的時候或者系統資源閑置的時候。同時你需要這個對象的占位符,以便程式中的其他對象可以正常工作。
在這種情況下,你可能開始不會建立一個完整的對象,而是一個輕量級的代理。這個對象能夠自己處理一些事情,例如回答關于資料的問題,但是它主要的功能還是作為這個大對象的占位符,以及在合适的時候轉發消息。當這個代理對象的forwardInvocation: 方法接收到一個發給其他對象的消息時,它會在該對象不存在是建立它以確定對象的存在。所有發給這個大對象的消息都經過這個代理,對象和代理是等價的。
轉發和繼承
盡管轉發酷似繼承,但是NSObject類絕不會把兩者混淆起來。respondsToSelector: and isKindOfClass: 這兩個方法僅僅存在于繼承體系中,不會存在于轉發鍊中。例如,一個Warrior對象是否響應 negotiate 方法的代碼為:
<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ( [aWarrior respondsToSelector:@selector(negotiate)] )
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>
答案是否定的,盡管它能夠接收 negotiate 消息并且做出反應,通過轉發消息給Diplomat對象。
在很多情況下,否定是正确的答案。但也可能不是這樣。如果你用轉發去建立一個代理對象或者去擴充一個類,轉發機制就和繼承機制一樣透明。如果你想要你的對象表現的好像它們真的繼承了你要轉發到的對象的行為的話,你就需要重新實作respondsToSelector:方法和isKindOfClass:方法以包含你的轉發機制:
<code class="hljs applescript has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">- (BOOL)respondsToSelector:(SEL)aSelector
{
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> ( [super respondsToSelector:aSelector] )
<span class="hljs-command" style="box-sizing: border-box;"> return</span> YES;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
/* Here, test whether <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> aSelector message can *
* be forwarded <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">to</span> another object <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">and</span> whether <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">that</span> *
* object can respond <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">to</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">it</span>. Return YES <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">it</span> can. */
}
<span class="hljs-command" style="box-sizing: border-box;"> return</span> NO;
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul>
除了 respondsToSelector: 和 isKindOfClass: 方法,instancesRespondToSelector 方法也應該映射轉發機制。conformsToProtocol: 方法應該也被加到清單中。相似的,如果一個對象轉發它收到的所有方法,它應該有一個方法 methodSignatureForSelector:能夠傳回方法描述;例如,如果一個對象能夠轉發消息給它的代理,它可能會像下面這樣實作methodSignatureForSelector: 方法:
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
你可能考慮将這些轉發算法放在私有代碼中,這樣就有了所有方法,forwardInvocation:已經包括了,請調用它。
注意:這是一個進階技術,僅适用于沒有其他解決方案的情況。他不應該作為繼承的替代品。如果你必須要使用該技術,請确認你已經完全了解了你要轉發的類和轉發到的類的行為。
本節提到的方法都在Foundation framework reference 的 NSObject 類的說明中有描述。關于invokeWithTarget: 的更多資訊,請參考 Foundation framework reference 中 NSInvocation 的說明。
類型編碼
(都是表格,暫不翻譯,原文位址:Objective-C Runtime Programming Guide)
屬性聲明
當編譯器遇到屬性聲明(參見《The Objective-C Programming Language》中的屬性聲明),它會為這個類、類别或者協定産生一些描述性的 metadata。你可以通過一些方法通路這些metadata,這些方法能夠通過類或者協定的名字查詢屬性,擷取屬性的類型,以及拷貝屬性的屬性。屬性聲明的清單對每個類和協定都适用。
屬性類型和方法
Property 結構定義了屬性描述符的handle。
<code class="hljs d has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">typedef</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">struct</span> objc_property *Property;</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
你可以使用 class_copyPropertyList 和 protocol_copyPropertyList 方法擷取一個類、類别或者協定的屬性清單:
<code class="hljs perl has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">objc_property_t <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*class_copyPropertyList</span>(Class cls, unsigned <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*outCount</span>)
objc_property_t <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*protocol_copyPropertyList</span>(Protocol <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*proto</span>, unsigned <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*outCount</span>)
</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>
例如,給定如下類聲明:
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@interface</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">Lender</span> : <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">NSObject</span> {</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> alone;
}
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@property</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> alone;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">@end</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li></ul>
你可以這樣擷取屬性清單:
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">id</span> LenderClass = objc_getClass(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Lender"</span>);
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">unsigned</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> outCount;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>
你可以使用 property_getName 來擷取屬性名:
<code class="hljs vbnet has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">const</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">char</span> *property_getName(objc_property_t <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">property</span>)
</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>
你可以用方法 class_getProperty 和 protocol_getProperty 擷取一個類或協定的指定名字的屬性的引用:
<code class="hljs objectivec has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">objc_property_t class_getProperty(Class cls, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span> *name)
objc_property_t protocol_getProperty(Protocol *proto, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">char</span> *name, <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">BOOL</span> isRequiredProperty, <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">BOOL</span> isInstanceProperty)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>
你可以用 property_getAttributes 方法擷取屬性的名字和 @encode 類型的字元串。關于編碼類型字元串的細節,請參考 《類型編碼》章節,細節請參考 《屬性類型字元串》和《屬性類型描述舉例》。
<code class="hljs vbnet has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">const</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">char</span> *property_getAttributes(objc_property_t <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">property</span>)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
是以,你可以用下面這段代碼列印一個類的屬性清單:
<code class="hljs perl has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">id LenderClass = objc_getClass(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Lender"</span>);
unsigned <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> outCount, i;
objc_property_t <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">*properties</span> = class_copyPropertyList(LenderClass, &outCount);
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> (i = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"<span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">%s</span> <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">%s</span>\n"</span>, property_getName(property), property_getAttributes(property));
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right: 1px solid rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul>
屬性類型字元串
你可以使用 property_getAttributes 方法來擷取屬性的名字、@encode類型字元串,以及屬性的其他屬性。
字元串以T開頭,然後是 @encode 類型和逗号,最後是V和執行個體變量的名字。在這之中,屬性由以下這些描述符指定:
表格7-1 屬性類型編碼
(表格不再翻譯,原文位址:Objective-C Runtime Programming Guide)