Objective-C Runtime Programming Guidehtml
Objective C Runtime有兩個不一樣的版本ios
1.0 legacy版本 & 2.0 modern版本, modern版本相較於1.0更爲健壯算法
legacy runtime
中, 若是改變了某類實例對象變量的內存佈局, 必須從新編譯這個類modern runtime
中, 若是改變了某類實例對象變量的內存佈局, 則不須要從新編譯這個類modern runtime
中,支持成員變量的屬性合成modern runtime
legacy runtime
Objective-C程序在三個不一樣的層面與運行時系統進行交互macos
在大部分狀況下, 運行時都在背後自動進行工做, 咱們只須要編寫Objective C代碼, 進行編譯便可編程
當編譯包含Objective-C類和方法的代碼時,編譯器會建立實現語言動態特性的數據結構和函數調用數組
Cocoa中大多數對象都是NSObject
類子類對象, 都繼承了NSObject
的方法, 因此其子類對象得到了基礎的行爲操做
(方法), 然而, 在某些狀況下, NSObject
類僅定義瞭如何完成操做的模板(只定義了接口), 自己並無提供實現的全部代碼緩存
例如NSObject
類提供了一個對象方法description
用於返回一個字符串, 字符串的內容是對類的描述, 主要用於GDB print-object
調試命令, 來輸出字符串, NSObject
對該方法的實現不知道繼承自NSObject的子類類包含什麼內容,所以它返回一個帶有對象名稱和地址的字符串, 子類需複寫從新實現已知足本身的打印輸出, 例NSArray
實例對象會打印所包含的元素對象數據結構
一些NSObject
方法只是使用運行時系統以獲取信息, 例如isKindOfClass:
判斷是不是某類或是某類的子類和isMemberOfClass:
判斷是不是某類的實例, respondsToSelector:
判斷對象可否接收特定的消息, conformsToProtocol:
判斷對象是否實現了指定的協議, methodForSelector:
返回一個方法實現的地址app
/usr/include/objc
頭文件中的一組功能和數據結構組成的公共接口NSObject
類提供了一些構建的基礎功能, 使用運行時系統能夠開發可擴展的運行時工具在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
指針
當一個消息發送給一個對象後, 消息發送方法會根據對象的isa
指針找到對象所屬的類class
, 在類的調度表中查找選擇器, 沒有找到的話,objc_msgSend
就會根據類的superClass
指針向上, 到父類中繼續查找, 若是一直查找失敗的話, objc_msgSend
則會一直向上查找直到NSObject
類, 一旦查找到選擇器, 就執行方法的調用, 調用在表中selector
方法,並將接收對象的數據結構傳遞給該方法
這就是在運行時選擇方法實現的方式---在面向對象編程的術語中,方法是動態綁定到消息的
爲了加速消息查找發送的過程, 運行時系統會緩存方法選擇器, 方法地址, 每一個類都有本身獨立的緩存, 緩存可以包含來自繼承類的選擇器, 在執行搜索類調度表以前, 消息傳遞例程首先檢查接收對象的類的緩存(基於曾經使用過的方法可能會再次使用), 若是方法在緩存中查到了, 消息傳遞僅比函數調用慢一點
一旦程序運行了足夠長的時間以"warm up"
其緩存,它發送的幾乎全部消息都將找到一個緩存方法。緩存在程序運行時動態增加以容納新的消息
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];
}
複製代碼
規避動態綁定的惟一方法是獲取方法的地址並像調用函數同樣直接調用它, 這在少數狀況下會很合適, 由於在這種狀況下, 特定方法將連續執行屢次, 而且但願避免每次執行該方法時消息傳遞的開銷
使用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語言的特性
某些狀況下, 您可能但願動態提供方法的實現, 例如在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
複製代碼
一個Objective C程序在運行的時候能夠加載和連接新的類和分類, 新代碼已合併到程序中, 並與開始時加載的類和分類徹底相同
動態加載能夠用來作不少不一樣的事情. 例如, 系統偏好設置應用程序中的各個模塊是動態加載的
在Cocoa環境中, 動態加載一般用於容許自定義應用程序, 程序運行時階段能夠加載他人編寫的模塊, 就像Interface Builder
加載自定義選項板和OS X System Preferences
應用程序加載自定義首選項模塊同樣, 可加載模塊擴展了應用程序的功能
儘管有一個運行時函數能夠在Mach-O
文件中執行Objective-C模塊的動態加載(objc_loadModules
,在objc/ objc-load.h
中定義), 但Cocoa的NSBundle
類爲動態加載提供了明顯更方便的接口, 更加面向對象和集成化
向不處理該消息的對象發送消息是錯誤的, 可是, 在返回錯誤以前, 運行時系統會給接收對象第二次處理消息的機會
若是將消息發送給不處理該消息的對象, 返回錯誤以前運行時向該對象發送一個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:
的處理,
轉發和多重繼承
轉發能夠模擬繼承, 可實現多重繼承的某些效果
在上圖中,Warrior
類的實例將negotiate
消息轉發到Diplomat
類的實例, 戰士看起來像外交官同樣進行談判, 它彷佛對談判的消息作出了迴應, 而且出於全部實際目的, 它的確作出了迴應(儘管其實是外交官在從事這項工做)
所以, 轉發消息的對象從繼承層次結構的兩個分支(其本身的分支以及響應消息的對象的分支)"繼承"了方法, 在上面的示例中, Warrior
相似乎繼承自Diplomat
及其本身的父類
轉發提供了一般須要多重繼承的大多數功能, 可是, 二者之間有一個重要的區別:多重繼承是在單個對象中結合了不一樣的功能, 它傾向於大型, 多面的物體, 而轉發將不一樣的職責分配給不一樣的對象, 它將問題分解爲較小的對象, 以消息的形式發送給這些對象
轉發不只模擬多重繼承, 能夠建立更爲輕量級的對象響應更多對象方法, 代理另外一個對象,並向其發送消息
在 The Objective-C Programming Language 的Remote Messaging
中討論的proxy
就是這樣的代理, proxy
負責管理將消息轉發到遠程接收者的全部細節, 確保鏈接和複製參數值等等, 此外就不作其餘過多的事情, 它不能複製遠程對象的功能, 只是給遠程對象一個本地地址, 一個能夠在另外一個應用程序中接收消息的地址
其餘種類的替代對象也是可能的, 例如, 有一個能夠處理大量數據的對象, 它也許會建立一個複雜的圖像或讀取磁盤上文件的內容, 設置該對象可能很耗時, 所以確實須要時或系統資源暫時空閒時進行懶加載操做, 同時, 該對象至少須要一個佔位符, 以使應用程序中的其餘對象正常運行
在這種狀況下, 一開始能夠不用建立的完整的對象, 而是輕量級的替代對象, 當任務發生的時候, 將消息傳遞給這個對象, 當代理對象的forwardInvocation:
方法首先收到發往另外一個對象的消息時, 它將確保該對象存在, 若是不存在則將建立該對象, 較大對象的全部消息都經過代理, 所以就程序的其他部分而言, 代理和較大對象的做用將是相同的
儘管轉發模仿繼承, 但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 {
}
複製代碼
爲了幫助運行時系統, 編譯器將每一個方法的返回和參數類型編碼爲字符串, 並將該字符串與方法選擇器關聯
它使用的編碼方案在其餘上下文中也頗有用,所以能夠經過@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));
複製代碼
當編譯器遇到屬性聲明時(詳情查看The Objective-C Programming Language Table of Contents中的Declared Properties部分), 編譯器會生成與封閉類、分類或協議關聯的描述性元數據, 可使用函數訪問此元數據,經過將屬性類型做爲@encode
字符串以及將屬性的屬性列表複製爲C字符串數組, 這些函數能夠作到支持按類或協議上的名稱查找屬性, 每一個類和協議都有聲明的屬性列表
屬性結構體定義屬性描述符的不透明句柄
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_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 |
理解若有錯誤 望指正 轉載請說明出處