Objective-C Runtime Programming Guide

Objective-C Runtime Programming Guidehtml

Runtime Versions and Platforms

Objective C Runtime有兩個不一樣的版本ios

Legacy and Modern Versions

1.0 legacy版本 & 2.0 modern版本, modern版本相較於1.0更爲健壯算法

  • legacy runtime中, 若是改變了某類實例對象變量的內存佈局, 必須從新編譯這個類
  • modern runtime中, 若是改變了某類實例對象變量的內存佈局, 則不須要從新編譯這個類
  • modern runtime中,支持成員變量的屬性合成

Platforms

  • iPhone和OS X v10.5及其之後的64位程序都是用的是modern runtime
  • 其餘(32位的應用程序)使用的是legacy runtime

Interacting with the Runtime

Objective-C程序在三個不一樣的層面與運行時系統進行交互macos

  • Objective-C源碼
  • Foundation框架的NSObject類中定義的方法
  • 運行時方法的直接調用

Objective-C Source Code

在大部分狀況下, 運行時都在背後自動進行工做, 咱們只須要編寫Objective C代碼, 進行編譯便可編程

當編譯包含Objective-C類和方法的代碼時,編譯器會建立實現語言動態特性的數據結構和函數調用數組

  • 數據結構捕獲在類和類別定義以及協議聲明中找到的信息
  • 包括類和協議對象, 方法選擇器, 對象成員變量, 以及從源代碼中提取的其餘信息

NSObject Methods

Cocoa中大多數對象都是NSObject類子類對象, 都繼承了NSObject的方法, 因此其子類對象得到了基礎的行爲操做(方法), 然而, 在某些狀況下, NSObject類僅定義瞭如何完成操做的模板(只定義了接口), 自己並無提供實現的全部代碼緩存

例如NSObject類提供了一個對象方法description用於返回一個字符串, 字符串的內容是對類的描述, 主要用於GDB print-object調試命令, 來輸出字符串, NSObject對該方法的實現不知道繼承自NSObject的子類類包含什麼內容,所以它返回一個帶有對象名稱和地址的字符串, 子類需複寫從新實現已知足本身的打印輸出, 例NSArray實例對象會打印所包含的元素對象數據結構

一些NSObject方法只是使用運行時系統以獲取信息, 例如isKindOfClass:判斷是不是某類或是某類的子類和isMemberOfClass:判斷是不是某類的實例, respondsToSelector:判斷對象可否接收特定的消息, conformsToProtocol:判斷對象是否實現了指定的協議, methodForSelector:返回一個方法實現的地址app

Runtime Functions

  • 運行時系統是一個動態分享庫
  • 由目錄/usr/include/objc頭文件中的一組功能和數據結構組成的公共接口
  • 大多數函數/方法容許使用C語言去替代編寫OC代碼時, 編譯器所作的操做
  • NSObject類提供了一些構建的基礎功能, 使用運行時系統能夠開發可擴展的運行時工具
  • 一般編寫Objective C代碼時不經常使用, 但有時也能提供很強大的功能

Messaging

The objc_msgSend Function

在Objective-C中,消息直到運行時才綁定到方法實現。編譯器將表達式爲[receiver message]這種的消息轉換爲objc_msgSend方法發送消息, objc_msgSend方法首要兩個參數分別爲方法的接收者receiver, 發送消息的方法selector框架

[receiver message] ====> objc_msgSend(receiver, selector)
複製代碼

多類型參數傳遞

objc_msgSend(receiver, selector, arg1, arg2, ...)
複製代碼

消息傳遞功能完成了動態綁定所需的一切

  • 首先找到方法選擇器(找到方法實現), 因爲不一樣的類能夠實現相同的方法, 因此還須要receiver接受者來查找確切的方法選擇器
  • 而後執行查詢例程, 向其傳遞接收對象(指向其數據的指針), 以及爲該方法指定的選擇器
  • 最後,將例程的返回值做爲本身的返回值傳遞

注意 編譯器生成對消息傳遞功能的調用, 編寫代碼的時候不該該直接調用

消息傳遞的關鍵在於編譯器爲每一個類和對象構建的結構, 每一個類的結構都包含這兩個基本要素

  • 一個指向父類的指針
  • 一個類調度表(類的查詢表), 表內存存儲了類其選擇器關聯的方法實現地址, 例如setOrigin::方法的選擇器就關聯了setOrigin::方法的實現地址

