- 本文首發(fā)于我的個(gè)人博客:「程序員充電站」
- 文章鏈接:「?jìng)魉烷T」
- 本文更新時(shí)間:2019年07月31日20:29:30
本文用來(lái)介紹 iOS 開(kāi)發(fā)中废封,如何通過(guò)『Runtime』獲取類詳細(xì)屬性哮肚、方法认轨。通過(guò)本文璃诀,您將了解到:
- 獲取類詳細(xì)屬性、方法簡(jiǎn)述
- 獲取類詳細(xì)屬性谷暮、方法(成員變量列表蒿往、屬性列表、方法列表湿弦、所遵循的協(xié)議列表)
- 應(yīng)用場(chǎng)景
3.1 修改私有屬性
3.2 萬(wàn)能控制器跳轉(zhuǎn)
3.3 實(shí)現(xiàn)字典轉(zhuǎn)模型
3.4 改進(jìn) iOS 歸檔和解檔文中示例代碼在: bujige / YSC-Class-DetailList-Demo
1. 獲取類詳細(xì)屬性瓤漏、方法簡(jiǎn)述
在蘋果官方為我們提供的類中,只能獲取一小部分公開(kāi)的屬性和方法。有些我們恰好需要的屬性和方法蔬充,可能會(huì)被官方隱藏了起來(lái)蝶俱,沒(méi)有直接提供給我們。
那應(yīng)該如何才能獲取一個(gè)類中所有的變量和方法饥漫,用來(lái)查找是否有對(duì)我們有用的變量和方法呢榨呆?
幸好 Runtime 中為我們提供了一系列 API 來(lái)獲取 Class (類)的 成員變量( Ivar )、屬性( Property )庸队、方法( Method )积蜻、協(xié)議( Protocol ) 等。我們可以通過(guò)這些方法來(lái)遍歷一個(gè)類中的成員變量列表彻消、屬性列表竿拆、方法列表、協(xié)議列表宾尚。從而查找我們需要的變量和方法丙笋。
比如說(shuō)遇到這樣一個(gè)需求:更改 UITextField 占位文字的顏色和字號(hào)。實(shí)現(xiàn)代碼參考 3.1 修改私有屬性 中的例子煌贴。
下面我們先來(lái)講解一下如何通過(guò)代碼獲取類詳細(xì)屬性不见、方法。
2. 獲取類詳細(xì)屬性崔步、方法
注意:頭文件中需引入
#import <objc/runtime.h>
稳吮。
2.1 獲取類的成員變量列表
// 打印成員變量列表
- (void)printIvarList {
unsigned int count;
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"ivar(%d) : %@", i, [NSString stringWithUTF8String:ivarName]);
}
free(ivarList);
}
2.2 獲取類的屬性列表
// 打印屬性列表
- (void)printPropertyList {
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"propertyName(%d) : %@", i, [NSString stringWithUTF8String:propertyName]);
}
free(propertyList);
}
2.3 獲取類的方法列表
// 打印方法列表
- (void)printMethodList {
unsigned int count;
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
Method method = methodList[i];
NSLog(@"method(%d) : %@", i, NSStringFromSelector(method_getName(method)));
}
free(methodList);
}
2.4 獲取類所遵循的協(xié)議列表
// 打印協(xié)議列表
- (void)printProtocolList {
unsigned int count;
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol(%d) : %@", i, [NSString stringWithUTF8String:protocolName]);
}
free(protocolList);
}
3. 應(yīng)用場(chǎng)景
3.1 修改私有屬性
需求:更改 UITextField 占位文字的顏色和字號(hào)
先來(lái)想想又幾種做法:
方法 1:通過(guò) attributedPlaceholder 屬性修改
我們知道 UITextField 中有 placeholder 屬性和 attributedPlaceholder 屬性。通過(guò) placeholder 屬性只能更改占位文字井濒,無(wú)法修改占位文字的字體和顏色灶似。而通過(guò) attributedPlaceholder 屬性我們就可以修改 UITextField 占位文字的顏色和字號(hào)了。
方法 2:重寫 UITextField 的 drawPlaceholderInRect: 方法修改
實(shí)現(xiàn)步驟:
- 自定義一個(gè) XXTextField 繼承自 UITextField瑞你;
- 重寫自定義 XXTextField 的 drawPlaceholderInRect: 方法酪惭;
- 在 drawPlaceholderInRect 方法中設(shè)置 placeholder 的屬性。
- (void)drawPlaceholderInRect:(CGRect)rect {
// 計(jì)算占位文字的 Size
NSDictionary *attributes = @{
NSForegroundColorAttributeName : [UIColor lightGrayColor],
NSFontAttributeName : [UIFont systemFontOfSize:15]
};
CGSize placeholderSize = [self.placeholder sizeWithAttributes:attributes];
[self.placeholder drawInRect:CGRectMake(0, (rect.size.height - placeholderSize.height)/2, rect.size.width, rect.size.height) withAttributes: attributes];
}
方法 3:利用 Runtime者甲,找到并修改 UITextfield 的私有屬性
實(shí)現(xiàn)步驟:
- 通過(guò)獲取類的屬性列表和成員變量列表的方法打印 UITextfield 所有屬性和成員變量春感;
- 找到私有的成員變量
_placeholderLabel
; - 利用 KVC 對(duì)
_placeholderLabel
進(jìn)行修改虏缸。
// 打印 UITextfield 的所有屬性和成員變量
- (void)printUITextFieldList {
unsigned int count;
Ivar *ivarList = class_copyIvarList([UITextField class], &count);
for (unsigned int i = 0; i < count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"ivar(%d) : %@", i, [NSString stringWithUTF8String:ivarName]);
}
free(ivarList);
objc_property_t *propertyList = class_copyPropertyList([UITextField class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"propertyName(%d) : %@", i, [NSString stringWithUTF8String:propertyName]);
}
free(propertyList);
}
// 通過(guò)修改 UITextfield 的私有屬性更改占位顏色和字體
- (void)createLoginTextField {
UITextField *loginTextField = [[UITextField alloc] init];
loginTextField.frame = CGRectMake(15,(self.view.bounds.size.height-52-50)/2, self.view.bounds.size.width-60-18,52);
loginTextField.delegate = self;
loginTextField.font = [UIFont systemFontOfSize:14];
loginTextField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
loginTextField.textColor = [UIColor blackColor];
loginTextField.placeholder = @"用戶名/郵箱";
[loginTextField setValue:[UIFont systemFontOfSize:15] forKeyPath:@"_placeholderLabel.font"];
[loginTextField setValue:[UIColor lightGrayColor]forKeyPath:@"_placeholderLabel.textColor"];
[self.view addSubview:loginTextField];
}
3.2 萬(wàn)能控制器跳轉(zhuǎn)
需求:
- 某個(gè)頁(yè)面的不同 banner 圖鲫懒,點(diǎn)擊可以跳轉(zhuǎn)到不同頁(yè)面。
- 推送通知刽辙,點(diǎn)擊跳轉(zhuǎn)到指定頁(yè)面窥岩。
- 二維碼掃描,根據(jù)不同內(nèi)容宰缤,跳轉(zhuǎn)不同頁(yè)面颂翼。
- WebView 頁(yè)面晃洒,根據(jù) URL 點(diǎn)擊不同,跳轉(zhuǎn)不同的原生頁(yè)面朦乏。
先來(lái)思考一下幾種解決方法球及。
方法 1:在每個(gè)需要跳轉(zhuǎn)的地方寫一堆判斷語(yǔ)句以及跳轉(zhuǎn)語(yǔ)句。
方法 2:將判斷語(yǔ)句和跳轉(zhuǎn)語(yǔ)句抽取出來(lái)呻疹,寫到基類吃引,或者對(duì)應(yīng)的 Category 中。
方法 3:利用 Runtime诲宇,定制一個(gè)萬(wàn)能跳轉(zhuǎn)控制器工具际歼。
實(shí)現(xiàn)步驟:
- 事先和服務(wù)器端商量好惶翻,定義跳轉(zhuǎn)不同控制器的規(guī)則姑蓝,讓服務(wù)器傳回對(duì)應(yīng)規(guī)則的相關(guān)參數(shù)。
比如:跳轉(zhuǎn)到 A 控制器吕粗,需要服務(wù)器傳回 A 控制器的類名纺荧,控制器 A 需要傳入的屬性參數(shù)(id、type 等等)颅筋。 - 根據(jù)服務(wù)器傳回的類名宙暇,創(chuàng)建對(duì)應(yīng)的控制器對(duì)象;
- 遍歷服務(wù)器傳回的參數(shù)议泵,利用 Runtime 遍歷控制器對(duì)象的屬性列表占贫;
- 如果控制器對(duì)象存在該屬性,則利用 KVC 進(jìn)行賦值先口;
- 進(jìn)行跳轉(zhuǎn)型奥。
首先,定義跳轉(zhuǎn)規(guī)則碉京,如下所示厢汹。XXViewController
是將要跳轉(zhuǎn)的控制器類名。property
字典中保存的是控制器所需的屬性參數(shù)谐宙。
// 定義的規(guī)則
NSDictionary *params = @{
@"class" : @"XXViewController",
@"property" : @{
@"ID" : @"123",
@"type" : @"XXViewController1"
}
};
然后烫葬,添加一個(gè)工具類 XXJumpControllerTool
,添加跳轉(zhuǎn)相關(guān)的類方法凡蜻。
/********************* XXJumpControllerTool.h 文件 *********************/
#import <Foundation/Foundation.h>
@interface XXJumpControllerTool : NSObject
+ (void)pushViewControllerWithParams:(NSDictionary *)params;
@end
/********************* XXJumpControllerTool.m 文件 *********************/
#import "XXJumpControllerTool.h"
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
@implementation XXJumpControllerTool
+ (void)pushViewControllerWithParams:(NSDictionary *)params {
// 取出控制器類名
NSString *classNameStr = [NSString stringWithFormat:@"%@", params[@"class"]];
const char *className = [classNameStr cStringUsingEncoding:NSASCIIStringEncoding];
// 根據(jù)字符串返回一個(gè)類
Class newClass = objc_getClass(className);
if (!newClass) {
// 創(chuàng)建一個(gè)類
Class superClass = [NSObject class];
newClass = objc_allocateClassPair(superClass, className, 0);
// 注冊(cè)你創(chuàng)建的這個(gè)類
objc_registerClassPair(newClass);
}
// 創(chuàng)建對(duì)象(就是控制器對(duì)象)
id instance = [[newClass alloc] init];
NSDictionary *propertys = params[@"property"];
[propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
// 檢測(cè)這個(gè)對(duì)象是否存在該屬性
if ([XXJumpControllerTool checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
// 利用 KVC 對(duì)控制器對(duì)象的屬性賦值
[instance setValue:obj forKey:key];
}
}];
// 跳轉(zhuǎn)到對(duì)應(yīng)的控制器
[[XXJumpControllerTool topViewController].navigationController pushViewController:instance animated:YES];
}
// 檢測(cè)對(duì)象是否存在該屬性
+ (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName {
unsigned int count, i;
// 獲取對(duì)象里的屬性列表
objc_property_t *properties = class_copyPropertyList([instance class], &count);
for (i = 0; i < count; i++) {
objc_property_t property =properties[i];
// 屬性名轉(zhuǎn)成字符串
NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
// 判斷該屬性是否存在
if ([propertyName isEqualToString:verifyPropertyName]) {
free(properties);
return YES;
}
}
free(properties);
return NO;
}
// 獲取當(dāng)前顯示在屏幕最頂層的 ViewController
+ (UIViewController *)topViewController {
UIViewController *resultVC = [XXJumpControllerTool _topViewController:[[UIApplication sharedApplication].keyWindow rootViewController]];
while (resultVC.presentedViewController) {
resultVC = [XXJumpControllerTool _topViewController:resultVC.presentedViewController];
}
return resultVC;
}
+ (UIViewController *)_topViewController:(UIViewController *)vc {
if ([vc isKindOfClass:[UINavigationController class]]) {
return [XXJumpControllerTool _topViewController:[(UINavigationController *)vc topViewController]];
} else if ([vc isKindOfClass:[UITabBarController class]]) {
return [XXJumpControllerTool _topViewController:[(UITabBarController *)vc selectedViewController]];
} else {
return vc;
}
return nil;
}
@end
測(cè)試代碼:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 萬(wàn)能跳轉(zhuǎn)控制器
[self jumpController];
}
3.3 實(shí)現(xiàn)字典轉(zhuǎn)模型
在日常開(kāi)發(fā)中搭综,將網(wǎng)絡(luò)請(qǐng)求中獲取的 JSON 數(shù)據(jù)轉(zhuǎn)為數(shù)據(jù)模型,是我們開(kāi)發(fā)中必不可少的操作划栓。通常我們會(huì)選用諸如 YYModel
设凹、JSONModel
或者 MJExtension
等第三方框架來(lái)實(shí)現(xiàn)這一過(guò)程。這些框架實(shí)現(xiàn)原理的核心就是 Runtime
和 KVC
茅姜,以及 Getter / Setter
闪朱。
實(shí)現(xiàn)的大體思路如下:借助 Runtime
可以動(dòng)態(tài)獲取成員列表的特性月匣,遍歷模型中所有屬性,然后以獲取到的屬性名為 key
奋姿,在 JSON
字典中尋找對(duì)應(yīng)的值 value
锄开;再使用 KVC
或直接調(diào)用 Getter / Setter
將每一個(gè)對(duì)應(yīng) value
賦值給模型,就完成了字典轉(zhuǎn)模型的目的称诗。
需求:將服務(wù)器返回的 JSON 字典轉(zhuǎn)為數(shù)據(jù)模型萍悴。
先準(zhǔn)備一份待解析的 JSON 數(shù)據(jù),內(nèi)容如下:
{
"id": "123412341234",
"name": "行走少年郎",
"age": "18",
"weight": 120,
"address": {
"country": "中國(guó)",
"province": "北京"
},
"courses": [
{
"name": "Chinese",
"desc": "語(yǔ)文課"
},
{
"name": "Math",
"desc": "數(shù)學(xué)課"
},
{
"name": "English",
"desc": "英語(yǔ)課"
}
]
}
假設(shè)這就是服務(wù)器返回的 JSON 數(shù)據(jù)寓免,內(nèi)容是一個(gè)學(xué)生的信息⊙⒂眨現(xiàn)在我們需要將該 JSON 字典轉(zhuǎn)為方便開(kāi)發(fā)的數(shù)據(jù)模型。
從這份 JSON 中可以看出袜香,字典中取值除了字符串之外撕予,還有數(shù)組和字典。那么在將字典轉(zhuǎn)換成數(shù)據(jù)模型的時(shí)候蜈首,就要考慮 模型嵌套模型实抡、模型嵌套模型數(shù)組 的情況了。具體步驟如下:
3.3.1 創(chuàng)建模型
經(jīng)過(guò)分析欢策,我們總共需要三個(gè)模型: XXStudentModel吆寨、XXAdressModel、XXCourseModel踩寇。
/********************* XXStudentModel.h 文件 *********************/
#import <Foundation/Foundation.h>
#import "NSObject+XXModel.h"
@class XXAdressModel, XXCourseModel;
@interface XXStudentModel : NSObject <XXModel>
/* 姓名 */
@property (nonatomic, copy) NSString *name;
/* 學(xué)生號(hào) id */
@property (nonatomic, copy) NSString *uid;
/* 年齡 */
@property (nonatomic, assign) NSInteger age;
/* 體重 */
@property (nonatomic, assign) NSInteger weight;
/* 地址(嵌套模型) */
@property (nonatomic, strong) XXAdressModel *address;
/* 課程(嵌套模型數(shù)組) */
@property (nonatomic, strong) NSArray *courses;
@end
/********************* XXStudentModel.m 文件 *********************/
#import "XXStudentModel.h"
#import "XXCourseModel.h"
@implementation XXStudentModel
+ (NSDictionary *)modelContainerPropertyGenericClass {
//需要特別處理的屬性
return @{
@"courses" : [XXCourseModel class],
@"uid" : @"id"
};
}
@end
/********************* XXAdressModel.h 文件 *********************/
#import <Foundation/Foundation.h>
@interface XXAdressModel : NSObject
/* 國(guó)籍 */
@property (nonatomic, copy) NSString *country;
/* 省份 */
@property (nonatomic, copy) NSString *province;
/* 城市 */
@property (nonatomic, copy) NSString *city;
@end
/********************* XXAdressModel.m 文件 *********************/
#import "XXAdressModel.h"
@implementation XXAdressModel
@end
/********************* XXCourseModel.h 文件 *********************/
#import <Foundation/Foundation.h>
@interface XXCourseModel : NSObject
/* 課程名 */
@property (nonatomic, copy) NSString *name;
/* 課程介紹 */
@property (nonatomic, copy) NSString *desc;
@end
/********************* XXCourseModel.m 文件 *********************/
#import "XXCourseModel.h"
@implementation XXCourseModel
@end
3.3.2 在 NSObject 分類中實(shí)現(xiàn)字典轉(zhuǎn)模型
細(xì)心的你可能已經(jīng)發(fā)現(xiàn):上面的 XXStudentModel.h
文件中導(dǎo)入了 #import "NSObject+XXModel.h"
文件啄清,并且遵循了 <XXModel>
協(xié)議,并且在 XXStudentModel.m
文件中實(shí)現(xiàn)了協(xié)議的 + (NSDictionary *)modelContainerPropertyGenericClass
方法俺孙。
NSObject+XXModel.h
辣卒、NSObject+XXModel.m
就是我們用來(lái)解決字典轉(zhuǎn)模型所創(chuàng)建的分類,協(xié)議中的 + (NSDictionary *)modelContainerPropertyGenericClass
方法用來(lái)告訴分類特殊字段的處理規(guī)則鼠冕,比如 id --> uid
添寺。
/********************* NSObject+XXModel.h 文件 *********************/
#import <Foundation/Foundation.h>
// XXModel 協(xié)議
@protocol XXModel <NSObject>
@optional
// 協(xié)議方法:返回一個(gè)字典,表明特殊字段的處理規(guī)則
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
@end;
@interface NSObject (XXModel)
// 字典轉(zhuǎn)模型方法
+ (instancetype)xx_modelWithDictionary:(NSDictionary *)dictionary;
@end
/********************* NSObject+XXModel.m 文件 *********************/
#import "NSObject+XXModel.h"
#import <objc/runtime.h>
@implementation NSObject (XXModel)
+ (instancetype)xx_modelWithDictionary:(NSDictionary *)dictionary {
// 創(chuàng)建當(dāng)前模型對(duì)象
id object = [[self alloc] init];
unsigned int count;
// 獲取當(dāng)前對(duì)象的屬性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
// 遍歷 propertyList 中所有屬性懈费,以其屬性名為 key计露,在字典中查找 value
for (unsigned int i = 0; i < count; i++) {
// 獲取屬性
objc_property_t property = propertyList[i];
const char *propertyName = property_getName(property);
NSString *propertyNameStr = [NSString stringWithUTF8String:propertyName];
// 獲取 JSON 中屬性值 value
id value = [dictionary objectForKey:propertyNameStr];
// 獲取屬性所屬類名
NSString *propertyType;
unsigned int attrCount;
objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
for (unsigned int i = 0; i < attrCount; i++) {
switch (attrs[i].name[0]) {
case 'T': { // Type encoding
if (attrs[i].value) {
propertyType = [NSString stringWithUTF8String:attrs[i].value];
// 去除轉(zhuǎn)義字符:@\"NSString\" -> @"NSString"
propertyType = [propertyType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
// 去除 @ 符號(hào)
propertyType = [propertyType stringByReplacingOccurrencesOfString:@"@" withString:@""];
}
} break;
default: break;
}
}
// 對(duì)特殊屬性進(jìn)行處理
// 判斷當(dāng)前類是否實(shí)現(xiàn)了協(xié)議方法,獲取協(xié)議方法中規(guī)定的特殊屬性的處理方式
NSDictionary *perpertyTypeDic;
if([self respondsToSelector:@selector(modelContainerPropertyGenericClass)]){
perpertyTypeDic = [self performSelector:@selector(modelContainerPropertyGenericClass) withObject:nil];
}
// 處理:字典的 key 與模型屬性不匹配的問(wèn)題憎乙,如 id -> uid
id anotherName = perpertyTypeDic[propertyNameStr];
if(anotherName && [anotherName isKindOfClass:[NSString class]]){
value = dictionary[anotherName];
}
// 處理:模型嵌套模型的情況
if ([value isKindOfClass:[NSDictionary class]] && ![propertyType hasPrefix:@"NS"]) {
Class modelClass = NSClassFromString(propertyType);
if (modelClass != nil) {
// 將被嵌套字典數(shù)據(jù)也轉(zhuǎn)化成Model
value = [modelClass xx_modelWithDictionary:value];
}
}
// 處理:模型嵌套模型數(shù)組的情況
// 判斷當(dāng)前 value 是一個(gè)數(shù)組票罐,而且存在協(xié)議方法返回了 perpertyTypeDic
if ([value isKindOfClass:[NSArray class]] && perpertyTypeDic) {
Class itemModelClass = perpertyTypeDic[propertyNameStr];
//封裝數(shù)組:將每一個(gè)子數(shù)據(jù)轉(zhuǎn)化為 Model
NSMutableArray *itemArray = @[].mutableCopy;
for (NSDictionary *itemDic in value) {
id model = [itemModelClass xx_modelWithDictionary:itemDic];
[itemArray addObject:model];
}
value = itemArray;
}
// 使用 KVC 方法將 value 更新到 object 中
if (value != nil) {
[object setValue:value forKey:propertyNameStr];
}
}
free(propertyList);
return object;
}
@end
3.3.3 測(cè)試代碼
- (void)parseJSON {
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Student" ofType:@"json"];
NSData *jsonData = [NSData dataWithContentsOfFile:filePath];
// 讀取 JSON 數(shù)據(jù)
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
NSLog(@"%@",json);
// JSON 字典轉(zhuǎn)模型
XXStudentModel *student = [XXStudentModel xx_modelWithDictionary:json];
NSLog(@"student.uid = %@", student.uid);
NSLog(@"student.name = %@", student.name);
for (unsigned int i = 0; i < student.courses.count; i++) {
XXCourseModel *courseModel = student.courses[i];
NSLog(@"courseModel[%d].name = %@ .desc = %@", i, courseModel.name, courseModel.desc);
}
}
效果如下:
當(dāng)然,如若需要考慮緩存機(jī)制泞边、性能問(wèn)題该押、對(duì)象類型檢查等,建議還是使用例如 YYModel
之類的知名第三方框架阵谚,或者自己造輪子蚕礼。
3.4 改進(jìn) iOS 歸檔和解檔
『歸檔』是一種常用的輕量型文件存儲(chǔ)方式烟具,在項(xiàng)目中,如果需要將數(shù)據(jù)模型本地化存儲(chǔ)奠蹬,一般就會(huì)用到歸檔和解檔朝聋。但是如果數(shù)據(jù)模型中有多個(gè)屬性的話,我們不得不對(duì)每個(gè)屬性進(jìn)行處理囤躁,這個(gè)過(guò)程非常繁瑣冀痕。
這里我們可以參考之前『字典轉(zhuǎn)模型』 的代碼。通過(guò) Runtime 獲取類的屬性列表狸演,實(shí)現(xiàn)自動(dòng)歸檔和解檔言蛇。歸檔操作和解檔操作主要會(huì)用到了兩個(gè)方法: encodeObject: forKey:
和 decodeObjectForKey:
。
首先在 NSObject 的分類 NSObject+XXModel.h
宵距、NSObject+XXModel.m
中添加以下代碼:
// 解檔
- (instancetype)xx_modelInitWithCoder:(NSCoder *)aDecoder {
if (!aDecoder) return self;
if (!self) {
return self;
}
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSString *name = [NSString stringWithUTF8String:propertyName];
id value = [aDecoder decodeObjectForKey:name];
[self setValue:value forKey:name];
}
free(propertyList);
return self;
}
// 歸檔
- (void)xx_modelEncodeWithCoder:(NSCoder *)aCoder {
if (!aCoder) return;
if (!self) {
return;
}
unsigned int count;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSString *name = [NSString stringWithUTF8String:propertyName];
id value = [self valueForKey:name];
[aCoder encodeObject:value forKey:name];
}
free(propertyList);
}
然后在需要實(shí)現(xiàn)歸檔解檔的模型中腊尚,添加 -initWithCoder:
和 -encodeWithCoder:
方法。
#import "XXPerson.h"
#import "NSObject+XXModel.h"
@implementation XXPerson
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
[self xx_modelInitWithCoder:aDecoder];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[self xx_modelEncodeWithCoder:aCoder];
}
@end
測(cè)試一下歸檔解檔代碼:
XXPerson *person = [[XXPerson alloc] init];
person.uid = @"123412341234";
person.name = @"行走少年郎";
person.age = 18;
person.weight = 120;
// 歸檔
NSString *path = [NSString stringWithFormat:@"%@/person.plist", NSHomeDirectory()];
[NSKeyedArchiver archiveRootObject:person toFile:path];
// 解檔
XXPerson *personObject = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
NSLog(@"personObject.uid = %@", personObject.uid);
NSLog(@"personObject.name = %@", personObject.name);
當(dāng)然消玄,上邊代碼只是演示一下 Runtime 對(duì)于歸檔和解檔的優(yōu)化跟伏,真正用在開(kāi)發(fā)中的邏輯遠(yuǎn)比上邊的樣例要負(fù)責(zé)丢胚,具體也參考 YYModel
的實(shí)現(xiàn)翩瓜。
參考資料
- CoyoteK : runtime從入門到精通(九)—— 萬(wàn)能界面跳轉(zhuǎn)
- 梧雨北辰 : Runtime-iOS運(yùn)行時(shí)應(yīng)用篇
- 雷曼同學(xué) : http://www.reibang.com/p/361c9136cf3a
- ibireme : iOS JSON 模型轉(zhuǎn)換庫(kù)評(píng)測(cè)
iOS 開(kāi)發(fā):『Runtime』詳解 系列文章:
- iOS 開(kāi)發(fā):『Runtime』詳解(一)基礎(chǔ)知識(shí)
- iOS 開(kāi)發(fā):『Runtime』詳解(二)Method Swizzling
- iOS 開(kāi)發(fā):『Runtime』詳解(三)Category 底層原理
- iOS 開(kāi)發(fā):『Runtime』詳解(四)獲取類詳細(xì)屬性、方法
尚未完成:
- iOS 開(kāi)發(fā):『Runtime』詳解(五)Crash 防護(hù)系統(tǒng)
- iOS 開(kāi)發(fā):『Runtime』詳解(六)Objective-C 2.0 結(jié)構(gòu)解析
- iOS 開(kāi)發(fā):『Runtime』詳解(七)KVO 底層實(shí)現(xiàn)
- 本文作者: 行走少年郎
- 本文鏈接: http://www.reibang.com/p/aeecc4b4621c
- 版權(quán)聲明: 本文章采用 CC BY-NC-SA 3.0 許可協(xié)議携龟。轉(zhuǎn)載請(qǐng)?jiān)谖淖珠_(kāi)頭注明『本文作者』和『本文鏈接』兔跌!