本主主要內容包括:html
一、概述
二、參考
三、運行時系統的版本和平臺
四、和運行時系統的交互
五、消息
六、動態方法解析
七、消息轉發
八、類型編碼
九、屬性聲明git
Objective-C語言將決定儘量的從編譯和連接時推遲到運行時。只要有可能,Objective-C老是使用動態的方式來解決問題。這意味着Objective-C語言不只須要一個編譯器,同時也須要一個運行時系統來執行編譯好的代碼。這裏的運行時系統扮演的角色相似於 Objective-C語言的操做系統,Objective-C基於該系統來工做。 程序員
本文章將具體介紹NSObject類以及Objective-C程序是如何與運行時系統交互的。特別地,本文章還給出來怎樣在運行時動態地加載新類和將消息轉發給其它對象的範例,同時也給出了怎樣在程序運行時獲取對象信息的方法。 github
一般,若是僅僅寫一個Cocoa 程序,程序員不須要知道和理解Objective-C運行時系統的底層細節,但這篇文章仍然值得推薦閱讀,以瞭解 Objective-C運行時系統的原理,並能更好的利用 Objective-C的優勢。編程
《Objective-C 2.0 運行時系統參考庫》描述了Objective-C運行庫的數據結構和函數接口。程序能夠經過這些接口來和Objective-C運行時系統交互。例如,您能夠增長一個類或者方法,或者得到全部類的定義列表等。數組
《Objective-C 2.0 程序設計語言》介紹了Objective-C語言自己。緩存
《Objective-C 版本說明》給出了在最近版本的Mac OS X系統中關於Objective-C運行時系統的一些改動。微信
在不一樣的平臺上Objective-C運行時系統的版本也不相同。數據結構
Objective-C運行時系統有兩個已知版本:早期版本和現行版本。app
現行版本主要是Objective-C 2.0 及與其相關的新特性。早期版本的編程接口見《Objective-C 1運行時系統參考庫》;現行版本的編程接口見《Objective-C 2.0 運行時系統參考庫》。
在現行版本中,最顯著的新特性就是實例變量是「健壯(non-fragile )的」:
1)在早期版本中,若是您改變類中實例變量的佈局,您必須從新編譯該類的全部子類。
2)在現行版本中,若是您改變類中實例變量的佈局,您無需從新編譯該類的任何子類。
此外,現行版本支持聲明property 的synthesis屬性(參考《Objective-C 2.0 程序設計語言》的「屬性」一節)。
iPhone 程序和Mac OS X 10.5及之後的系統中的64位程序使用的都是Objective-C運行時系統的現行版本。
其它狀況(Mac OS X系統中的32位程序)使用的是早期版本。
Objective-C程序有三種途徑和運行時系統交互:
1)經過 Objective-C源代碼;
2)經過 Foundation框架中類NSObject的方法;
3)經過直接調用運行時系統的函數。
大部分狀況下,運行時系統在後臺自動運行,您只需編寫和編譯Objective-C源代碼。
當您編譯Objective-C類和方法時,編譯器爲實現語言動態特性將自動建立一些數據結構和函數。這些數據結構包含類定義和協議類定義中的信息,如在《Objective-C 2.0 程序設計語言》中「定義類」和「協議類」一節所討論的類的對象和協議類的對象,方法選標,實例變量模板,以及其它來自於源代碼的信息。運行時系統的主要功能就是根據源代碼中的表達式發送消息,如「消息」一節所述。
Cocoa 程序中絕大部分類都是NSObject類的子類,因此大部分都繼承了NSObject類的方法,於是繼承了NSObject的行爲。(NSProxy類是個例外;更多細節參考「消息轉發」一節。)然而,某些狀況下,NSObject類僅僅定義了完成某件事情的模板,而沒有提供全部須要的代碼。
例如,NSObject類定義了description 方法,返回該類內容的字符串表示。這主要是用來調試程序——GDB中的print-object方法就是直接打印出該方法返回的字符串。NSObject類中該方法的實現並不知道子類中的內容,因此它只是返回類的名字和對象的地址。NSObject的子類能夠從新實現該方法以提供更多的信息。例如,NSArray 類改寫了該方法來返回NSArray 類包含的每一個對象的內容。
某些NSObject的方法只是簡單地從運行時系統中得到信息,從而容許對象進行必定程度的自我檢查。例如,
class 返回對象的類;
isKindOfClass:和isMemberOfClass:檢查對象是否在指定的類繼承體系中;
respondsToSelector:檢查對象可否響應指定的消息;
conformsToProtocol:檢查對象是否實現了指定協議類的方法;
methodForSelector:返回指定方法實現的地址。
運行時系統是一個有公開接口的動態庫,由一些數據結構和函數的集合組成,這些數據結構和函數的聲明頭文件在/usr/include/objc 中。這些函數支持用純 C 的函數來實現Objective-C一樣的功能。還有一些函數構成了NSObject類方法的基礎。這些函數使得訪問運行時系統接口和提供開發工具成爲可能。儘管大部分狀況下它們在 Objective-C程序不是必須的,可是有時候對於 Objecitve-C程序來講某些函數是很是有用的。
這些函數的文檔參見《Objective-C 2.0 運行時系統參考庫》。
本章節描述了代碼的消息表達式如何轉換爲對objc_msgSend函數的調用,如何經過名字來指定一個方法,以及如何使用objc_msgSend函數。
避免動態綁定的惟一辦法就是取得方法的地址,而且直接像函數調用同樣調用它。當一個方法會被連續調用不少次,並且您但願節省每次調用方法都要發送消息的開銷時,使用方法地址來調用方法就顯得頗有效。
利用NSObject類中的methodForSelector:方法,您能夠得到一個指向方法實現的指針,並可使用該指針直接調用方法實現。methodForSelector:返回的指針和賦值的變量類型必須徹底一致,包括方法的參數類型和返回值類型都在類型識別的考慮範圍中。
下面的例子展現了怎麼使用指針來調用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);
方法指針的第一個參數是接收消息的對象(self),第二個參數是方法選標(_cmd)。這兩個參數在方法中是隱藏參數,但使用函數的形式來調用方法時必須顯示的給出。
使用methodForSelector:來避免動態綁定將減小大部分消息的開銷,可是這隻有在指定的消息被重複發送不少次時纔有意義,例如上面的for 循環。
注意:methodForSelector:是Cocoa 運行時系統的提供的功能,而不是Objective-C語言自己的功能。
在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 變量。
類和對象的結構如圖 5-1 所示。
圖5-1 消息框架
當對象收到消息時,消息函數首先根據該對象的 isa 指針找到該對象所對應的類的方法表,並從表中尋找該消息對應的方法選標。若是找不到,objc_msgSend將繼續從父類中尋找,直到 NSObject類。一旦找到了方法選標, objc_msgSend則以消息接收者對象爲參數調用,調用該選標對應的方法實現。
這就是在運行時系統中選擇方法實現的方式。在面向對象編程中,通常稱做方法和消息動態綁定的過程。
爲了加快消息的處理過程,運行時系統一般會將使用過的方法選標和方法實現的地址放入緩存中。每一個類都有一個獨立的緩存,同時包括繼承的方法和在該類中定義的方法。消息函數會首先檢查消息接收者對象對應的類的緩存(理論上,若是一個方法被使用過一次,那麼它極可能被再次使用)。若是在緩存中已經有須要的方法選標,則消息僅僅比函數調用慢一點點。若是程序運行了足夠長的時間,幾乎每一個消息都能在緩存中找到方法實現。程序運行時,緩存也將隨着新的消息的增長而增長。
當objc_msgSend找到方法對應的實現時,它將直接調用該方法實現,並將消息中全部的參數都傳遞給方法實現,同時,它還將傳遞兩個隱藏的參數:
1)接收消息的對象
2)方法選標
這些參數幫助方法實現得到了消息表達式的信息。它們被認爲是「隱藏」的緣由是它們並無在定義方法的源代碼中聲明,而是在代碼編譯時是插入方法的實現中的。
儘管這些參數沒有被顯示聲明,但在源代碼中仍然能夠引用它們(就像能夠引用消息接收者對象的實例變量同樣)。在方法中能夠經過 self來引用消息接收者對象,經過選標_cmd來引用方法自己。在下面的例子中,_cmd指的是strange 方法,self指的收到strange 消息的對象。
- strange { id target = getTheReceiver(); SEL method = getTheMethod(); if ( target == self || method == _cmd ) return nil; return [target performSelector:method]; }
在這兩個參數中,self更有用一些。實際上,它是在方法實現中訪問消息接收者對象的實例變量的途徑。
本章節將描述怎樣動態地提供一個方法的實現。
有時候,程序員須要動態地提供一個方法的實現。例如,Objective-C中屬性(Property )( 參考《Objective-C 2.0 程序設計語言》中「屬性」小節)前的修飾符@dynamic
@dynamic propertyName;
表示編譯器須動態地生成該屬性對應地方法。
程序員能夠經過實現resolveInstanceMethod:和resolveClassMethod:來動態地實現給定選標的對象方法或者類方法。
Objective-C方法能夠認爲是至少有兩個參數self和_cmd的C 函數。您能夠經過class_addMethod 方法將一個函數加入到類的方法中。例如,有以下的函數:
void dynamicMethodIMP(id self, SEL _cmd) { // implementation .... }
程序員能夠經過resolveInstanceMethod:將它做爲類方法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
一般消息轉發(見 「消息轉發」)和動態方法解析是互不相干的。在進入消息轉發機制以前,respondsToSelector:和instancesRespondToSelector: 會被首先調用。您能夠在這兩個方法中爲傳進來的選標提供一個IMP 。若是您實現了resolveInstanceMethod:方法,可是仍然但願正常的消息轉發機制進行,您只須要返回NO便可。
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 文件格式參考庫》。
一般,給一個對象發送它不能處理的消息會獲得出錯提示,然而,Objective-C運行時系統在拋出錯誤以前,會給消息接收對象發送一條特別的消息來通知該對象。
若是一個對象收到一條沒法處理的消息,運行時系統會在拋出錯誤前,給該對象發送一條forwardInvocation:消息,該消息的惟一參數是個NSInvocation類型的對象——該對象封裝了原始的消息和消息的參數。
程序員能夠實現forwardInvocation:方法來對不能處理的消息作一些默認的處理,也能夠以其它的某種方式來避免錯誤被拋出。如forwardInvocation:的名字所示,它一般用來將消息轉發給其它的對象。
關於消息轉發的做用,您能夠考慮以下情景:假設,須要設計一個可以響應negotiate 消息的對象,而且可以包括其它類型的對象對消息的響應。經過在negotiate 方法的實現中將negotiate 消息轉發給其它的對象這種方式能夠很容易的達到這一目的。
更進一步,假設您但願您的對象和另一個類的對象對 negotiate 的消息的響應徹底一致。一種可能的方式就是讓您的類繼承其它類的方法實現。然而,有時候這種方式不可行,由於您的類和其它類可能須要在不一樣的繼承體系中響應negotiate 消息。
雖然您的類沒法繼承其它類的negotiate 方法,您仍然能夠提供一個方法實現,這個方法實現只是簡單的將negotiate 消息轉發給其餘類的對象,就好像從其它類那裏「借」來的實現同樣。以下所示:
- negotiate { if ( [someOtherObject respondsTo:@selector(negotiate)] ) return [someOtherObject negotiate]; return self; }
這種方式顯得有欠靈活,特別是有不少消息您都但願傳遞給其它對象時,您必須爲每一種消息提供方法實現。此外,這種方式不能處理未知的消息。當您寫下代碼時,全部您須要轉發的消息的集合也必須肯定。然而,實際上,這個集合會隨着運行時事件的發生,新方法或者新類的定義而變化。
forwardInvocation:消息給這個問題提供了一個更特別的,動態的解決方案:當一個對象因爲沒有相應的方法實現而沒法響應某消息時,運行時系統將經過 forwardInvocation:消息通知該對象。每一個對象都從NSObject類中繼承了forwardInvocation:方法。然而,NSObject中的方法實現只是簡單地調用了doesNotRecognizeSelector:。經過實現您本身的forwardInvocation:方法,您能夠在該方法實現中將消息轉發給其它對象。
要轉發消息給其它對象,forwardInvocation:方法所必須作的有:
1)決定將消息轉發給誰,而且
2)將消息和原來的參數一塊轉發出去
消息能夠經過invokeWithTarget:方法來轉發:
- (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類的文檔。
消息轉發很像繼承,而且能夠用來在Objective-C程序中模擬多重繼承。如圖 7-1 所示, 一個對象經過轉發來響應消息,看起來就像該對象從別的類那借來了或者「繼承」了方法實現同樣。
圖7-1 消息轉發
在上圖中,Warrior類的一個對象實例將negotiate 消息轉發給Diplomat 類的一個實例。看起來,Warrior相似乎和Diplomat 類同樣,響應negotiate消息,而且行爲和Diplomat 同樣(儘管其實是Diplomat類響應了該消息)。
轉發消息的對象看起來有兩個繼承體系分支——本身的和響應消息的對象的。在上面的例子中,Warrior看起來同時繼承自Diplomat 和本身的父類。
消息轉發提供了多重繼承的不少特性。然而,二者有很大的不一樣:多重繼承是將不一樣的行爲封裝到單個的對象中,有可能致使龐大的,複雜的對象。而消息轉發是將問題分解到更小的對象中,可是又以一種對消息發送對象來講徹底透明的方式將這些對象聯繫起來。
消息轉發不只和繼承很像,它也使得以一個輕量級的對象(消息代理對象)表明更多的對象進行消息處理成爲可能。
《Objective-C 2.0 程序設計語言》中「遠程消息」一節中的代理類就是這樣一個代理對象。代理類負責將消息轉發給遠程消息接收對象的管理細節,保證消息參數的傳輸等等。可是消息類沒有進一步的複製遠程對象的功能,它只是將遠程對象映射到一個本地地址上,從而可以接收其它應用程序的消息。
同時也存在着其它類型的消息代理對象。例如,假設您有個對象須要操做大量的數據——它可能須要建立一個複雜的圖片或者須要從磁盤上讀一個文件的內容。建立一個這樣的對象是很費時的,您可能但願能推遲它的建立時間——直到它真正須要時,或者系統資源空閒時。同時,您又但願至少有一個預留的對象和程序中其它對象交互。
在這種狀況下,您能夠爲該對象建立一個輕量的代理對象。該代理對象能夠有一些本身的功能,例如響應數據查詢消息,可是它主要的功能是表明某個對象,當時間到來時,將消息轉發給被表明的對象。當代理對象的forwardInvocation:方法收到須要轉發給被表明的對象的消息時,代理對象會保證所表明的對象已經存在,不然就建立它。全部發到被表明的對象的消息都要通過代理對象,對程序來講,代理對象和被表明的對象是同樣的。
儘管消息轉發很「像」繼承,但它不是繼承。例如在NSObject類中,方法respondsToSelector:和isKindOfClass:只會出如今繼承鏈中,而不是消息轉發鏈中。例如,若是向一個 Warrior類的對象詢問它可否響應negotiate 消息,
if ( [aWarrior respondsToSelector:@selector(negotiate)] ) ...
返回值是NO,儘管該對象可以接收和響應negotiate。(見圖 7-1 。)
大部分狀況下,NO是正確的響應。但不是全部時候都是的。例如,若是您使用消息轉發來建立一個代理對象以擴展某個類的能力,這裏的消息轉發必須和繼承同樣,儘量的對用戶透明。若是您但願您的代理對象看起來就像是繼承自它表明的對象同樣,您須要從新實現respondsToSelector:和isKindOfClass:方法:
- (BOOL)respondsToSelector:(SEL)aSelector { if ( [super respondsToSelector:aSelector] ) return YES; else { /* Here, test whether the aSelector message can * * be forwarded to another object and whether that * * object can respond to it. Return YES if it can. */ } return NO; }
除了respondsToSelector:和isKindOfClass:方法外,instancesRespondToSelector:方法也必須從新實現。若是您使用的是協議類,須要從新實現的還有conformsToProtocol:方法。相似地,若是對象須要轉發遠程消息,則 methodSignatureForSelector:方法必須可以返回實際響應消息的方法的描述。例如,若是對象須要將消息轉發給它所表明的對象,您可能須要以下的methodSignatureForSelector:實現:
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { NSMethodSignature* signature = [super methodSignatureForSelector:selector]; if (!signature) { signature = [surrogate methodSignatureForSelector:selector]; } return signature; }
您也能夠將消息轉發的部分放在一段私有的代碼裏,而後從forwardInvocation:調用它。
注意:消息轉發是一個比較高級的技術,僅適用於沒有其它更好的解決辦法的狀況。它並非用來代替繼承的。若是您必須使用該技術,請肯定您已經徹底理解了轉發消息的類和接收轉發消息的類的行爲。
本節中涉及的方法在Foundation框架參考庫中的NSObject類的文檔中都有描述。關於invokeWithTarget:的具體信息,請參考Foundation框架參考庫中NSInvocation類的文檔。
爲了和運行時系統協做,編譯器將方法的返回類型和參數類型都編碼成一個字符串,而且和方法選標關聯在一塊兒。這些編碼在別的上下文環境中一樣有用,因此您能夠直接使@encode()編譯指令來獲得具體的編碼。給定一個類型, @encode()將返回該類型的編碼字符串。類型能夠是基本類型例如整形,指針,結構體或者聯合體,也能夠是一個類,就和 C 語言中的sizeof()操做符的參數同樣,能夠是任何類型。
char *buf1 = @encode(int **); char *buf2 = @encode(struct key); char *buf3 = @encode(Rectangle);
下表列出了這些類型編碼。注意,它們可能不少和您使用的對象編碼有一些重合。然而,這裏列出來的有些編碼是您寫編碼器的時候不會使用的,也有一些不是@encode()產生的,可是在您寫編碼器的時候是會使用的。(關於對象編碼的更多信息,請參考Foundation框架參考庫中的NSCoder類文檔。)
表8-1 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]
結構體和聯合體分別用大括號和小括號表示。括號中首先是結構體標籤,而後是一個「=」符號,接着是結構體中各個成員的編碼。例如,結構體
typedef struct example { id anObject; char *aString; int anInt; } Example;
的編碼以下:
{example=@*i}
定義的類型名(Example)和結構體標籤(example)有一樣的編碼結果。指向結構體類型的指針的編碼一樣也包含告終構體內部數據成員的編碼信息,以下所示:
^{example=@*i}
然而,更高層次的間接關聯就沒有了內部數據成員的編碼信息:
^^{example}
對象的編碼相似結構體。例如, @encode()對NSObject編碼以下:
{NSObject=#}
NSObject類僅聲明瞭一個Class 類型的實例變量,isa。
注意,儘管有一些編碼沒法從 @encode()的結果中直接獲得,可是運行時系統會使用它們來表示協議類中方法的修飾符,這些編碼如表8-2 所示。
表8-2 Objective-C方法編碼 |
|
編碼 |
含義 |
r |
const |
n |
in |
N |
inout |
o |
out |
O |
bycopy |
R |
byref |
V |
oneway |
當編譯器遇到一個屬性(Property )聲明時(參考《Objective-C 2.0 程序設計語言》中的「屬性」小節),編譯器將產生一些描述性的元數據與屬性所在的類或者協議類關聯。您能夠經過函數訪問元數據,這些函數支持在類或者協議類中經過名字來查找,經過@encode得到屬性的類型編碼,將屬性的特徵(Attribute )做爲C字符串的數組返回等。每一個類或者協議類都維護了一個聲明瞭的屬性列表。
屬性(Property )類型定義了對描述屬性的結構體objc_property 的不透明的句柄。
typedef struct objc_property *Property;
您可使用函數class_copyPropertyList和protocol_copyPropertyList 來得到類(包括範疇類)或者協議類中的屬性列表:
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函數得到屬性的名字:
const char *property_getName(objc_property_t property)
函數class_getProperty 和protocol_getProperty則在類或者協議類中返回具備給定名字的屬性的引用:
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編碼。關於類型編碼的更多細節,參考「類型編碼」一節;關於屬性的類型編碼,見「屬性類型編碼」及「屬性特徵的描述範例」。
const char *property_getAttributes(objc_property_t property)
綜合起來,您能夠經過下面的代碼獲得一個類中全部的屬性。
id LenderClass = objc_getClass("Lender"); unsigned int outCount, i; objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount); for (i = 0; i < outCount; i++) { objc_property_t property = properties[i]; fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property)); }
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 而後是屬性的名字結束。
範例請參考 「屬性特徵的描述範例」 一節。
給定以下定義:
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 id idDefault; Note:the complier warns:no ‘assign’,’retain’,or’copy’ attribute is specified – ‘assign’ is assumed |
T@,VidDefault |
@property int *intPointer; |
T^i,VintPointer |
@property void *voidPointerDefault; |
T^v,VvoidPointerDefault |
@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 |
在此,運行時機制相關問題已經所有闡述。消息發送和轉發是Runtime的強大之處,經過它,您能夠爲程序增長不少動態的行爲,雖然在實際開發中不多直接使用這些機制(如直接調用objc_msgSend),但瞭解它們有助於您更多地去了解底層的實現。其實在實際的編碼過程當中,您也能夠靈活地使用這些機制,去實現一些特殊的功能,如hook操做等。
原文:http://www.apple.com.cn/developer/Documentation/index.html
【GitHub】關注更多精彩代碼,請前往這裏。
【微信掃一掃,關注更多精彩內容】