一個對象建立了, 內存分配了, 實例變量初始化了, 對象中第一個變量是一個指向其類的指針, 名爲isa的指針, 可以讓對象訪問到它的類, 而後就能訪問到它所繼承的全部類

雖然不是嚴格的語言組成部分,但isa指針是對象與Objective-C運行時系統一塊兒使用的關鍵, 不管結構定義的何種字段, 對象都必須與結構objc_object(在objc/objc.h中定義)"等效", 不須要建立本身的根類, 只要建立NSObject, NSProxy類及其子類對象, 默認還有isa指針

Messaging Framework

當一個消息發送給一個對象後, 消息發送方法會根據對象的isa指針找到對象所屬的類class, 在類的調度表中查找選擇器, 沒有找到的話,objc_msgSend就會根據類的superClass指針向上, 到父類中繼續查找, 若是一直查找失敗的話, objc_msgSend則會一直向上查找直到NSObject類, 一旦查找到選擇器, 就執行方法的調用, 調用在表中selector方法,並將接收對象的數據結構傳遞給該方法

這就是在運行時選擇方法實現的方式---在面向對象編程的術語中,方法是動態綁定到消息的

爲了加速消息查找發送的過程, 運行時系統會緩存方法選擇器, 方法地址, 每一個類都有本身獨立的緩存, 緩存可以包含來自繼承類的選擇器, 在執行搜索類調度表以前, 消息傳遞例程首先檢查接收對象的類的緩存(基於曾經使用過的方法可能會再次使用), 若是方法在緩存中查到了, 消息傳遞僅比函數調用慢一點

一旦程序運行了足夠長的時間以"warm up"其緩存,它發送的幾乎全部消息都將找到一個緩存方法。緩存在程序運行時動態增加以容納新的消息

Using Hidden Arguments

objc_msgSend方法在執行查詢方法實現的過程當中, 查找到實現後, 會把全部參數所有傳遞給方法, 它一樣會傳遞兩個隱藏的參數

  • 方法的接收者receiver
  • 方法選擇器selector

這是全部方法實現都會傳遞的參數, 之因此說它們是"隱藏的", 是由於它們沒有在定義方法的源代碼中聲明, 當代碼進行編譯的時候, 會自動插入這兩個參數

雖然這兩個參數沒有顯示聲明, 在編寫代碼的時候仍然能夠引用它們, 使用self引用消息的接收者receiver, 使用_cmd引用方法選擇器selector

下面的例子中_cmd表明的就是方法strange, self表明的就是消息接收者對象, 接收strange消息

- strange
{
    id  target = getTheReceiver();
    SEL method = getTheMethod();
 
    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}
複製代碼

Getting a Method Address

規避動態綁定的惟一方法是獲取方法的地址並像調用函數同樣直接調用它, 這在少數狀況下會很合適, 由於在這種狀況下, 特定方法將連續執行屢次, 而且但願避免每次執行該方法時消息傳遞的開銷

使用NSObject類中的methodForSelector:方法能夠查詢一個指向方法實現的指針, 而後用指針執行方法

methodForSelector:方法返回的指針必須仔細轉換爲正確的函數類型, 返回和參數類型都應包含在強制類型轉換中

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:規避動態綁定可節省消息傳遞所需的大部分時間, 只有在重複屢次特定消息的狀況下節省的開銷纔是可觀的

注意 methodForSelector:由Cocoa的運行時系統提供, 可是它並非Objective C語言的特性

Dynamic Method Resolution

Dynamic Method Resolution

某些狀況下, 您可能但願動態提供方法的實現, 例如在Objective C語言中, 使用@dynamic聲明的屬性, 告訴編譯器, 屬性關聯的方法(setter, getter)方法將本身實現

@dynamic propertyName;
複製代碼

可使用resolveInstanceMethod:resolveClassMethod:動態地爲實例方法和類方法的給定選擇器提供實現

可使用class_addMethod方法給類添加方法

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}

@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
複製代碼

轉發方法(如Message Forwarding中所述)和動態方法解析在很大程度上是正交的, 類有機會在轉發機制啓動以前動態解析方法, 若是調用responsesToSelector:或instanceRespondToSelector:,則動態方法解析器將有機會首先爲選擇器提供IMP, 若是實現resolveInstanceMethod:但但願經過轉發機制實際轉發特定的選擇器則對這些選擇器返回NO

