Runtime之成員變量&屬性&關聯對象

上篇介紹了Runtime類和對象的相關知識點,在4.5和4.6小節,也介紹了成員變量和屬性的一些方法應用。本篇將討論實現細節的相關內容。html

在討論以前,咱們先來介紹一個很冷僻但又頗有用的一個關鍵字:@encode數組

1.類型編碼

爲了協助運行時系統,編譯器用字符串爲每一個方法的返回值、參數類型和方法選擇器編碼,使用的編碼方案在其餘狀況下也頗有用。在 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編碼

2.成員變量 

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);

這幾個方法在上篇中已經使用,這裏就不提供示例了。指針

3.屬性

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);

這幾個方法在上篇中已經使用,這裏就不提供示例了。

4.關聯對象

咱們知道,在 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) 複製關聯對象,原子操做

5.應用講解

5.1歸檔解檔

首先咱們先來思考一下咱們常規的歸檔解檔方案:

//第一步:實現協議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);
}

【思考】:使用這種方式,成員變量可否正常的歸檔和解檔?

5.2關聯對象

假設如今有這麼一個應用場景:須要動態的給一個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

6.小結

本篇討論了Runtime中成員變量、屬性、關聯對象相關的內容。成員變量與屬性是類的數據基礎,合理地使用Runtime中的相關操做能讓咱們更加靈活地來處理與類數據相關的工做。

相關文章
相關標籤/搜索