上篇介紹了Runtime類和對象的相關知識點,在4.5和4.6小節,也介紹了成員變量和屬性的一些方法應用。本篇將討論實現細節的相關內容。html
在討論以前,咱們先來介紹一個很冷僻但又頗有用的一個關鍵字:@encode數組
爲了協助運行時系統,編譯器用字符串爲每一個方法的返回值、參數類型和方法選擇器編碼,使用的編碼方案在其餘狀況下也頗有用。在 Objective-C 運行時的消息發送機制中,傳遞參數時,因爲類型信息的缺失,須要類型編碼進行輔助以保證類型信息也可以被傳遞。在實際的應用開發中,使用案例比較少:某些 API 中 Apple 建議使用 NSValue
的 valueWithBytes:objCType:
來獲取值 (好比 CIAffineClamp
的文檔裏) ,這時的 objCType
就須要類型的編碼值;另外就是在類型信息丟失時咱們可能須要用到這個特性。在上篇的函數介紹過程當中,有幾個函數用到了類型編碼,types參數須要用 Objective-C 的編譯器指令 @encode() 來建立,@encode() 返回的是 Objective-C 類型編碼,這是一種內部表示的字符串(例如,@encode(int)
→ i
),相似於 ANSI C 的 typeof
操做,蘋果的 Objective-C 運行時庫內部利用類型編碼幫助加快消息分發。app
//添加方法 OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); //替代方法的實現 OBJC_EXPORT IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); //添加成員變量 OBJC_EXPORT BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); //爲協議添加方法 OBJC_EXPORT void protocol_addMethodDescription(Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0);
那麼經常使用的類型編碼都有哪些呢?ide
NSLog(@"基本數據類型:"); NSLog(@"short \t\t %s", @encode(short)); NSLog(@"int \t\t %s", @encode(int)); NSLog(@"long \t\t %s", @encode(long)); NSLog(@"ong long \t\t %s", @encode(long long)); NSLog(@"float \t\t %s", @encode(float)); NSLog(@"double \t\t %s", @encode(double)); NSLog(@"char \t\t %s", @encode(char)); NSLog(@"\n"); NSLog(@"指針和數組類型:"); NSLog(@"int * \t\t %s", @encode(int *)); NSLog(@"int ** \t\t %s", @encode(int **)); NSLog(@"int *** \t\t %s", @encode(int ***)); NSLog(@"int [] \t\t %s", @encode(int [])); NSLog(@"int [2] \t\t %s", @encode(int [2])); NSLog(@"int [][3] \t\t %s", @encode(int [][3])); NSLog(@"int [3][3] \t\t %s", @encode(int [3][3])); NSLog(@"int [][4][4] \t\t %s", @encode(int [][4][4])); NSLog(@"int [4][4][4] \t\t %s", @encode(int [4][4][4])); NSLog(@"\n"); NSLog(@"空類型:"); NSLog(@"void \t\t %s", @encode(void)); NSLog(@"void * \t\t %s", @encode(void *)); NSLog(@"void ** \t\t %s", @encode(void **)); NSLog(@"void *** \t\t %s", @encode(void ***)); NSLog(@"\n"); NSLog(@"結構體類型:"); struct Person { char *anme; int age; char *birthday; }; NSLog(@"struct Person \t\t %s", @encode(struct Person)); NSLog(@"CGPoint \t\t %s", @encode(CGPoint)); NSLog(@"CGRect \t\t %s", @encode(CGRect)); NSLog(@"\n"); NSLog(@"OC類型:"); NSLog(@"BOOL \t\t %s", @encode(BOOL)); NSLog(@"SEL \t\t %s", @encode(SEL)); NSLog(@"id \t\t %s", @encode(id)); NSLog(@"Class \t\t %s", @encode(Class)); NSLog(@"Class * \t\t %s", @encode(Class *)); NSLog(@"NSObject class \t\t %s", @encode(typeof([NSObject class]))); NSLog(@"[NSObject class] * \t\t %s", @encode(typeof([NSObject class]) *)); NSLog(@"NSObject \t\t %s", @encode(NSObject)); NSLog(@"NSObject * \t\t %s", @encode(NSObject *)); NSLog(@"NSArray \t\t %s", @encode(NSArray)); NSLog(@"NSArray * \t\t %s", @encode(NSArray *)); NSLog(@"NSMutableArray \t\t %s", @encode(NSMutableArray)); NSLog(@"NSMutableArray * \t\t %s", @encode(NSMutableArray *)); NSLog(@"UIView \t\t %s", @encode(UIView)); NSLog(@"UIView * \t\t %s", @encode(UIView *)); NSLog(@"UIImage \t\t %s", @encode(UIImage)); NSLog(@"UIImage * \t\t %s", @encode(UIImage *));
打印結果:函數
其餘類型可參考Type Encoding,在此不細說。ui
對於屬性而言,還會有一些特殊的類型編碼,以代表屬性是隻讀、拷貝、retain等等,詳情能夠參考Property Type String。編碼
Ivar是表示實例變量的類型,其實際是指向objc_ivar結構體的指針,其定義以下:atom
typedef struct objc_ivar *Ivar; struct objc_ivar { char *ivar_name OBJC2_UNAVAILABLE; //變量名 char *ivar_type OBJC2_UNAVAILABLE; //變量類型 int ivar_offset OBJC2_UNAVAILABLE; //基地址偏移字節 #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif }
對應的操做函數有以下幾個:spa
//獲取成員變量名 OBJC_EXPORT const char *ivar_getName(Ivar v) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); //設置成員變量值 OBJC_EXPORT void object_setIvar(id obj, Ivar ivar, id value) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); //獲取成員變量值 OBJC_EXPORT id object_getIvar(id obj, Ivar ivar) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); // 獲取成員變量類型編碼 OBJC_EXPORT const char *ivar_getTypeEncoding(Ivar v) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); // 獲取成員變量的偏移量 對於類型id或其它對象類型的實例變量,能夠調用object_getIvar和object_setIvar來直接訪問成員變量,而不使用偏移量 OBJC_EXPORT ptrdiff_t ivar_getOffset(Ivar v) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
這幾個方法在上篇中已經使用,這裏就不提供示例了。指針
objc_property_t是表示屬性的類型,其實際是指向objc_property結構體的指針,其定義以下:
typedef struct objc_property *objc_property_t; //來自objc-private.h struct objc_property { const char *name; const char *attributes; };
objc_property_attribute_t定義了屬性的特性(attribute),它也是一個結構體,定義以下:
typedef struct { const char *name; //特性名 const char *value; //特性值 } objc_property_attribute_t;
對應的操做函數有以下幾個:
//獲取屬性名 OBJC_EXPORT const char *property_getName(objc_property_t property) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); // 獲取屬性特性描述字符串 OBJC_EXPORT const char *property_getAttributes(objc_property_t property) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0); // 獲取屬性中指定的特性 OBJC_EXPORT char *property_copyAttributeValue(objc_property_t property, const char *attributeName) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0); // 獲取屬性的特性列表 OBJC_EXPORT objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount) OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0);
這幾個方法在上篇中已經使用,這裏就不提供示例了。
咱們知道,在 Objective-C 中能夠經過 Category 給一個現有的類添加屬性,可是卻不能添加實例變量。Objective-C針對這一問題,提供了一個解決方案:即關聯對象。
關聯對象相似於成員變量,不過是在運行時添加的。咱們一般會把成員變量(Ivar)放在類聲明的頭文件中,或者放在類實現的@implementation
後面。但這有一個缺點,咱們不能再分類中添加成員變量。若是咱們嘗試在分類中添加新的成員變量,編譯器會報錯。
與關聯對象相關的函數有以下三個:
//給對象添加關聯對象,傳入 則能夠移除已有的關聯對象 OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0); //獲取關聯對象 OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key) OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0); //移除一個對象的全部關聯對象 OBJC_EXPORT void objc_removeAssociatedObjects(id object) OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);nil
【注意】:objc_removeAssociatedObjects
函數咱們通常是用不上的,由於這個函數會移除一個對象的全部關聯對象,將該對象恢復成「原始」狀態。這樣作就頗有可能把別人添加的關聯對象也一併移除,這並非咱們所但願的。因此通常的作法是經過給 objc_setAssociatedObject
函數傳入 nil
來移除某個已有的關聯對象。
在給一個對象添加關聯對象時有五種關聯策略可供選擇:
關聯策略 | 等價屬性 | 說明 |
OBJC_ASSOCIATION_ASSIGN | @property (assign) @property (unsafe_unretained) |
弱引用關聯對象 |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (strong, nonatomic) | 強引用關聯對象,非原子操做 |
OBJC_ASSOCIATION_COPY_NONATOMIC | @property (copy, nonatomic) | 複製關聯對象,非原子操做 |
OBJC_ASSOCIATION_RETAIN | @property (strong, atomic) | 強引用關聯對象,原子操做 |
OBJC_ASSOCIATION_COPY | @property (copy, atomic) | 複製關聯對象,原子操做 |
首先咱們先來思考一下咱們常規的歸檔解檔方案:
//第一步:實現協議NSCoding @interface GofUser : NSObject<NSCoding> @property (nonatomic, strong) NSString *name; //!<姓名 @property (nonatomic, strong) NSString *phone; //!<電話 @end @implementation GofUser //第二步:實現協議的兩個方法 - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) {
//須要解碼的屬性 self.name = [aDecoder decodeObjectForKey:@"name"]; self.phone = [aDecoder decodeObjectForKey:@"phone"]; } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder {
//須要編碼的屬性 [aCoder encodeObject:self.name forKey:@"name"]; [aCoder encodeObject:self.phone forKey:@"phone"]; } @end //第三步:歸檔 GofUser *user = [[GofUser alloc] init]; user.name = @"LeeGof"; user.phone = @"13800138000"; [NSKeyedArchiver archiveRootObject:user toFile:[GofUser cacheMetadataFilePath]]; //第四步:解檔 GofUser *user1 = [NSKeyedUnarchiver unarchiveObjectWithFile:[GofUser cacheMetadataFilePath]]; NSLog(@"name : %@ phone : %@", user1.name, user1.phone);
這裏的GofUser類只有兩個屬性,若是要歸檔的類有100個屬性怎麼辦?難道在NSCoding協議的兩個方法各寫100次嗎?答案是否認的,咱們能夠用runtime的成員變量來實現。
//核心代碼 - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { unsigned int count = 0; Ivar *ivars = class_copyIvarList([self class], &count); for (int i = 0; i < count; i++) { //取出成員變量 Ivar ivar = ivars[i]; const char *name = ivar_getName(ivar); //獲取KEY NSString *key = [NSString stringWithUTF8String:name]; //解檔 id value = [aDecoder decodeObjectForKey:key]; //KVC賦值 [self setValue:value forKey:key]; } free(ivars); } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { unsigned int count = 0; Ivar *ivars = class_copyIvarList([self class], &count); for (int i = 0; i < count; i++) { //取出成員變量 Ivar ivar = ivars[i]; const char *name = ivar_getName(ivar); //獲取KEY NSString *key = [NSString stringWithUTF8String:name]; //歸檔 [aCoder encodeObject:[self valueForKey:key] forKey:key]; } free(ivars); }
【思考】:使用這種方式,成員變量可否正常的歸檔和解檔?
假設如今有這麼一個應用場景:須要動態的給一個GofPerson類添加屬性workSpace。
@interface GofPerson (GofWork) @property (nonatomic, strong) NSString *workSpace; //!<工做空間 @end static const void *s_WorkSpace = "s_WorkSpace"; @implementation GofPerson (GofWork) - (void)setWorkSpace:(NSString *)workSpace { objc_setAssociatedObject(self, s_WorkSpace, workSpace, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSString *)workSpace { return objc_getAssociatedObject(self, s_WorkSpace); } @end
本篇討論了Runtime中成員變量、屬性、關聯對象相關的內容。成員變量與屬性是類的數據基礎,合理地使用Runtime中的相關操做能讓咱們更加靈活地來處理與類數據相關的工做。