動態方法解析示例

出處: Effective Objective C 2.0 - Matt Galloway

實現@dynamic屬性

@interface EOCAutoDictionary : NSObject

@property (copy, nonatomic) NSString *string;

@property (strong, nonatomic) NSNumber *number;

@property (strong, nonatomic) NSDate *date;

@property (strong, nonatomic) id opaqueObject;

@end

#import "EOCAutoDictionary.h"
#import <objc/runtime.h>

@interface EOCAutoDictionary ()

@property (strong, nonatomic) NSMutableDictionary *backingStore;

@end

@implementation EOCAutoDictionary

@dynamic string, number, date, opaqueObject;

id autoDictionaryGetter(id self, SEL _cmd);
void autoDictionarySetter(id self, SEL _cmd, id value);

- (instancetype)init {
    if (self = [super init]) {
        _backingStore = NSMutableDictionary.new;
    }
    return self;
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
// if (/* selector from dynamic property */) {
        NSString *selectorString = NSStringFromSelector(sel);
        if ([selectorString hasPrefix:@"set"]) {
            class_addMethod(self, sel, (IMP)autoDictionarySetter, "v@:@");
        } else  {
            class_addMethod(self, sel, (IMP)autoDictionaryGetter, "@@:");
        }
        return YES;
// }
// return [super resolveInstanceMethod:sel];
}

id autoDictionaryGetter(id self, SEL _cmd) {
    // Get the backing store from the object
    EOCAutoDictionary *typeSelf = (EOCAutoDictionary *)self;
    NSMutableDictionary *backingStore = typeSelf.backingStore;
    
    // The key is simply the selector name
    NSString *key = NSStringFromSelector(_cmd);
    
    // Return the value
    return [backingStore objectForKey:key];
}

void autoDictionarySetter(id self, SEL _cmd, id value) {
    // Get the backing store from the object
    EOCAutoDictionary *typeSelf = (EOCAutoDictionary *)self;
    NSMutableDictionary *backingStore = typeSelf.backingStore;
    
    NSString *selectorString = NSStringFromSelector(_cmd);
    NSMutableString *key = selectorString.mutableCopy;
    
    // Remove the ":" at the end
    [key deleteCharactersInRange:NSMakeRange(key.length - 1, 1)];
    
    // Remove the 'set' prefix
    [key deleteCharactersInRange:NSMakeRange(0, 3)];
    
    // Lowercase the first character
    NSString *lowercaseFirstChar = [key substringToIndex:1].lowercaseString;
    [key replaceCharactersInRange:NSMakeRange(0, 1) withString:lowercaseFirstChar];
    
    if (value) {
        [backingStore setObject:value forKey:key];
    } else {
        [backingStore removeObjectForKey:key];
    }
}

@end
複製代碼

Dynamic Loading

一個Objective C程序在運行的時候能夠加載和連接新的類和分類, 新代碼已合併到程序中, 並與開始時加載的類和分類徹底相同

動態加載能夠用來作不少不一樣的事情. 例如, 系統偏好設置應用程序中的各個模塊是動態加載的

在Cocoa環境中, 動態加載一般用於容許自定義應用程序, 程序運行時階段能夠加載他人編寫的模塊, 就像Interface Builder加載自定義選項板和OS X System Preferences應用程序加載自定義首選項模塊同樣, 可加載模塊擴展了應用程序的功能

儘管有一個運行時函數能夠在Mach-O文件中執行Objective-C模塊的動態加載(objc_loadModules,在objc/ objc-load.h中定義), 但Cocoa的NSBundle類爲動態加載提供了明顯更方便的接口, 更加面向對象和集成化

Message Forwarding

向不處理該消息的對象發送消息是錯誤的, 可是, 在返回錯誤以前, 運行時系統會給接收對象第二次處理消息的機會

Forwarding

若是將消息發送給不處理該消息的對象, 返回錯誤以前運行時向該對象發送一個forwardInvocation消息, 其中NSInvocation對象做爲其惟一參數, NSInvocation對象封裝了原始消息以及與之一塊兒傳遞的參數

能夠實現forwardInvocation:方法, 以對消息提供默認響應或以其餘方式避免錯誤, 顧名思義,forwardInvocation:一般用於將消息轉發到另外一個對象

