在runtime.h中,你可以通過其中的一個方法來獲取實例變數,那就是class_copyIvarList方法 ...
在runtime.h中,你可以通過其中的class_copyIvarList方法來獲取實例變數。具體的實現如下(記得導入頭文件<objc/runtime.h>):
- (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,先看一下定義:
/// 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位設備,這裡可以延伸出一個方法:
//判斷當前設備是否是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的註釋如下:
它返回的是一個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。這裡有個點需要註意以下幾點:
- 你的model的屬性名稱要和服務端返回的數據一致,比如你的model有個屬性叫做name,那麼你服務端返回的數據字典裡面的對應屬性也要叫做name,因為這個方法是根據屬性從字典裡面拿數據的。你也可以做一個映射,讓自定義的實例變數名稱映射到服務端提供的變數名稱。
- 實現裡面有個substringFromIndex:操作,其目的就是把使用該方法拿到的實例變數前面的" _ "去掉。所以你最好使用 @property 進行屬性聲明,並且不要去修改自動生成的實例變數。(@property = getter + setter + _ ivar,這裡的 _ ivar其實就是編譯器幫我們生成的實例變數)
接下來你可以嘗試去獲取UILabel的實例變數列表:
[self ivarArray:[UILabel class]]
你會發現拿到的結果是這樣的:
(
"_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,所以沒有在數組裡麵包含。
@synthsize:如果沒有手動實現setter/getter方法那麼會自動生成,自動生成_var變數。如果不寫,預設生成getter/setter和_var。你也可以使用該關鍵字自己設置自動變數的名稱。
@dynamic告訴編譯器:屬性的setter/getter需要用戶自己實現,不自動生成,而且也不會產生_var變數。
也就是說在UILabel裡面雖然有個text的屬性,也許在UILabel.m裡面已經包含:
@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方法解釋如下:
記得使用過後也要調用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