上一篇我们已经对OC的Runtime做了简单的介绍,了解的其原理和API的使用RunTime理解与实战(一),这篇文章我们用runtime写一个json转模型的例子。后面会有完整的代码下载
从JSON映射到Model的原理
想一下平时我们是怎么使用类似这样子的库的,当我们有一个JSON的时候,我们把所有JSON的字段(比如name、page)全部写成对应的类中的属性。然后库会自动把你JSON对应字段的值赋值到这些对应的属性里去。属性我们用 @property 来定义,就意味着编译器会帮你生成对应的getset方法,我们使用的 . 其实也是在调用get
set方法来实现赋值的。在 Objective-C 中有一个著名的函数 objc_msgSend(...) 我们所有的类似 [obj method] 的方法调用(发送消息)都会被转换成 objc_msgSend(...) 的方式来调用。
所以对于一个库来说,要调用你这个 Model 的 set 方法,用 objc_msgSend(...) 会容易的多,所以JSON映射到Model的原理其实就是调用这个函数而已。
所以整个流程就是,你给我一个 Model 类,我会用 runtime 提供的各种函数来拿到你所有的属性和对应的get``set,判断完相应的类型以后,调用objc_msgSend(...)。
json与字典的转换
//字典转字符串
+ (NSString*)DataTOjsonString:(id)object
{
NSString *jsonString = nil;
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:object
options:NSJSONWritingPrettyPrinted
// Pass 0 if you don't care about the readability of the generated string
error:&error];
if (! jsonData) {
NSLog(@"Got an error: %@", error);
} else {
jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
return jsonString;
}
//字符串转字典
+ (NSDictionary *)ww_dictionaryWithJSON:(id)json{
if (!json || json == (id)kCFNull) {
return nil;
}
NSDictionary *dic = nil;
NSData *data = nil;
if ([json isKindOfClass:[NSDictionary class]]) {
dic = json;
}else if ([json isKindOfClass:[NSString class]]) {
data = [(NSString *)json dataUsingEncoding:NSUTF8StringEncoding];
}else if ([json isKindOfClass:[NSData class]]) {
data = json;
}
if (data) {
dic = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableLeaves error:nil];
if (![dic isKindOfClass:[NSDictionary class]]) {
dic = nil;
}
}
return dic;
}
简单的字典转模型
//简单的转换
+ (instancetype)modelWithDict:(NSDictionary *)dict{
id model = [[self alloc] init];
unsigned int count = 0;
//成员变量列表
Ivar *ivars = class_copyIvarList(self, &count);
for (int i = 0 ; i < count; i++) {
Ivar ivar = ivars[i];
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
//去掉下划线
ivarName = [ivarName substringFromIndex:1];
id value = dict[ivarName];
//nil值 会有崩溃
[model setValue:value forKeyPath:ivarName];
}
return model;
}
use
//简单的转换
- (void)simpleness_jsonToModle{
Department *dp = [[Department alloc] init];
NSDictionary *dict = @{
@"employeeId" : @2,
@"name" : @"Emp2",
@"department" : dp
};
Employee *el = [Employee modelWithDict:dict];
NSLog(@"%@",el.name);
}
这样可以简单的把字典转成模型,但是却有几个缺点:扩展性差,内存消耗大,不能关联属性对象,异常没有处理等。但是通过这个简单的转换可以了解从JSON映射到Model的原理,正如文章开头所说。
优化
获取属性列表,因为最后需要通过KVC给属性赋值的。
//获取属性列表
+ (NSArray *)objectProperties {
static const void * const kPropertyKey = &kPropertyKey;
//runtime关联对象 减少性能开销
NSMutableArray *properties = objc_getAssociatedObject(self, kPropertyKey);
if (!properties) {
properties = [NSMutableArray array];
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(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
[properties addObject:[NSString stringWithUTF8String:propertyName]];
}
objc_setAssociatedObject(self, kPropertyKey, properties, OBJC_ASSOCIATION_COPY);
}
return properties;
}
用一个枚举来自定义属性类型,判断哪些属性对象需要关联
typedef NS_ENUM(NSUInteger, WWPropertyType) {
WWPropertyTypeUndefined,
WWPropertyTypeInteger,
WWPropertyTypeFloat,
WWPropertyTypeString,
WWPropertyTypeBoolean,
WWPropertyTypeDate,
WWPropertyTypeData,
WWPropertyTypeArray,
WWPropertyTypeRelationship
};
//属性类型 转为自定义类型
+ (NSDictionary *)propertyTypes {
static const void * const kPropertyTypesKey = &kPropertyTypesKey;
//runtime关联对象
NSDictionary *result = objc_getAssociatedObject(self, kPropertyTypesKey);
if (!result) {
NSArray *properties = [self objectProperties];
result = [[NSMutableDictionary alloc] initWithCapacity:properties.count];
for (NSString *property in properties) {
WWPropertyType type = [self propertyTypeOfClass:self propertyName:property];
[(NSMutableDictionary *)result setObject:@(type) forKey:property];
}
objc_setAssociatedObject(self, kPropertyTypesKey, result, OBJC_ASSOCIATION_COPY);
}
return result;
}
自定义属性类型
//自定义属性类型
+ (WWPropertyType)propertyTypeOfClass:(Class)classType propertyName:(NSString *)propertyName {
//属性的原始类型
NSString *type = [WWReflection ww_propertyTypeOfClass:classType propertyName:propertyName];
if ([@"int" isEqualToString:type] ||
[@"unsigned" isEqualToString:type] ||
[@"short" isEqualToString:type] ||
[@"long" isEqualToString:type] ||
[@"unsigned long" isEqualToString:type] ||
[@"long long" isEqualToString:type] ||
[@"unsigned long long" isEqualToString:type] ||
[@"char" isEqualToString:type]) {
return WWPropertyTypeInteger;
} else if ([@"float" isEqualToString:type] ||
[@"double" isEqualToString:type]) {
return WWPropertyTypeFloat;
} else if ([@"NSString" isEqualToString:type] ||
[@"NSMutableString" isEqualToString:type]) {
return WWPropertyTypeString;
} else if ([@"bool" isEqualToString:type]) {
return WWPropertyTypeBoolean;
} else if ([@"NSDate" isEqualToString:type]) {
return WWPropertyTypeDate;
} else if ([@"NSData" isEqualToString:type] ||
[@"NSMutableData" isEqualToString:type]) {
return WWPropertyTypeData;
} else if ([@"NSArray" isEqualToString:type] ||
[@"NSMutableArray" isEqualToString:type]) {
return WWPropertyTypeArray;
}
else {
Class propertyClass = NSClassFromString(type);
//这里只是简单的判断 最好是继承自己的基类
if ([propertyClass isSubclassOfClass:[NSObject class]]) {
return WWPropertyTypeRelationship;
}
return WWPropertyTypeUndefined;
}
}
还需要一个方法来获取属性的数据类型:
+ (NSString *)ww_propertyTypeOfClass:(Class)classType propertyName:(NSString *)propertyName {
static NSMutableDictionary *cache = nil;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
cache = [[NSMutableDictionary alloc] init];
});
@synchronized(cache) {
NSMutableDictionary *propertyTypeMap = [cache objectForKey:NSStringFromClass(classType)];
if (!propertyTypeMap) {
propertyTypeMap = [[NSMutableDictionary alloc] init];
[cache setObject:propertyTypeMap forKey:NSStringFromClass(classType)];
}
NSString *type = [propertyTypeMap objectForKey:propertyName];
if (!type) {
objc_property_t property = class_getProperty(classType, [propertyName UTF8String]);
NSString *attributes = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
if ([attributes hasPrefix:@"T@"]) {
NSArray *substrings = [attributes componentsSeparatedByString:@"\""];
if ([substrings count] >= 2) {
type = [substrings objectAtIndex:1];
} else {
type = @"id";
}
} else if ([attributes hasPrefix:@"T{"]) {
type = @"struct";
} else {
if ([attributes hasPrefix:@"Ti"]) {
type = @"int";
} else if ([attributes hasPrefix:@"TI"]) {
type = @"unsigned";
} else if ([attributes hasPrefix:@"Ts"]) {
type = @"short";
} else if ([attributes hasPrefix:@"Tl"]) {
type = @"long";
} else if ([attributes hasPrefix:@"TL"]) {
type = @"unsigned long";
} else if ([attributes hasPrefix:@"Tq"]) {
type = @"long long";
} else if ([attributes hasPrefix:@"TQ"]) {
type = @"unsigned long long";
} else if ([attributes hasPrefix:@"TB"]) {
type = @"bool";
} else if ([attributes hasPrefix:@"Tf"]) {
type = @"float";
} else if ([attributes hasPrefix:@"Td"]) {
type = @"double";
} else if ([attributes hasPrefix:@"Tc"]) {
type = @"char";
} else if ([attributes hasPrefix:@"T^i"]) {
type = @"int *";
} else if ([attributes hasPrefix:@"T^I"]) {
type = @"unsigned *";
} else if ([attributes hasPrefix:@"T^s"]) {
type = @"short *";
} else if ([attributes hasPrefix:@"T^l"]) {
type = @"long *";
} else if ([attributes hasPrefix:@"T^q"]) {
type = @"long long *";
} else if ([attributes hasPrefix:@"T^Q"]) {
type = @"unsigned long long *";
} else if ([attributes hasPrefix:@"T^B"]) {
type = @"bool *";
} else if ([attributes hasPrefix:@"T^f"]) {
type = @"float *";
} else if ([attributes hasPrefix:@"T^d"]) {
type = @"double *";
} else if ([attributes hasPrefix:@"T*"]) {
type = @"char *";
} else {
NSAssert(0, @"Unkonwn type");
}
}
[propertyTypeMap setObject:type forKey:propertyName];
}
return type;
}
}
最后递归调用
+ (instancetype)ww_objectWithDictionary:(NSDictionary *)dictionary {
id object = [[self alloc] init];
NSArray *objectProperties = [self objectProperties];
NSDictionary *propertyTypes = [self propertyTypes];
for (NSString *key in dictionary.allKeys) {
if ([objectProperties containsObject:key]) {
id value = [dictionary objectForKey:key];
WWPropertyType propertyType = [[propertyTypes objectForKey:key] unsignedIntegerValue];
//如果是属性关联的对象就 递归
if (propertyType == WWPropertyTypeRelationship) {
NSAssert([value isKindOfClass:[NSDictionary class]], @"");
Class propertyClass = [[self propertyClasses] objectForKey:key];
value = [propertyClass ww_objectWithDictionary:value];
}
[object setValue:value forKey:key];
}
}
return object;
}
我们还可以方法交换调用
- (void)viewDidLoad {
[super viewDidLoad];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL simpleness_Sel = @selector(simpleness_jsonToModle);
SEL complex_Sel = @selector(complex_dicToObject);
//两个方法的Method
Method simpleMethod = class_getInstanceMethod([self class], simpleness_Sel);
Method complexMethod = class_getInstanceMethod([self class], complex_Sel);
//首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
BOOL isAdd = class_addMethod([self class], simpleness_Sel, method_getImplementation(complexMethod), method_getTypeEncoding(complexMethod));
if (isAdd) {
//如果成功,说明类中不存在这个方法的实现
//将被交换方法的实现替换到这个并不存在的实现
class_replaceMethod([self class], simpleness_Sel, method_getImplementation(simpleMethod), method_getTypeEncoding(simpleMethod));
}else{
//否则,交换两个方法的实现
method_exchangeImplementations(simpleMethod, complexMethod);
}
});
//方法交换后 这里实际调的是complex_dicToObject 的实现
[self simpleness_jsonToModle];
// [self complex_dicToObject];
}
可能这样介绍比较乱,可以下载项目,对照代码来看,还是比较简单的。
完整源码地址
当然,这里只是简单的运用runtime的知识,如果你想用更好更高效的json转模型,可以参考GitHub的YYmodel
如果你都看到这里了,请给我点个赞吧,你的点赞是我装逼(~ 啊不,坚持原创)的不竭动力。
另外.....
我的愿望是.......
世界和平.........
网友评论