若要查看轉發的範圍和意圖請設想如下情形:假設首先正在設計一個對象, 該對象能夠響應稱爲協商negotiate的消息, 而且但願其響應包括另外一種對象的響應, 能夠簡單實如今消息negotiate內部, 讓其餘對象來響應自身的negotiate消息, 進一步進行此操做, 並假但願對象對negotiate消息的響應正是在另外一個類中實現的響應, 實現此目的的一種方法是使當前類從另外一類繼承該方法, 然而這種方式並非合適的, 由於實現negotiate消息的類可能來自不一樣的繼承樹

即便當前的類沒法繼承negotiate方法, 仍然能夠經過實現該方法的版原本"借用"該方法, 該方法將消息簡單地傳遞到另外一個類的實例

- (id)negotiate
{
    if ( [someOtherObject respondsTo:@selector(negotiate)] )
        return [someOtherObject negotiate];
    return self;
}
複製代碼

這種方式可能會有點麻煩, 尤爲是一個對象向另外一個對象傳遞大量的消息, 必須實現一個方法來涵蓋想從另外一類中借用的全部方法, 在編寫代碼時, 可能有不知道的case, 會形成覆蓋不全, 該集合可能取決於運行時的事件, 而且可能隨着未來實現新方法和類而改變

相較於靜態方法, 動態方法forwardInvocation:消息提供的第二次機會提供了針對此問題的解決方案, 它的工做方式以下:當對象因爲沒有與消息中的選擇器匹配的方法而沒法響應消息時,運行時系統會經過向它發送forwardInvocation:消息來通知對象, 每一個NSObject對象都繼承了forwardInvocation:方法, 若是未實現此方法, 未經歷消息轉發過程, 最終則會調用doesNotRecognizeSelector:拋出異常, 經過覆蓋NSObject的版本並實現本身的版本, 能夠利用forwardInvocation:消息提供的將消息轉發到其餘對象的機會

腹瀉forwardInvocation:方法要注意兩點

  • 肯定消息應該去哪裏
  • 將其原始參數發送到新的去處

消息能夠經過方法invokeWithTarget:發送

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:[anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}
複製代碼

最終消息的返回值會返回到初始的消息發送者那裏, 全部類型的返回值均可以傳遞給發送者, 包括id類型的對象, 結構和雙精度浮點數等

forwardInvocation:方法能夠充當未識別消息的分發中心, 將其打包到其餘接收者, 也能夠是將全部消息發送到同一目的地的中轉站, 能夠將消息轉化爲另外一種消息, 又或是將消息內化, 以致於沒有響應和沒有錯誤, forwardInvocation:方法還能夠將多個消息合併爲一個響應, 它提供了在轉發鏈中連接對象的機會, 爲程序設計開闢了可能性

注意只有當receiver沒有實現響應消息選擇器, 纔會執行forwardInvocation:的處理,

Forwarding and Multiple Inheritance

轉發和多重繼承

轉發能夠模擬繼承, 可實現多重繼承的某些效果

Forwarding

在上圖中,Warrior類的實例將negotiate消息轉發到Diplomat類的實例, 戰士看起來像外交官同樣進行談判, 它彷佛對談判的消息作出了迴應, 而且出於全部實際目的, 它的確作出了迴應(儘管其實是外交官在從事這項工做)

所以, 轉發消息的對象從繼承層次結構的兩個分支(其本身的分支以及響應消息的對象的分支)"繼承"了方法, 在上面的示例中, Warrior相似乎繼承自Diplomat及其本身的父類

轉發提供了一般須要多重繼承的大多數功能, 可是, 二者之間有一個重要的區別:多重繼承是在單個對象中結合了不一樣的功能, 它傾向於大型, 多面的物體, 而轉發將不一樣的職責分配給不一樣的對象, 它將問題分解爲較小的對象, 以消息的形式發送給這些對象

Surrogate Objects

轉發不只模擬多重繼承, 能夠建立更爲輕量級的對象響應更多對象方法, 代理另外一個對象,並向其發送消息

The Objective-C Programming Language Remote Messaging中討論的proxy就是這樣的代理, proxy負責管理將消息轉發到遠程接收者的全部細節, 確保鏈接和複製參數值等等, 此外就不作其餘過多的事情, 它不能複製遠程對象的功能, 只是給遠程對象一個本地地址, 一個能夠在另外一個應用程序中接收消息的地址

