注:本文不是原創(chuàng),只是在學習中做的整理和筆記佃延,以便自己以后更好的復習现诀。原文來自runtime從入門到精通系列。
runtime實現(xiàn)自動歸檔和解檔:
如果你實現(xiàn)過自定義模型數(shù)據(jù)持久化的過程履肃,那么你也肯定明白仔沿,如果一個模型有許多個屬性,那么我們需要對每個屬性都實現(xiàn)一遍encodeObject 和decodeObjectForKey方法尺棋,如果這樣的模型又有很多個封锉,這還真的是一個十分麻煩的事情。下面來看看簡單的實現(xiàn)方式膘螟。
假設(shè)現(xiàn)在有一個Movie類成福,有3個屬性,它的h文件這這樣的:
#import <Foundation/Foundation.h>
//1. 如果想要當前類可以實現(xiàn)歸檔與反歸檔荆残,需要遵守一個協(xié)議NSCoding
@interface Movie : NSObject<NSCoding>
@property (nonatomic, copy) NSString *movieId;
@property (nonatomic, copy) NSString *movieName;
@property (nonatomic, copy) NSString *pic_url;
@end
如果是正常寫法奴艾, m文件應該是這樣的:
#import "Movie.h"
@implementation Movie
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_movieId forKey:@"id"];
[aCoder encodeObject:_movieName forKey:@"name"];
[aCoder encodeObject:_pic_url forKey:@"url"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
self.movieId = [aDecoder decodeObjectForKey:@"id"];
self.movieName = [aDecoder decodeObjectForKey:@"name"];
self.pic_url = [aDecoder decodeObjectForKey:@"url"];
}
return self;
}
@end
如果這里有100個屬性,那么我們也只能把100個屬性都給寫一遍内斯。不過你會使用runtime后蕴潦,這里就有更簡便的方法像啼。下面看看runtime的實現(xiàn)方式:
#import "Movie.h"
#import <objc/runtime.h>
@implementation Movie
- (void)encodeWithCoder:(NSCoder *)encoder
{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Movie class], &count);
for (int i = 0; i<count; i++) {
// 取出i位置對應的成員變量
Ivar ivar = ivars[i];
// 查看成員變量
const char *name = ivar_getName(ivar);
// 取值歸檔
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
[encoder encodeObject:value forKey:key];
}
free(ivars);
}
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init])
{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Movie class], &count);
for (int i = 0; i<count; i++)
{
// 取出i位置對應的成員變量
Ivar ivar = ivars[i];
// 查看成員變量
const char *name = ivar_getName(ivar);
// 取值解檔
NSString *key = [NSString stringWithUTF8String:name];
id value = [decoder decodeObjectForKey:key];
// 設(shè)置到成員變量身上
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
@end
這樣的方式實現(xiàn),不管有多少個屬性潭苞,寫這幾行代碼就搞定了忽冻。可將方法抽成宏此疹,顯得更簡單:
#import "Movie.h"
#import <objc/runtime.h>
#define encodeRuntime(A) \
\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for (int i = 0; i<count; i++) {\
Ivar ivar = ivars[i];\
const char *name = ivar_getName(ivar);\
NSString *key = [NSString stringWithUTF8String:name];\
id value = [self valueForKey:key];\
[encoder encodeObject:value forKey:key];\
}\
free(ivars);\
\
#define initCoderRuntime(A) \
\
if (self = [super init]) {\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for (int i = 0; i<count; i++) {\
Ivar ivar = ivars[i];\
const char *name = ivar_getName(ivar);\
NSString *key = [NSString stringWithUTF8String:name];\
id value = [decoder decodeObjectForKey:key];\
[self setValue:value forKey:key];\
}\
free(ivars);\
}\
return self;\
\
@implementation Movie
- (void)encodeWithCoder:(NSCoder *)encoder
{
encodeRuntime(Movie)
}
- (id)initWithCoder:(NSCoder *)decoder
{
initCoderRuntime(Movie)
}
@end
我們可以把這兩個宏單獨放到一個文件里面僧诚,這里以后需要進行數(shù)據(jù)持久化的模型都可以直接使用這兩個宏。
runtime實現(xiàn)字典轉(zhuǎn)模型:
第一步:設(shè)計模型
模型屬性秀菱,通常需要跟字典中的key一一對應振诬,根據(jù)字典生成對應的屬性字符串,實現(xiàn)原理是通過遍歷字典,判斷類型,拼接字符串:
// NSObject 的一個分類
@implementation NSObject (Log)
// 自動打印屬性字符串
+ (void)resolveDict:(NSDictionary *)dict
{
// 拼接屬性字符串代碼
NSMutableString *strM = [NSMutableString string];
// 1.遍歷字典衍菱,把字典中的所有key取出來赶么,生成對應的屬性代碼
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// 類型經(jīng)常變,抽出來
NSString *type;
if ([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) {
type = @"NSString";
}else if ([obj isKindOfClass:NSClassFromString(@"__NSCFArray")]){
type = @"NSArray";
}else if ([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
type = @"int";
}else if ([obj isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
type = @"NSDictionary";
}
// 屬性字符串
NSString *str;
if ([type containsString:@"NS"]) {
str = [NSString stringWithFormat:@"@property (nonatomic, strong) %@ *%@;",type,key];
}else{
str = [NSString stringWithFormat:@"@property (nonatomic, assign) %@ %@;",type,key];
}
// 每生成屬性字符串脊串,就自動換行辫呻。
[strM appendFormat:@"\n%@\n",str];
}];
// 把拼接好的字符串打印出來,就好了琼锋。
NSLog(@"%@",strM);
}
@end
第二步:字典轉(zhuǎn)模型
方式1:KVC的方式來字典轉(zhuǎn)模型
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
Model *model = [[self alloc] init];
[model setValuesForKeysWithDictionary: dict];
return model;
}
KVC字典轉(zhuǎn)模型弊端:必須保證放闺,模型中的屬性和字典中的key一一對應。如果不一致缕坎,就會調(diào)用[ setValue:forUndefinedKey:]怖侦,報key找不到的錯。為防止程序Crash掉谜叹,需重寫對象的- setValue:forUndefinedKey:
方法匾寝,就能繼續(xù)使用KVC,字典轉(zhuǎn)模型了荷腊。
方式2:利用Runtime來字典轉(zhuǎn)模型
實現(xiàn)思路:利用運行時艳悔,遍歷模型中所有屬性,根據(jù)模型的屬性名女仰,去字典中查找key猜年,取出對應的值,給模型的屬性賦值疾忍∏峭猓可以對NSObject寫一個分類,專門字典轉(zhuǎn)模型锭碳,以后所有模型都可以通過這個分類轉(zhuǎn)袁稽。
#import <Foundation/Foundation.h>
@protocol ModelDelegate <NSObject>
@optional
// 提供一個協(xié)議,只要準備這個協(xié)議的類擒抛,都能把數(shù)組中的字典轉(zhuǎn)模型
推汽、补疑、用在三級數(shù)組轉(zhuǎn)換
+ (NSDictionary *)arrayContainModelClass;
@end
@interface NSObject (Item)
// 字典轉(zhuǎn)模型
+ (instancetype)objectWithDict:(NSDictionary *)dict;
@end
#import "NSObject+Item.h"
#import <objc/message.h>
/*
* 把字典中所有value給模型中屬性賦值,
* KVC:遍歷字典中所有key,去模型中查找
* Runtime:根據(jù)模型中屬性名去字典中查找對應value,如果找到就給模型的屬性賦值.
*/
@implementation NSObject (Item)
// 字典轉(zhuǎn)模型
+ (instancetype)objectWithDict:(NSDictionary *)dict
{
// 創(chuàng)建對應模型對象
id objc = [[self alloc] init];
unsigned int count = 0;
// 1.獲取成員屬性數(shù)組
Ivar *ivarList = class_copyIvarList(self, &count);
// 2.遍歷所有的成員屬性名,一個一個去字典中取出對應的value給模型屬性賦值
for (int i = 0; i < count; i++) {
// 2.1 獲取成員屬性
Ivar ivar = ivarList[i];
// 2.2 獲取成員屬性名 C -> OC 字符串
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 2.3 _成員屬性名 => 字典key
NSString *key = [ivarName substringFromIndex:1];
// 2.4 去字典中取出對應value給模型屬性賦值
id value = dict[key];
// 獲取成員屬性類型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 二級轉(zhuǎn)換,字典中還有字典,也需要把對應字典轉(zhuǎn)換成模型
// 判斷下value,是不是字典
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType containsString:@"NS"]) { // 是字典對象,并且屬性名對應類型是自定義類型
// user User
// 處理類型字符串 @\"User\" -> User
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
// 自定義對象,并且值是字典
// value:user字典 -> User模型
// 獲取模型(user)類對象
Class modalClass = NSClassFromString(ivarType);
// 字典轉(zhuǎn)模型
if (modalClass) {
// 字典轉(zhuǎn)模型 user
value = [modalClass objectWithDict:value];
}
}
// 三級轉(zhuǎn)換:NSArray中也是字典,把數(shù)組中的字典轉(zhuǎn)換成模型.
// 判斷值是否是數(shù)組
if ([value isKindOfClass:[NSArray class]]) {
// 判斷對應類有沒有實現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
if ([self respondsToSelector:@selector(arrayContainModelClass)]) {
// 轉(zhuǎn)換成id類型歹撒,就能調(diào)用任何對象的方法
id idSelf = self;
// 獲取數(shù)組中字典對應的模型
NSString *type = [idSelf arrayContainModelClass][key];
// 生成模型
Class classModel = NSClassFromString(type);
NSMutableArray *arrM = [NSMutableArray array];
// 遍歷字典數(shù)組莲组,生成模型數(shù)組
for (NSDictionary *dict in value) {
// 字典轉(zhuǎn)模型
id model = [classModel objectWithDict:dict];
[arrM addObject:model];
}
// 把模型數(shù)組賦值給value
value = arrM;
}
}
// 2.5 KVC字典轉(zhuǎn)模型
if (value) {
[objc setValue:value forKey:key];
}
}
// 返回對象
return objc;
}
@end
模型代碼:
#import <Foundation/Foundation.h>
#import "NSObject+Item.h"
@class User;
@interface Status : NSObject <ModelDelegate>
@property (nonatomic, strong) NSString *source;
@property (nonatomic, assign) int reposts_count;
@property (nonatomic, strong) NSArray *pic_urls;
@property (nonatomic, strong) NSString *created_at;
@property (nonatomic, assign) int attitudes_count;
@property (nonatomic, strong) NSString *idstr;
@property (nonatomic, strong) NSString *text;
@property (nonatomic, assign) int comments_count;
@property (nonatomic, strong) User *user;
@end
#import "Status.h"
@implementation Status
+ (NSDictionary *)arrayContainModelClass
{
return @{@"pic_urls" : @"Picture"};
}
@end
基本上主流的json 轉(zhuǎn)model 都少不了,使用運行時動態(tài)獲取屬性的屬性名的方法暖夭,來進行字典轉(zhuǎn)模型替換锹杈,字典轉(zhuǎn)模型效率最高的(耗時最短的)的是KVC,其他的字典轉(zhuǎn)模型是在KVC 的key 和Value 做處理迈着,動態(tài)的獲取json 中的key 和value ,當然轉(zhuǎn)換的過程中,第三方框架需要做一些判空啊,鑲嵌的邏輯處理, 再進行KVC 轉(zhuǎn)模型竭望。
無論JsonModle、YYKIt裕菠、MJextension 都少不了[xx setValue:value forKey:key]咬清;這句代碼的,不信可以去搜,這是字典轉(zhuǎn)模型的核心方法奴潘。