Objective-C Runtime 文檔翻譯

前言

 

若是讀到感受不理解、晦澀的地方,或者想要交流的能夠聯繫我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

 

Runtime 版本和平臺

 

在不一樣的平臺,有不一樣版本的OC runtime。ide

 

舊的和如今的版本

 

有兩個版本的OC runtime——「舊版」和「如今版」。如今版就是OC-2.0幷包含了許多新特性。舊版本的runtime的編程接口就是OC-1;如今版本的runtime所有接口參見 Objective-C Runtime Reference
最值得注意的新特性是,如今版本的實例變量是「不脆弱的」:函數

  • 在舊版本runtime,若是咱們改變一個類的實例變量的佈局,咱們必須從新編譯全部繼承自它的類。
  • 在如今版本runtime,若是咱們改變一個類的實例變量的佈局,咱們不須要從新編譯全部繼承自它的類。

另外,如今版本的runtime支持爲聲明的屬性作實例變量的synthesis(參見Objective-C Programming Language)。工具

 

平臺

 

iPhone應用和OSX 10.5版本的64-位編程使用如今版本的runtime。佈局

 

與runtime的相互做用

 

OC編程和runtime系統的相互做用,能夠分三個不一樣的標準:

  • 經過OC代碼。
  • 經過在Foundation framework 的 NSObject類中定義方法。
  • 經過直接調用runtime 函數。

 

OC代碼

 

這是最重要的一部分,runtime 系統在該場景背後自動運行。咱們僅僅經過寫和編譯OC代碼就可使用runtime系統。
當編譯包含OC類和方法的代碼時,編譯器就會建立數據結構和(實現了語言動態特徵)函數。數據結構可以捕獲有Class和category以及protocol中聲明的信息;它們包含了Class和Protocol(在Objective-C Programming Language 中定義的Class和Protocol,還有方法selectors、實例變量以及其餘從源碼中提取到的信息)。主要的runtime功能就是發送消息,參見 Messaging ,它也會被OC代碼消息表達式調用。

 

NSObject Methods

 

許多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:,用於提供方法實現體的地址。這些對象自己都是校驗性的能力的方法。

 

Runtime Functions

 

運行時系統是動態共享庫,而且頭文件中(文件路徑/usr/include/objc)有一系列函數和數據結構接口聲明;其中大部分函數容許咱們使用基本的C來複制那些編譯器的實現(同咱們以OC代碼編譯後的代碼)。其餘基礎功能能夠經過NSObject得到。這些功能可以讓咱們爲runtime 系統開發其接口和工具,以便提升開發效率;在使用OC編程時也能夠不使用runtime接口。不過,當用OC編寫程序時,有些runtime函數功能在某些場合是很是有用的。全部runtime函數聲明可參見Objective-C Runtime Reference

 

消息機制

 

本節講述消息表達式是如何轉換爲objc_msgSend函數調用的,和如何經過name查找方法;而後會解釋咱們如何充分利用objc_msgSend,和如何避免動態綁定(若是有須要)。

 

objc_msgSend 函數(消息函數)

 

OC中,在運行時前,消息是不能肯定方法的實現體地址的。編譯器轉換消息表達式,

[receiver message]

轉換成消息函數,objc_msgSend。這個函數須要該消息表達式中的接收者(receiver)和方法的名字——方法的selector做爲第二個主要的參數:

objc_msgSend(receiver, selector)

任何傳入消息的參數都和一經過objc_msgSend處理:

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

消息函數爲動態綁定完成全部所須要的事情:

  • 首先找到方法實現 (method implementation) ,也就是selector所指向的地址。因爲相同的方法在不一樣的類對應的方法實現也是不一樣的,因此準確的實現體的得到取決於reciver的類型。
  • 而後調用方法實現,將其(selector)傳遞給receiver (指向它的數據的指針) ,和該方法須要的參數。
  • 最終,返回函數的返回值。

注意:編譯器生成消息函數的調用。咱們永遠不要在本身寫的代碼中直接調用它(PS:文檔中此處所說的注意在現實中,貌似只起到了提醒你們要確保消息發送正確的做用,慎重使用)。

消息發送的核心在於編譯器爲每一個類個對象建立的結構體,每一個類結構體包含兩個基本的要素:

  • 一個指向superclass的指針。
  • 一個類派發表。這個表包含全部的(該類所指定的方法的)相關的selectors。能夠說,setOrigin::方法的selector就是(程序中的實現體)setOrigin::的地址,display方法和display的地址相關聯,等等。
    當一個新的對象被建立時,就會申請(alloca)內存,它的實例對象會被初始化(initialized)。首先,在對象的變量中有一個指向它自身類結構體的指針,被稱爲isa,經過它可以讓對象找到所屬的類,經過所屬的類能夠查找全部該對象繼承過程的類。

注意:該語言有不嚴謹的一部分,isa指針是對象關聯OC運行時系統所必須的。一個對象須要「等價」於一個結構體 objc_object (在objc/objc.h中定義的) ,包含全部該結構體中的份量。然而,咱們不多建立咱們本身的根類(root object),繼承自NSObject或者NSProxy的對象會自動具有isa變量。

Class和對象結構體擁有的基本元素如圖3-1所示:

圖3-1 Messaging Framework

當一個消息被髮送到某對象,這個消息函數查找指向該類結構體的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的實現體時,他就會調用實現體,並將消息中的全部參數傳遞給他,其中也包含如下兩個參數:

  • receiver 對象。
  • method的selector。

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須要連續的調用屢次,而且咱們想要避免以前每次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的原理

 

有時候咱們須要爲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 文件格式參考資料。

 

消息轉發(Message Forwarding)

 

將消息發送給一個對象,而且對象沒有處理這個消息,就會產生一個錯誤。可是,在宣告錯誤以前,運行時系統給receiver 對象第二次機會去處理消息。

 

轉發(Forwarding)

 

若是將消息發送給一個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,看起來像是借或者「繼承」了另一個類的方法實現。

圖5-1 Messaging Framework

在這個插圖中,一個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和繼承

 

儘管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類的說明。

 

類型編碼(Type Encodings)

 

爲了協助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 Type 和函數

 

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 StringProperty 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 Type String

 

咱們可使用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

 

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
相關文章
相關標籤/搜索