其餘種類的替代對象也是可能的, 例如, 有一個能夠處理大量數據的對象, 它也許會建立一個複雜的圖像或讀取磁盤上文件的內容, 設置該對象可能很耗時, 所以確實須要時或系統資源暫時空閒時進行懶加載操做, 同時, 該對象至少須要一個佔位符, 以使應用程序中的其餘對象正常運行

在這種狀況下, 一開始能夠不用建立的完整的對象, 而是輕量級的替代對象, 當任務發生的時候, 將消息傳遞給這個對象, 當代理對象的forwardInvocation:方法首先收到發往另外一個對象的消息時, 它將確保該對象存在, 若是不存在則將建立該對象, 較大對象的全部消息都經過代理, 所以就程序的其他部分而言, 代理和較大對象的做用將是相同的

Forwarding and Inheritance

儘管轉發模仿繼承, 但NSObject類從不會混淆二者, 諸如responsToSelector:isKindOfClass:之類的方法僅查看繼承層次結構, 例如, 詢問Warrior對象是否響應negotiate消息

if ([aWarrior respondsToSelector:@selector(negotiate)])
    ...
複製代碼

Warrior實例對象雖然將消息negotiate轉發給了Diplomat實例對象, 沒有錯誤而且響應了消息, 但[aWarrior respondsToSelector:@selector(negotiate)]結果是NO, Warrior類中的派發表中並無negotiate選擇器的地址

在許多狀況下, 否(NO)是正確的答案. 但事實並不是全都如此, 若是使用轉發來設置代理對象或擴展類的功能, 則轉發機制應該與繼承同樣透明, 若是但願目標對象像它們真正繼承了轉發消息的對象的行爲同樣工做, 則須要從新實現responsToSelector: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;
}
複製代碼

除了responsesToSelector:isKindOfClass:外, instancesRespondToSelector:方法也要針對消息轉發作相應的修改, 若是還遵循的有協議, conformsToProtocol:方法也是同樣的要作處理

若是對象轉發了它收到的任何遠程消息, 則它應該具備methodSignatureForSelector的版本, 該版本能夠返回對最終響應所轉發消息的方法的準確描述, 實現forwardInvocation:方法前必定要先實現methodSignatureForSelector:方法, 方法返回了NSMethodSignature類的對象纔會進入forwardInvocation:方法實現

例如, 若是對象可以將消息轉發到其代理則能夠像以下實現methodSignatureForSelector:方法

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
    NSMethodSignature* signature = [super methodSignatureForSelector:selector];
    if (!signature) {
       signature = [surrogate methodSignatureForSelector:selector];
    }
    return signature;
}
複製代碼

注意 消息轉發是一項先進的技術, 僅適用於沒法解決其餘問題的狀況, 它的目標也不是打算來取代繼承, 若是必須使用到此技術, 請確保徹底瞭解進行轉發的類和要轉發到的類的行爲

實例方法轉發流程

// 未查找到選擇器對應方法地址時, 給一次動態添加方法的機會
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}

// resolveInstanceMethod返回NO了進入, 改變方法的接收者
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return nil;
}

// forwardingTargetForSelector也返回nil, 則進入完整的消息轉發 獲取轉發對象響應方法的簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return nil;
}

// methodSignatureForSelector返回有值進入forwardInvocation
/** 能夠改變消息的接收者target -- `NSInvocation - (void)invokeWithTarget:(id)target` 或者改變響應方法的參數 -- `NSInvocation - (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx` 改變返回值 -- `NSInvocation - (void)setReturnValue:(void *)retLoc` 也能夠實現其餘`anInvocation`或者不響應, 總之自由度比較高 */
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"target: %@, selector: %@", anInvocation.target, NSStringFromSelector(anInvocation.selector));
}

// 沒有進入消息轉發流程methodSignatureForSelector方法返回nil則進入, 而後拋出異常
- (void)doesNotRecognizeSelector:(SEL)aSelector {
    
}
複製代碼

類方法轉發流程

+ (BOOL)resolveClassMethod:(SEL)sel {
    return NO;
}

+ (id)forwardingTargetForSelector:(SEL)aSelector {
    return nil;
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"target: %@, selector: %@", anInvocation.target, NSStringFromSelector(anInvocation.selector));
}

+ (void)doesNotRecognizeSelector:(SEL)aSelector {
    
}
複製代碼

Type Encodings

爲了幫助運行時系統, 編譯器將每一個方法的返回和參數類型編碼爲字符串, 並將該字符串與方法選擇器關聯

