class_copyIvarList方法獲取實例變量問題引起的思考

在runtime.h中,你能夠經過其中的class_copyIvarList方法來獲取實例變量。具體的實現以下(記得導入頭文件<objc/runtime.h>):html

- (NSArray *)ivarArray:(Class)cls {
    unsigned int stuIvarCount = 0;
    Ivar *ivars = class_copyIvarList(cls, &stuIvarCount);
    if (stuIvarCount == 0) {
        return nil;
    }
    NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:stuIvarCount];
    for (int i = 0;i<stuIvarCount;i++) {
        Ivar ivar = ivars[i];
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        NSLog(@"%@",ivarName);
        [arr addObject:ivarName];
    }
    free(ivars);
    return arr;
}

如上面代碼。其中cls就是你要獲取實例變量的類,stuIvarCount用來承載要獲取類的實例變量的個數。打印出來的ivarName就是cls的實例變量。接下來對這個方法進行解析: 首先看一下里面的Ivar,先看一下定義:objective-c

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;  //變量名字
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;   //變量類型
    int ivar_offset                                          OBJC2_UNAVAILABLE; //偏移量
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;  //存儲空間
#endif
}

Ivar是一個叫作objc_ivar的結構體指針,其中的 ifdef判斷是判斷當前設備是不是64位設備,這裏能夠延伸出一個方法:json

//判斷當前設備是不是64位設備,也能夠用這個方法判斷是不是32位設備
- (BOOL)is64Bit {
#if defined(__LP64__) && __LP64__
    return YES;
#else
    return NO;
#endif
}
OBJC_EXPORT Ivar _Nonnull * _Nullable
class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

class_copyIvarList的註釋以下: class_copyIvarList 它返回的是一個Ivar的數組,這個數組裏麪包含了你要查看類的全部實例變量,可是不包括從父類繼承過來的。若是你傳入的類沒有實例變量或者改class爲Nil,那麼該方法返回的就是NULL,count值也就變成了0。有一點須要注意:你必須使用free()方法將該數組釋放。 而後就是經過for循環遍歷,經過ivar _ getName拿到ivarName。 以上即是對clas_copyIvarList的介紹。
它還有一個最經常使用的使用方式(在開發中常常用到的):根據字典或者json字符串轉化爲model,在網絡請求返回數據時常常用到。使用方法就是本身寫一個基類的model,而後讓項目中用到的model都繼承自此基類,基類中的關鍵代碼以下:數組

+ (instancetype)zg_modelFromDic:(NSDictionary *)dataDic {
    id model = [[self alloc] init];  
    unsigned int count = 0;
    
    Ivar *ivarsA = class_copyIvarList(self, &count);
    if (count == 0) {
        return model;
    }
    for (int i = 0;i < count; i++) {
        Ivar iv = ivarsA[i];
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(iv)];
        ivarName = [ivarName substringFromIndex:1];
        id value = dataDic[ivarName];
        [model setValue:value forKey:ivarName];
    }
    free(ivarsA);
    return model;
}

這裏是把字典轉成model,先用class_copyIvar獲取該model的全部實例變量,而後經過kvc對屬性進行賦值。最終返回model。這裏有個點須要注意如下幾點:網絡

  1. 你的model的屬性名稱要和服務端返回的數據一致,好比你的model有個屬性叫作name,那麼你服務端返回的數據字典裏面的對應屬性也要叫作name,由於這個方法是根據屬性從字典裏面拿數據的。你也能夠作一個映射,讓自定義的實例變量名稱映射到服務端提供的變量名稱。
  2. 實現裏面有個substringFromIndex:操做,其目的就是把使用該方法拿到的實例變量前面的" _ "去掉。因此你最好使用 @property 進行屬性聲明,而且不要去修改自動生成的實例變量。(@property = getter + setter + _ ivar,這裏的 _ ivar其實就是編譯器幫咱們生成的實例變量)

