若是讀到感受不理解、晦澀的地方,或者想要交流的能夠聯繫我QQ1325582826,Call me!歡迎賜教!html
Objective-C語言儘量多的將許多決定從編譯鏈接推遲到運行時。不管什麼時候,它都儘量的動態處理事件。這就意味着OC語言不只僅須要編譯器,還須要一個運行時系統來執行編譯完成的代碼。對於OC而言,運行時系統扮演了操做系統的角色;就是它使得OC運行起來。編程
這個文檔涉及到NSObject類和Objective-C程序如何與運行時系統互相做用。尤爲是,對於動態加載新的類和向其餘對象轉發消息,本文檔可用於檢索編程示例。咱們也能夠從本文檔查到在程序運行時,關於如何查找到對象相關的信息。數組
咱們應該閱讀此文檔,以便加深(對OC運行時系統是如何工做的和如何利用它)的認知和理解。尤爲是,咱們在寫Cocoa APP時,有必要閱讀這份文檔。緩存
本文檔有一下章節:數據結構
Objective-C Runtime Reference描述了OC運行時庫支持的數據結構和函數。咱們變成可使用這些接口和OC運行時系統交互。例如,咱們能夠添加類和方法,或者獲取全部(已經加載的)類的定義的列表。
Programming with Objective-C 描述了OC語言。
Objective-C Release Notes 描述了OSX中,OC運行時在最近實現的變化。app
在不一樣的平臺,有不一樣版本的OC runtime。ide
有兩個版本的OC runtime——「舊版」和「如今版」。如今版就是OC-2.0幷包含了許多新特性。舊版本的runtime的編程接口就是OC-1;如今版本的runtime所有接口參見 Objective-C Runtime Reference 。
最值得注意的新特性是,如今版本的實例變量是「不脆弱的」:函數
另外,如今版本的runtime支持爲聲明的屬性作實例變量的synthesis(參見Objective-C Programming Language)。工具
iPhone應用和OSX 10.5版本的64-位編程使用如今版本的runtime。佈局
OC編程和runtime系統的相互做用,能夠分三個不一樣的標準:
這是最重要的一部分,runtime 系統在該場景背後自動運行。咱們僅僅經過寫和編譯OC代碼就可使用runtime系統。
當編譯包含OC類和方法的代碼時,編譯器就會建立數據結構和(實現了語言動態特徵)函數。數據結構可以捕獲有Class和category以及protocol中聲明的信息;它們包含了Class和Protocol(在Objective-C Programming Language 中定義的Class和Protocol,還有方法selectors、實例變量以及其餘從源碼中提取到的信息)。主要的runtime功能就是發送消息,參見 Messaging ,它也會被OC代碼消息表達式調用。
許多Cocoa種的對象都是NSObject類的子類,所以許多對象繼承了它定義的方法。(NSProxy類是個例外,更多信息參見 Message Forwarding 。)所以它的方法創建了行爲(對每一個實例和類對象來講都是已經存在的方法實現)。少數狀況下,NSObject類只定義了應該如何作的方法模板,它自身不提供全部的必須的代碼。
例如,NSObject類定義了description實例方法,該方法用於返回一個用於描述類內容的字符串,這主要是用於debugging—GDB print-object命令打印由該方法返回額字符串。NSObject的該方法的實現不知道該類包含什麼,所以它返回一個包含了對象的名稱和地址的字符串。NSObject的子類可以重寫該方法並返回更詳細的描述。例如,Foundation的NSArray類返回了一個array包含的全部的對象的列表。
NSObject的一些方法僅僅查詢runtime系統獲取信息。這些方法使得對象可以執行校驗。例如「class」方法,是用來查詢對象的類型;isKindOfClass:和isMemberOfClass:,是測試對象在繼承層次中的位置;respondsToSelector:,用於校驗對象可否接收一個指定的消息;conformsToProtocol:,用於校驗是否某個對象聲明瞭指定Protocol中定義的方法的實現;methodForSelector:,用於提供方法實現體的地址。這些對象自己都是校驗性的能力的方法。
運行時系統是動態共享庫,而且頭文件中(文件路徑/usr/include/objc)有一系列函數和數據結構接口聲明;其中大部分函數容許咱們使用基本的C來複制那些編譯器的實現(同咱們以OC代碼編譯後的代碼)。其餘基礎功能能夠經過NSObject得到。這些功能可以讓咱們爲runtime 系統開發其接口和工具,以便提升開發效率;在使用OC編程時也能夠不使用runtime接口。不過,當用OC編寫程序時,有些runtime函數功能在某些場合是很是有用的。全部runtime函數聲明可參見Objective-C Runtime Reference。
本節講述消息表達式是如何轉換爲objc_msgSend函數調用的,和如何經過name查找方法;而後會解釋咱們如何充分利用objc_msgSend,和如何避免動態綁定(若是有須要)。
OC中,在運行時前,消息是不能肯定方法的實現體地址的。編譯器轉換消息表達式,
[receiver message]
轉換成消息函數,objc_msgSend。這個函數須要該消息表達式中的接收者(receiver)和方法的名字——方法的selector做爲第二個主要的參數:
objc_msgSend(receiver, selector)
任何傳入消息的參數都和一經過objc_msgSend處理:
objc_msgSend(receiver, selector, arg1, arg2, ...)
消息函數爲動態綁定完成全部所須要的事情:
注意:編譯器生成消息函數的調用。咱們永遠不要在本身寫的代碼中直接調用它(PS:文檔中此處所說的注意在現實中,貌似只起到了提醒你們要確保消息發送正確的做用,慎重使用)。
消息發送的核心在於編譯器爲每一個類個對象建立的結構體,每一個類結構體包含兩個基本的要素:
注意:該語言有不嚴謹的一部分,isa指針是對象關聯OC運行時系統所必須的。一個對象須要「等價」於一個結構體 objc_object (在objc/objc.h中定義的) ,包含全部該結構體中的份量。然而,咱們不多建立咱們本身的根類(root object),繼承自NSObject或者NSProxy的對象會自動具有isa變量。
Class和對象結構體擁有的基本元素如圖3-1所示:
當一個消息被髮送到某對象,這個消息函數查找指向該類結構體的isa指針,在類結構體中查找到方法的分發表裏的對應的selector;若是在此處找不到selector,objc_msgSend 順着superclass的指針嘗試在superclass中的分發表中查找selector。若是尚未查找到,那麼objc_msgSend將會沿着類的繼承層次向上尋找,直到NSObject類。一旦定位到selector,函數將會調用分發表裏的方法,並將reciver 對象的數據結構體傳遞給它。
這就是方法實現體在運行時選擇的方式,以面向對象編程的術語說,methods(方法實現)就是動態綁定到message(消息)。
爲了加速消息發送的過程,runtime系統緩存selector和methods被使用的地址。每一個類都有單獨 的cache,它能夠包含繼承的方法的selector,也會包含在該類中定義的方法。在搜索分發表以前,消息機制會先檢查receiver 對象類的cache(理論上,被使用過一次的method頗有可能被再次調用);若是selector是在cache裏面的,消息發送就只是稍微慢於函數調用。一旦程序運行了足夠長時間使得它的類的caches「徹底活躍」,基本上消息發送就是經過查找cache裏的method完成了。爲了容納新的消息發送,Caches隨着程序的運行動態增加。
當objc_msgSend找到method的實現體時,他就會調用實現體,並將消息中的全部參數傳遞給他,其中也包含如下兩個參數:
Method的實現體將會從消息表達式中得到這兩個參數。這兩個參數被稱爲「隱藏的」,是由於它們沒有在method中的源碼中聲明。它們在編譯的時候被插入實現體。
儘管這兩個參數是隱式聲明的,源代碼仍舊可以引用他們(因爲它可以指向receiver 對象的實例變量)。一個method引用receiver對象就是self(對象自己),對於它本身的selector就是_cmd。下面的例子中,_cmd指向strange方法的selector,self指向接受strange消息的對象。
- strange { id target = getTheReceiver(); SEL method = getTheMethod(); if ( target == self || method == _cmd ) return nil; return [target performSelector:method]; }
self是這兩個參數中最有用的。本質上來講,就意味着,receiver對象的實例變量對於method是可用的。
惟一避免動態綁定的方式就是得到method的地址並直接把它當作一個函數調用。若是某個method須要連續的調用屢次,而且咱們想要避免以前每次method被執行時的消息發送環節,此時避免動態綁定是有必要的。
經過使用NSObject類的methodForSelector:方法,咱們能夠查找method的實現體的指針,而後使用這個指針調用實現體。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);
傳遞到函數的前兩個參數是receiver對象(self)和method selector(_cmd)。這些參數是隱藏在method語法中,可是當method被做爲函數調用時是必需要顯示傳遞的。
使用methodForSelector:可以避免動態綁定,並節省消息發送機制所須要的時間。然而,僅僅當執行要被重複屢次的特殊的消息(就像上面的for循環展現同樣)這種節省時間的纔有意義。
注意methodForSelecotor:是被Cocoa runtime系統提供的,而不是OC語言的特徵。
本節主要講如何爲method提供動態的IMP(實現)。
有時候咱們須要爲method動態地提供IMP。例如OC聲明屬性特徵(Declared Properties in The Objective-C Programming Language)包括 @dynmaic 指令:
@dynamic propertyName;
這將會告訴編譯器,這個屬性關聯的methods將會動態提供。
咱們可以經過實現methodsresolveInstanceMethod:和resolveClassMethod:來分別爲實例或者類method指定的selector提供IMP。
一個OC的method就是簡單的C函數,只不過這個C函數至少有兩個參數——self和_cmd。咱們可以使用函數class_addMethod爲一個類添加一個函數。例以下面的函數:
void dynamicMethodIMP(id self, SEL _cmd) { // implementation .... }
咱們可以動態的爲一個類添加一個method(調用resolveThisMethodDynamically)使用resolveInstanceMethod:以下:
@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
轉發methods(就像Message Forwarding中介紹的)和動態方法的原理基本是同樣的。一個Class在轉發機制前仍是有方法動態處理method的。若是respondsToSelector:或者instancesRespondToSelector是被調用的,此時,動態method是被給一次機會爲selector提供IMP。若是咱們實現resolveInstanceMethod:,可是想要某個特殊方法繼續消息轉發機制,咱們應該爲這些selector返回NO。
一個OC程序可以在運行的時候,加載和鏈接新的類和分類。新的代碼是被合併到程序內並和最初加載的類和分類同等對待。
動態加載可以被用於作許多不一樣的事情。例如,系統設置APP裏的不一樣的模塊是被動態加載的。
在Cocoa環境中,動態加載一般被用於讓APP更個性化。咱們程序可以在運行時加載第三方寫的模塊——儘管Interface Buider加載自定義的工具,以及OSX偏好設置APP加載自定義的偏好模塊。可加載的模塊擴展了咱們APP的功能,它們在通過APP容許的狀況下才起做用,可是也有難以預測的問題。
儘管,有一個runtime功能是爲在Mach-O文件中的OC模塊執行動態加載(objc_loadModules,在objc/objc-load.h中定義),Cocoa的NSBundle類爲動態加載提供大量的更便利的接口——面向對象並集成了相關服務。經過查看NSBundle類在Foundation framework中的說明,查找NSBundle類和它的使用。相應的對於Mach-O文件查看OSX ABI Mach-O 文件格式參考資料。
將消息發送給一個對象,而且對象沒有處理這個消息,就會產生一個錯誤。可是,在宣告錯誤以前,運行時系統給receiver 對象第二次機會去處理消息。
若是將消息發送給一個object,而且object沒有處理這個消息,在宣告錯誤以前runtime將forwardInvocation:消息和惟一一個參數即NSInvocation對象發送給object;NSInvocation對象封裝了最初的消息和傳遞過來的參數。
咱們可以實現forwardInvocation:方法爲消息提供一個默認的相應,或者以某種方式避免這種錯誤。見名知意,forwardInvocation:一般被用於將消息轉發到其餘對象。
爲了瞭解轉發的能力範圍和目的,想一想一下場景:假設,首先,咱們設計一個對象可以響應一個叫作negotiate的消息,而且咱們咱們但願這個消息的響應中包含另一種對象的響應。經過在negotiate的實現體中將negotiate消息傳遞給另外的對象,咱們可以輕鬆的完成這個任務。
進一步想一想,並假設咱們想要咱們的object爲一個negotiate消息作出的響應,是另一個類的實現體。一種實現方式是讓咱們的類繼承另外的類。然而,咱們不必這麼作,由於當前的類和實現了negotiate的類是在不一樣的繼承層次分支上。
即便咱們的類不經過繼承也能得到negotiate方法,咱們可以「借」到這個方法,簡單的版本就是經過將消息傳遞給其餘類的實例:
- (id)negotiate { if ( [someOtherObject respondsTo:@selector(negotiate)] ) return [someOtherObject negotiate]; return self; }
這種方式顯得比較笨重,尤爲是,若是咱們有大量的消息須要object傳遞給其餘的對象。咱們不得不以這種方式重寫每一個咱們要「借」的方法。此外,他將不可能處理咱們漏掉或者不知道的方法;即使咱們將包含object全部消息的集合都以這種形式複寫了,但是這些消息都是依賴於運行時的,而且在將來某個時刻他們可能變成了由新的method和類響應。
由forwardInvocation:消息提供的第二次機會,針對此問題,提供更少許代碼的解決方案,而且是動態的而不是靜態的。它將像這樣:當一個對象因爲它沒有匹配selector的method而不能響應某個消息時,系統將經過發送forwardInvocation:消息告知對象。每個對象都從NSObject繼承了一個forwardInvocation:方法。只不過,NSObject版本的此方法只是簡單的調用了doesNotRecognizeSelector:。經過重寫NSObject版本的此方法並本身給出實現,咱們可以利用這個機會,經過forwardInvocation:將消息轉發到其餘對象。
爲了轉發消息,forwardInvocation:方法內應該這麼作:
可使用invokeWithTarget:將消息發送出去:
- (void)forwardInvocation:(NSInvocation *)anInvocation { if ([someOtherObject respondsToSelector: [anInvocation selector]]) [anInvocation invokeWithTarget:someOtherObject]; else [super forwardInvocation:anInvocation]; }
被轉發的消息的返回值會被返回到原來的發送者。全部類型的返回值可以被傳遞到發送者,包括ids,結構體,和雙精度floating數字。
一個forwardInvocation:方法可以用做於未識別的消息的分配中心,將他們發給不一樣的receiver;或者做爲一個轉發站,將消息發送到相同的目的地。它也能夠將一個消息轉義爲別的消息;或者簡單的「吃掉」某些消息,使得既沒有相應也沒有錯誤。一個forwardInvocation:方法也能講幾個消息結合起來,得到一個響應。forwardInvocation:可以作什麼取決於實現這。它爲轉發鏈條的對象開啓了加入編程設計的機會。
注意:僅當它們沒法調用receiver內存在的method時,forwardInvocation:方法纔會處理消息。例如,咱們想要咱們的object轉發negotiate消息到另外的object,首先咱們的object不能有negotiate方法,若是有,消息將不會到達forwardInvocation:。
更多信息關於轉發和調用能夠參見NSInvocation類的說明。
轉發能夠模擬繼承,能夠用於提供多繼承的效果。像圖5-1中,一個經過轉發響應圖中消息的object,看起來像是借或者「繼承」了另一個類的方法實現。
在這個插圖中,一個Warrior類的實例將negotiate消息轉發給一個Diplomat類的實例。Warrior將會看起來像Diplomat的negotiate實現,Warrior看起來好像響應了negotiate消息(儘管其實是Diplomat響應的)。
轉發了消息的對象也所以「繼承」了來自多重繼承的兩個分支——它本身的分支和實際響應消息的object。在上面的例子中,Warrior看起來好像它同時繼承了Diploma和它本身的superclass。
轉發提供許多特性,典型的就是多繼承:可是,多繼承和轉發是兩個不一樣的事物:多繼承將多個類的功能集結在一個對象上,它變得更大,多個對象的結合體;而轉發,換句話說,是將某些響應與不相干的對象關聯起來。它將問題分解成更小的目標,並將這些小目標與消息發送者關聯起來。
Forwarding不只能夠模擬多繼承;經過forwarding,咱們能夠用輕量級對象做爲實質對象的「封面」或表明。代替品代替其餘對象並接收發送到它的消息。
在Objective-C Programming Language中被稱爲「遠程消息」的proxy就是一個代替品。一個proxy涉及到對的管理細節有:forwarding消息到遠程receiver,在鏈接過程時確保參數值是被拷貝的和從新獲取的,等等。可是它也不會嘗試去作更多別的;它不復制遠程object的函數功能只是給遠程object一個本地地址,也就是它能在別的APP中接受消息的地址。
還有些其餘類型的代替品objects。例如,假設,咱們有一個object,它是用來處理大量數據的——可能它建立了複雜的圖片或者從硬盤中的一個文件裏讀取內容。配置這個object多是很費事間的,所以更傾向於懶加載——當真正須要它時或者系統資源是暫時閒置時。同時,爲了APP中其餘objects正常運行,咱們須要爲這個object提供至少一個佔位object。
在這種狀況下,咱們在最初能夠建立不徹底健全的object,代替的爲它建立一個輕量級的對象。這個對象可以獨自處理一些事情,例如請求數據,可是大多數狀況下它只是做爲一個爲大object的佔位,當消息來了,就將消息轉發給它。當代替品的forwardInvocation:方法第一次接收到發往其餘object的消息時,代替品將會先確認那個object是否存在,若是不存在就建立它。全部發往重量級object的消息都通過代理,所以剩下的程序中,代替品和重量級object在使用上是同樣的。
儘管forwarding能夠模擬繼承,NSObject類毫不會混淆這二者。像respondsToSelector:和IsKindOfClass:能夠經過繼承層次使用,卻不能經過forwarding鏈使用。例如,若是Warrior object被查詢是否響應negotiate消息,
if ( [aWarrior respondsToSelector:@selector(negotiate)] ) ...
這個返回值將會是NO,即便它可以接收negotiate消息並不報錯的響應該消息,例如經過forwarding 消息到Diplomat。( 見圖5-1 )
大多數狀況下,正確的答案是NO。可是也可能不是,若是咱們咱們設置一個代替品object來擴展一個類的能力,forwarding機制會像繼承同樣。若是咱們想要想要咱們的objects像真的繼承了(它們轉發消息的目標)對象;咱們須要從新實現respondsToSelector:和isKindOfClass:方法來包含咱們的forwarding規則。
- (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:方法也應該反應出forwarding規則。若是協議是被使用的,conformsToProtocol:方法應該一樣的被添加到重寫的列表中。類似的,若是一個object forwarding任何它接收到的遠程消息,他應該有一個methodSignatureForSelector: ,這可以精確的返回最終響應forwarded消息的描述;例如,若是一個object是有能力將消息forward到他的代替品,咱們應該實現methodSingnatureForSelector:就像如下:
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { NSMethodSignature* signature = [super methodSignatureForSelector:selector]; if (!signature) { signature = [surrogate methodSignatureForSelector:selector]; } return signature; }
咱們應該儘可能吧forwarding規則的代碼一塊兒放在某處,包括forwardInvocation:。
這個高級技巧,僅適合真的沒有別的解決方案時使用。它不是繼承的替代品。若是咱們不得不使用這種技巧,就確保徹底掌握這些運轉規律(作轉發的類和被轉發的類關於forwarding的機制)。
在本節中提到的method能夠查看詳細說明(NSObject )。更多關於invokeWithTarget:,參見NSInvocation類的說明。
爲了協助runtime系統,編譯器將每一個method的返回值類型和參數類型編碼成一個特徵string,並把這個string與method selector結合起來。這個編碼方案在其餘環境也是可用的,而且是公開用於 @encode() 編碼指令。當給定一個type 說名,@encode() 返回一個string 編碼該type;type可使int、一個指針(pointer)、一個帶有標籤的structure或者union、或者一個Class名稱——任何類型,事實上,就是全部可用於做爲C的sizeof()操做的類型。
char *buf1 = @encode(int **); char *buf2 = @encode(struct key); char *buf3 = @encode(Rectangle);
下面的表列出了類型編碼。注意,當爲歸檔和解檔編碼對象時,如下大部分是和編碼一致的。不過,有些在寫編碼器時不能使用的編碼也被列出來了;而且,也有一些編碼不是經過 @encode 生成的。(關於更多歸檔和解檔編碼objects,參見NSCoder 類說明。)
表6-1 OC type encodings
編碼 | 類型 |
---|---|
c | A char |
i | An int |
s | A short |
l | A long 。 l is treated as a 32-bit quantity on 64-bit programs |
q | A long long |
C | An unsigned char |
I | An unsigned int |
S | An unsigned short |
L | An unsigned long |
Q | An unsigned long long |
f | A float |
d | A double |
B | A C++ bool or a C99 _Bool |
v | A void |
* | A character string (char *) |
@ | An object (whether statically typed or typed id) |
# | A class object (Class) |
: | A method selector (SEL) |
[array type] | An array |
{name=type...} | A structure |
(name=type...) | A union |
b (num) | A bit field of num bits |
^ | A pointer to type |
? | An unknown type (among other things, this code is used for function pointers) |
OC不支持long double類型。@encode(long double) 返回 d,就是說和double的編碼一致。
Array的編碼是閉合的中括號;array中的元素在開括號緊接着後面。例如,包含12個指向float的指針編碼以下:
[12^f]
Structures是使用大括號,unions使用小括號;structure的標記(struct)是被列出來的首先,而後是大括號,以及被列在其中的元素。例如:
typedef struct example { id anObject; char *aString; int anInt; } Example;
將會被編碼爲:
{example=@*i}
不管是 @encode() 傳入的是type 名稱(Example)或結構體標記(example),都會獲得相同的編碼。爲結構體指針的編碼以下:
^{example=@*i}
不過,更高階的結構體指針,將不會顯示結構體內在的類型:
^^{example}
Objects 是被視爲結構體,例如將NSObject類名傳入 @encode():
{NSObject=#}
NSObject類只聲明瞭一個示例變量,isa,也就是Class類型的
注意,儘管存在 @encode() 指令不返回值,runtime系統會使用額外的編碼列表,如表6-2,當類型限定符被用於聲明protocol中的methods時。
表6-2 OC method encodings
編碼 | 類型 |
---|---|
r | const |
n | in |
N | inout |
o | out |
O | bycopy |
R | byref |
V | oneway |
當編譯器碰見屬性聲明(參見Objective-C Programming Language 中的聲明的屬性),它生成與閉合的類、分類、Protocol相關聯的描述性的元數據。咱們可以獲取這些元數據經過使用函數,這些函數支持藉助類或Protocol的name查找屬性,獲取屬性的type(就像 @encode 得到的string),拷貝一個由C string構成的數組的property 的attributes。聲明的屬性列表對每一個Class和Protocol都是可獲取的。
Property結構體爲property 描述符號定義一個不透明的操做。
typedef struct objc_property *Property;
咱們可以使用class_copyPropertyList和protocol_copyPropertyList來分別獲取與Class(包括被夾在的分類)以及Protocol相關聯的屬性數組。
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函數查找屬性的name:
const char *property_getName(objc_property_t property)
咱們可使用class_getProperty和protocol_getProperty,藉助一個類中指定的name獲取類或Protocol中的一個property的引用。
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_getAtributes函數獲取一個property的name和 @encode 類型string。更詳細的編碼類型string,參見類型編碼;此string更詳細參見Property Type String和Property Attribute Description。
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的name和 @encode 類型的字符串,以及property的其餘特徵。
String以T開頭,以後跟隨着 @encode type 和一個逗號;最後面跟隨一個V,以後是實例變量的name。在這兩塊之間,會插入下面的描述符,經過逗號間隔:
Table7-1聲明property type 編碼
Code | Meaning |
---|---|
R | The property is read-only (readonly). |
C | The property is a copy of the value last assigned (copy). |
& | The property is a reference to the value last assigned (retain). |
N | The property is non-atomic (nonatomic). |
G
|
The property defines a custom getter selector name. The name follows the G (for example,GcustomGetter,). |
S
|
The property defines a custom setter selector name. The name follows the S (for example, ScustomSetter:,) |
D | The property is dynamic (@dynamic). |
W | The property is a weak reference (__weak). |
P | The property is eligible for garbage collection. |
t
|
Specifies the type using old-style encoding. |
更多實例參見Property Attribute Description。
預先給定這些定義:
enum FooManChu { FOO, MAN, CHU }; struct YorkshireTeaStruct { int pot; char lady; }; typedef struct YorkshireTeaStruct YorkshireTeaStructType; union MoneyUnion { float alone; double down; };
下面的表展現了示例property聲明和property_getAttributes返回的string:
Property 聲明 | Property 說明符 |
---|---|
@property char charDefault; | Tc,VcharDefault |
@property double doubleDefault; | Td,VdoubleDefault |
@property enum FooManChu enumDefault; | Ti,VenumDefault |
@property float floatDefault; | Tf,VfloatDefault |
@property int intDefault; | Ti,VintDefault |
@property long longDefault; | Tl,VlongDefault |
@property short shortDefault; | Ts,VshortDefault |
@property signed signedDefault; | Ti,VsignedDefault |
@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 compiler 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 |
注意,關於(非id)Class類property_getAtributes以下:
@property (nonatomic,strong)NSString * maStingClass; fprintf(stdout, "%s", property_getAttributes(property)); 得到的字符串爲: T@"NSString",&,N,V_maStingClass