它使用的編碼方案在其餘上下文中也頗有用,所以能夠經過@encode()編譯器指令直接使用, 在給定類型說明後,@encode()返回對該類型進行編碼的字符串, 該類型能夠是基本類型例如int, 指針, 帶標籤的結構體或聯合體或類名-實際上能夠作用於C語言sizeof()運算符的參數的任何類型

char *buf1 = @encode(int **);
char *buf2 = @encode(struct key);
char *buf3 = @encode(Rectangle);
複製代碼

下表列出了類型編碼, 注意它們中的許多編碼發生在對象進行歸檔或分發, 可是一下列出的類型編碼在自行編寫編碼器的時候並不能直接使用

Objective-C type encodings

Code Meaning
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
bnum A bit field of num bits
^type A pointer to type
? An unknown type (among other things, this code is used for function pointers)

重要 Objective-C不支持long double類型, @encode(long double)返回d, 它與double的編碼相同

數組的類型使用方括號表示, 舉例包含12個浮點數的指針的數組類型編碼表示以下

[12^f]
複製代碼

結構體使用大括號表示, 例如結構體Example

typedef struct example {
    id   anObject;
    char *aString;
    int  anInt;
} Example;
複製代碼

結構體Example的類型編碼爲(@encode()後)

{example=@*i}
複製代碼

Example結構體指針的類型編碼則爲

^{example=@*i}
複製代碼

可是,另外一種間​​接訪問級別刪除了內部類型規範

^^{example}
複製代碼

NSObject類的類型編碼, 對象被看成結構對待, NSObject類僅聲明一個Class類型的實例變量isa

{NSObject=#}
複製代碼

在協議申明聲明的方法中, 運行時系統會用到特定的類型編碼, 以下

Objective-C method encodings

Code Meaning
r const
n in
N inout
o out
O bycopy
R byref
V oneway

NSInvocation.h中的枚舉

enum _NSObjCValueType {
    NSObjCNoType = 0,
    NSObjCVoidType = 'v',
    NSObjCCharType = 'c',
    NSObjCShortType = 's',
    NSObjCLongType = 'l',
    NSObjCLonglongType = 'q',
    NSObjCFloatType = 'f',
    NSObjCDoubleType = 'd',
    NSObjCBoolType = 'B',
    NSObjCSelectorType = ':',
    NSObjCObjectType = '@',
    NSObjCStructType = '{',
    NSObjCPointerType = '^',
    NSObjCStringType = '*',
    NSObjCArrayType = '[',
    NSObjCUnionType = '(',
    NSObjCBitfield = 'b'
} API_DEPRECATED("Not supported", macos(10.0,10.5), ios(2.0,2.0), watchos(2.0,2.0), tvos(9.0,9.0));

複製代碼

Declared Properties

當編譯器遇到屬性聲明時(詳情查看The Objective-C Programming Language Table of Contents中的Declared Properties部分), 編譯器會生成與封閉類、分類或協議關聯的描述性元數據, 可使用函數訪問此元數據,經過將屬性類型做爲@encode字符串以及將屬性的屬性列表複製爲C字符串數組, 這些函數能夠作到支持按類或協議上的名稱查找屬性, 每一個類和協議都有聲明的屬性列表

Property Type and Functions

屬性結構體定義屬性描述符的不透明句柄

typedef struct objc_property *Property;
複製代碼

獲取類和協議的屬性列表

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);
複製代碼

能夠獲取屬性名稱

const char *property_getName(objc_property_t 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)
複製代碼

查詢屬性@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 Type String

property_getAttributes方法能夠查詢屬性的名稱, @encode類型編碼字符串, 以及屬性的其餘屬性

字符串以T開頭後跟@encode類型和逗號,最後以V開頭而後是實例變量的名稱. 屬性由如下指定描述符組成用逗號分隔

Declared property type encodings

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 Examples

特定用例定義

enum FooManChu { FOO, MAN, CHU };
struct YorkshireTeaStruct { int pot; char lady; };
typedef struct YorkshireTeaStruct YorkshireTeaStructType;
union MoneyUnion { float alone; double down; };
複製代碼

經過property_getAttributes:獲取屬性查詢的信息樣例

Property declaration Property description
@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

理解若有錯誤 望指正 轉載請說明出處

相關文章
相關標籤/搜索