接下來你能夠嘗試去獲取UILabel的實例變量列表:ui

[self ivarArray:[UILabel class]]

你會發現拿到的結果是這樣的:spa

(
    "_size",
    "_highlightedColor",
    "_numberOfLines",
    "_measuredNumberOfLines",
    "_baselineReferenceBounds",
    "_lastLineBaseline",
    "_previousBaselineOffsetFromBottom",
    "_firstLineBaseline",
    "_previousFirstLineBaseline",
    "_minimumScaleFactor",
    "_content",
    "_synthesizedAttributedText",
    "_defaultAttributes",
    "_fallbackColorsForUserInterfaceStyle",
    "_minimumFontSize",
    "_lineSpacing",
    "_layout",
    "_scaledMetrics",
    "_cachedIntrinsicContentSize",
    "_contentsFormat",
    "_cuiCatalog",
    "_cuiStyleEffectConfiguration",
    "_textLabelFlags",
    "_adjustsFontForContentSizeCategory",
    "__textColorFollowsTintColor",
    "_preferredMaxLayoutWidth",
    "_multilineContextWidth",
    "__visualStyle"
)

可是跳轉到UILabel.h,你會發現裏面有好多的屬性不包含在咱們根據該方法得出的屬性數組裏面,並且使用該方法獲得的屬性在UILabel.h裏面並無。這個是什麼緣由呢?
先看一下好多UILabel裏面的屬性沒有在數組裏面打印問題:猜測應該是在UILabel.m裏面使用了 @dynamic。致使沒有自動生成getter、setter和ivar,因此沒有在數組裏麪包含。3d

@synthsize:若是沒有手動實現setter/getter方法那麼會自動生成,自動生成_var變量。若是不寫,默認生成getter/setter和_var。你也可使用該關鍵字本身設置自動變量的名稱。
@dynamic告訴編譯器:屬性的setter/getter須要用戶本身實現,不自動生成,並且也不會產生_var變量。指針

也就是說在UILabel裏面雖然有個text的屬性,也許在UILabel.m裏面已經包含:code

@dynamic text;

這樣的話在實現裏面沒有產生實例變量,只是手動實現了getter和setter,因此就不會顯示text屬性在剛纔獲得的數組裏面了。
至於數組中有UILabel.h裏面沒有的變量,這個就好理解了,有可能在UILabel.m裏面添加了一些實例變量或者在運行時添加了這些實例變量。

除此方法以外,你還可使用class_copyPropertyList方法,這個是拿到的全部用 @property 聲明的屬性,包括在.m裏面添加的屬性(因此打印出來的可能要比真實在.h裏面看到的多),具體實現和上面的獲取方法相似:

- (NSArray *)propertyArr:(Class)cls {
    unsigned count = 0;
    objc_property_t *properties = class_copyPropertyList(cls, &count);
    if (count == 0) {
        return nil;
    }
    NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:count];
    for (int i = 0; i < count; i ++) {
        objc_property_t property = properties[i];
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)] ;
        [arr addObject:propertyName];
    }
    free(properties);
    return arr;
}

其中的copyPropertyList方法解釋以下: class_copyPropertyList 記得使用事後也要調用free去釋放數組。(PS:在源代碼中暫未找到objc_property結構體的說明)所以,你能夠經過使用該方法來實現字典或者json字符串轉model操做:

+ (instancetype)zg_modelFromDic:(NSDictionary *)dataDic {
    id model = [[self alloc] init];
    unsigned int count = 0;
    
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    if (count == 0) {
        return model;
    }
    for (int i = 0;i < count; i++) {
        objc_property_t property = properties[i];
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
        id value = dataDic[propertyName];
        [model setValue:value forKey:propertyName];
    }
    free(properties);
    return model;
}

兩種方式都可實現model轉換操做。 以上即是由class_copyIvarList所引起的思考。

轉載請標明來源:http://www.cnblogs.com/zhanggui/p/8177400.html

相關文章
相關標籤/搜索