咱们都知道,开发中会有这么一个过程,就是将服务器返回的数据转换成咱们本身定义的模型对象。固然服务器返回的数据结构有xml
类型的,也有json
类型的。本文只讨论json
格式的。html
你们在项目中通常是怎么样将服务器返回的json
转化成本身定义的模型类呢?ios
我在项目中通常都是使用的MJExtension。
本文讲解的也基本就是解读MJExtension
中的部分源码。
好了,废话很少说,直接上代码,let's gogit
首先,从最简单的字典开始,例如咱们须要将以下的字典转化成自定义的模型。github
NSDictionary *dict = @{@"name":@"Scott", @"icon" : @"lufy.png", @"age" : @"20", @"height" : @1.75, @"money" : @"100.9", @"sex" : @(SexMale), @"gay" : @"ture", };
咱们定义一个ScottUser
类,而且定义好属性名以下:json
#import <Foundation/Foundation.h> typedef NS_ENUM(NSInteger, Sex) { SexMale, // 男 SexFemale // 女 }; @interface ScottUser : NSObject /** 姓名 */ @property (nonatomic, copy) NSString *name; /** 头像 */ @property (nonatomic, copy) NSString *icon; /** 年龄 */ @property (nonatomic, assign) unsigned int age; /** 身高 */ @property (nonatomic, strong) NSNumber *height; /** 财富 */ @property (nonatomic, copy) NSString *money; /** 性别 */ @property (nonatomic, assign) Sex sex; /** 是否同性 */ @property (nonatomic, assign, getter=isGay) BOOL gay; @end
到此为止,咱们下一步的目标就是拿到字典里面的值(value
)对ScottUser
模型属性进行赋值,模型的属性名对应着字典里面的key
。数组
最直接的方法就是:缓存
ScottUser *user = [[ScottUser alloc] init]; user.name = dict[@"name"]; user.icon = dict[@"icon"]; ...
可是,对于每一次的数据转模型,你都要这样去写大量的重复代码,毫无心义。
固然咱们利用setValuesForKeysWithDictionary:(NSDictionary *)dict
进行kvc
赋值。性能优化
KVC赋值服务器
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
方法。咱们能够经过写一个框架自动帮咱们实现字典转模型,大体思路就是:数据结构
属性
,而后拿到属性名
做为键值
去字典中寻找值
;值
后,根据模型的属性类型
将值
转化成正确的类型
;属性名
赋值。属性
,拿到属性名
做为键值
去字典中寻找值
。方法伪代码:
[模型类 遍历属性的方法];
为了方便使用,建立一个叫NSObject+ScottProperty
的分类,写一个获取全部属性的方法。
#import <Foundation/Foundation.h> @interface NSObject (ScottProperty) + (NSArray *)properties; @end
假设咱们在看不到一个类的.h
和.m
文件的前提下,有什么办法能够获取它全部的实例变量呢?
答案是经过Runtime
。
#import "NSObject+ScottProperty.h" #import <objc/runtime.h> @implementation NSObject (ScottProperty) + (NSArray *)properties { NSMutableArray *propertiesArr = [NSMutableArray array]; unsigned int outCount = 0; objc_property_t *properties = class_copyPropertyList(self, &outCount); for (int i=0; i<outCount; i++) { objc_property_t property = properties[i]; [propertiesArr addObject:propertyObj]; char *name = property_getName(property); char *att = property_getAttributes(property); NSLog(@"name:%s-----att:%s",name,att); } return propertiesArr; }
在外部调用+ (NSArray *)properties
方法可以打印出一个类的全部属性,如:
NSArray *arr = [ScottUser properties];
运行程序,可以看到控制台的输出:
从输出中能够看到经过property_getName()
获取每个objc_property_t
的name
表示成员属性的名字,经过property_getAttributes()
获取每个objc_property_t
的attributes
表示成员属性中的一些特性(如是什么类,原子性仍是非原子性,是strong仍是weak仍是copy,生成的成员变量名等信息...)
从苹果的官方文档(Objective-C Runtime Programming Guide)能够得知,attributes
是一个类型编码字符串,这个字符串以T
做为开始,接上@encode
类型编码和一个逗号,以V
接上实例变量名做为结尾,在他们之间是一些其余信息,以逗号分隔,具体内容能够查看官方文档中详细的表格。
在实际赋值过程当中,咱们并不关心该属性的内存管理、生成的成员变量名、或者其余什么信息,在attributes
中,只须要知道它所属的类
或者知道什么基本数据类型
,即T
至第一个逗号以前
中间的内容,若是是类
的话还须要将@
和""
去掉。
实际上,Runtime
已经给咱们提供获取属性名和属性特性的函数了,也就是经过property_getName()
和property_getAttributes()
。
这时候咱们就能够获取到属性名和属性对应的属性特性了。
值
后,根据属性类型
将值
转化成正确的类型
如今已经完成了第一步,而且拿到了属性名
,可是数据类型还须要咱们进一步截取,截取方法以下:
for (int i=0; i<outCount; i++) { objc_property_t property = properties[i]; // 为了之后方便使用,将C字符串转化成OC对象 // char *name = property_getName(property); NSString *name = @(property_getName(property)); // char *att = property_getAttributes(property); NSString *att = @(property_getAttributes(property)); NSUInteger loc = 1; NSUInteger len = [att rangeOfString:@","].location - loc; NSString *type = [att substringWithRange:NSMakeRange(loc, len)]; NSLog(@"%@",type); }
控制台结果显示,咱们可以截取到其中的类型了:
回归到咱们拿到这些属性类型
的初衷,是为了用字典中的值的类型
与模型中属性的类型
进行对比,想要对比,须要拿到属性的类型
,所以须要将这些编码转换成一个表示类的类,建立一个类用来包装类型。
#import <Foundation/Foundation.h> @interface ScottPropertyType : NSObject /** 是否为id类型 */ @property (nonatomic, readonly, getter=isIdType) BOOL idType; /** 是否为基本数据类型(int、float等) */ @property (nonatomic, readonly, getter=isNumberType) BOOL numberType; /** 是否为bool类型 */ @property (nonatomic, readonly, getter=isBoolType) BOOL boolType; /** 对象类型(若是是基本数据类型,此值为nil) */ @property (nonatomic, readonly) Class typeClass; @end
OC对象能够经过Class
来表示类型,而基本数据类型只能用布尔来标识。
把这些名字和类型遍历出来,确定是为了之后有用,因此须要把它们存起来,因为它们是一个"总体",因此仍是设计一个类将他们包装起来比较好,建立一个包装成员属性的类--ScottProperty
。
#import <Foundation/Foundation.h> @class ScottPropertyType; @interface ScottProperty : NSObject /** 属性名 */ @property (nonatomic, readonly) NSString *name; /** 成员属性的类型 */ @property (nonatomic, readonly) ScottPropertyType *type; @end
这时,代码就能够进行重构了,将属于不一样类的功能封装到对应的类上,让ScottProperty
提供一个类方法用于返回一个将objc_property_t
进行包装的类。
for (int i=0; i<outCount; i++) { objc_property_t property = properties[i]; ScottProperty *propertyObj = [ScottProperty propertyWithProperty:property]; [propertiesArr addObject:propertyObj]; }
propertyWithProperty:
方法实现以下:
@implementation ScottProperty + (instancetype)propertyWithProperty:(objc_property_t)property { return [[ScottProperty alloc] initWithProperty:property]; } - (instancetype)initWithProperty:(objc_property_t)property { if (self = [super init]) { _name = @(property_getName(property)); _type = [ScottPropertyType propertiesWithAttributeString:@(property_getAttributes(property))]; } return self; } @end
ScottPropertyType
也提供类方法用于包装类型:
#import "ScottPropertyType.h" @implementation ScottPropertyType + (instancetype)propertiesWithAttributeString:(NSString *)att { return [[ScottPropertyType alloc] initWithTypeString:att]; } - (instancetype)initWithTypeString:(NSString *)typeString { if (self = [super init]) { NSUInteger loc = 1; NSUInteger len = [typeString rangeOfString:@","].location - loc; NSString *typeCode = [typeString substringWithRange:NSMakeRange(loc, len)]; NSLog(@"%@",typeCode); } return self; } @end
重构完成以后,结构显得更加清晰,更加有利于接下来的工做,下面继续完成typeCode
的提取。
运行重构以后的代码,能够看到和重构以前是同样的:
上面提到的这些类型,是类型编码,在苹果文档中告诉咱们编码对应的类型:
根据这个对应关系的图表,咱们将经常使用的几个编码定义成常量字符串或者宏表示它所对应的类型,利于编码和阅读:
在ScottPropertyType
类定义如下属性类型:
/** * 成员变量类型(属性类型) */ NSString *const ScottPropertyTypeInt = @"i"; NSString *const ScottPropertyTypeShort = @"s"; NSString *const ScottPropertyTypeFloat = @"f"; NSString *const ScottPropertyTypeDouble = @"d"; NSString *const ScottPropertyTypeLong = @"q"; NSString *const ScottPropertyTypeChar = @"c"; NSString *const ScottPropertyTypeBOOL1 = @"c"; NSString *const ScottPropertyTypeBOOL2 = @"b"; NSString *const ScottPropertyTypePointer = @"*"; NSString *const ScottPropertyTypeIvar = @"^{objc_ivar=}"; NSString *const ScottPropertyTypeMethod = @"^{objc_method=}"; NSString *const ScottPropertyTypeBlock = @"@?"; NSString *const ScottPropertyTypeClass = @"#"; NSString *const ScottPropertyTypeSEL = @":"; NSString *const ScottPropertyTypeId = @"@";
并写一个方法用于提取每一个属性的类型:
- (instancetype)initWithTypeString:(NSString *)typeString { if (self = [super init]) { NSUInteger loc = 1; NSUInteger len = [typeString rangeOfString:@","].location - loc; NSString *typeCode = [typeString substringWithRange:NSMakeRange(loc, len)]; [self getTypeCode:typeCode]; } return self; } - (void)getTypeCode:(NSString *)code { if ([code isEqualToString:ScottPropertyTypeId]) { _idType = YES; }else if (code.length > 3 && [code hasPrefix:@"@\""]){ // 去掉@"和",截取中间的类型名称 code = [code substringWithRange:NSMakeRange(2, code.length - 3)]; _typeClass = NSClassFromString(code); _numberType = (_typeClass == [NSNumber class] || [_typeClass isSubclassOfClass:[NSNumber class]]); } // 是否为数字类型 NSString *lowerCode = code.lowercaseString; NSArray *numberTypes = @[ScottPropertyTypeInt, ScottPropertyTypeShort, ScottPropertyTypeFloat, ScottPropertyTypeDouble, ScottPropertyTypeLong, ScottPropertyTypeChar, ScottPropertyTypeBOOL1, ScottPropertyTypeBOOL2]; if ([numberTypes containsObject:lowerCode]) { _numberType = YES; if ([lowerCode isEqualToString:ScottPropertyTypeBOOL1] || [lowerCode isEqualToString:ScottPropertyTypeBOOL2]) { _boolType = YES; } } }
到这里,咱们一个ScottProperty
的骨架大体就搭好了。
在NSObject+ScottProperty
分类中遍历属性的时候,打印属性名和属性类型看看:
for (int i=0; i<outCount; i++) { objc_property_t property = properties[i]; ScottProperty *propertyObj = [ScottProperty propertyWithProperty:property]; [propertiesArr addObject:propertyObj]; NSLog(@"name:%@--type:%@",propertyObj.name,propertyObj.type.typeClass); }
从图中能够看出,属于基本类型的属性打印出来的类型是null
,其余的都能正确打印出对应类型。
当咱们想要使用字典转模型功能的时候,提供一个类方法方便转换,该方法放在NSObject+ScottKeyValue
分类中,该分类负责字典转模型的方法实现。
+ (instancetype)objectWithKeyValues:(id)keyValues { if (!keyValues) return nil; return [[[self alloc] init] setKeyValues:keyValues]; } - (instancetype)setKeyValues:(id)keyValues { NSArray *propertiesArray = [self.class properties]; for (ScottProperty *property in propertiesArray) { ScottPropertyType *type = property.type; Class typeClass = type.typeClass; if (type.isBoolType) { NSLog(@"Bool"); }else if (type.isIdType){ NSLog(@"ID"); }else if (type.isNumberType){ NSLog(@"Number"); }else{ NSLog(@"%@",typeClass); } } return self; }
咱们想要字典转模型的时候,直接以下使用:
NSDictionary *dict = @{@"name":@"Scott", @"icon" : @"lufy.png", @"age" : @"20", @"height" : @1.75, @"money" : @"100.9", @"sex" : @(SexMale), @"gay" : @"ture", }; ScottUser *userModel = [ScottUser objectWithKeyValues:dict];
ok,运行程序,能够看到控制台输出ScottUser
类中各属性对应的类型:
咱们进行下一步:用该属性名做为键去字典中寻找对应的值
伪代码:
[字典 valueForKey:属性名];
此处的属性名会有点问题,例如咱们定义属性名的时候不能是关键字,而若是字典中的key
是涉及到关键字的,那么咱们须要转换,可是也并不是全部的都有这种状况,所以咱们能够想到使用代理。咱们在NSObject+ScottKeyValue
分类中写一个ScottKeyValue
协议,而且让它遵照该协议:
@protocol ScottKeyValue <NSObject> @optional + (NSDictionary *)replacedKeyFromPropertyName; @end
而后咱们提供一个类方法,用于处理将属性名与字典中的key
达到一致。
+ (NSString *)propertyKey:(NSString *)propertyName { NSString *key; if ([self respondsToSelector:@selector(replacedKeyFromPropertyName)]) { key = [self replacedKeyFromPropertyName][propertyName]; } return key ? key : propertyName; }
调用:
// 属性名做为键去寻找对应的值 id value = [keyValues valueForKey:[self.class propertyKey:property.name]]; if (!value) continue; NSLog(@"%@",value);
运行,咱们能够看到已经可以拿到值了:
接下来,咱们拿到值后将值的类型转换为属性对应的数据类型。
首先须要处理数字类型,若是模型的属性是数字类型,即type.isNumberType == YES
,若是字典中的值是字符串类型,须要将其转成NSNumber类型,若是原本就是基本数据类型,则不用进行任何转换。
if (type.isNumberType == YES) { // 字符串-->数字 if ([value isKindOfClass:[NSString class]]) { value = [[[NSNumberFormatter alloc] init] numberFromString:value]; } }
其中有一种状况,是须要进行特殊处理的,当模型的属性是char
类型或者bool
类型时,获取到的编码都是c
,而且bool
还有多是B
编码,它们都对应_boolType
,由于数字类型包含布尔类型,因此bool
类型要在数字类型的条件下进行额外判断。
if (type.isNumberType == YES) { NSString *oldValue = value; // 字符串-->数字 if ([value isKindOfClass:[NSString class]]) { value = [[[NSNumberFormatter alloc] init] numberFromString:value]; if (type.isBoolType) { NSString *lower = [oldValue lowercaseString]; if ([lower isEqualToString:@"yes"] || [lower isEqualToString:@"ture"]) { value = @YES; }else if ([lower isEqualToString:@"no"] || [lower isEqualToString:@"false"]){ value = @NO; } } } } else { // 而后处理其余类型转化成字符串类型的状况: if (typeClass == [NSString class]) { if ([value isKindOfClass:[NSNumber class]]) { if (type.isNumberType) // NSNumber -> NSString value = [value description]; }else if ([value isKindOfClass:[NSURL class]]){ // NSURL -> NSString value = [value absoluteString]; } } } // 最后赋值 [self setValue:value forKey:property.name];
最后咱们调用并打印
ScottUser *userModel = [ScottUser objectWithKeyValues:dict]; NSLog(@"name:%@,icon:%@,age:%d,height:%@,money:%@,sex:%ld,gay:%d",userModel.name,userModel.icon,userModel.age,userModel.height,userModel.money,(long)userModel.sex,userModel.gay);
到这里最简单的字典转模型大体完成了,固然还有不少的细节没有完善,后面再作处理。
定义一个json
字符串转成模型:
#pragma mark - JSON字符串转模型 void keyValues2object1(){ // 1.定义一个json字符串 NSString *jsonString = @"{\"name\":\"scott\",\"icon\":\"lufy.png\",\"age\":20}"; // 2.将json字符串转为LZUser模型 ScottUser *user = [ScottUser objectWithKeyValues:jsonString]; // 3.打印模型属性 NSLog(@"name=%@, icon=%@, age=%d",user.name,user.icon,user.age); }
运行程序,这时程序会华丽丽的崩溃,由于程序原来只对字典类型做了处理:
// 咱们能够定位到程序崩溃在这里 id value = [keyValues valueForKey:[self.class propertyKey:property.name]];
因此在这以前须要将JSON
转成Foundation
框架中的对象,苹果提供了强大的NSJSONSerialization
,利用它,在刚开始传入字典/JSON
字符串的时候将其进行转换。
- (instancetype)setKeyValues:(id)keyValues { keyValues = [keyValues JSONObject]; NSArray *propertiesArray = [self.class properties]; ...... }
该方法的实现以下,若是当前是字符串,则转换成NSData
再进行序列化。
- (id)JSONObject { id foundationObj; if ([self isKindOfClass:[NSString class]]) { NSString *str = (NSString *)self; foundationObj = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:nil]; }else if ([self isKindOfClass:[NSData class]]){ foundationObj = [NSJSONSerialization JSONObjectWithData:(NSData *)self options:kNilOptions error:nil]; } // 若是foundationObj有值,则返回foundationObj,不然返回self return foundationObj ? : self; }
此时,运行程序,OK,可以看到控制台能正确输入结果:
定义一个模型中包含模型的复杂字典:
NSDictionary *dict = @{@"text":@"是啊,今每天气确实不错!", @"user":@{ @"name":@"scott", @"icon":@"lufy.png" }, @"retweetedStatus":@{ @"text":@"是啊,今每天气确实不错", @"user":@{ @"name":@"scott_status", @"icon":@"lufy_status.png" } } };
对待这种字典的思路,应该想到递归,当碰到模型中的属性类型是一个模型类时,将字典中的value
做为字典处理,而后再调用字典转模型的方法返回一个模型类,因此在包装类型时还要有个属性表示它是不是自定义的模型类,才能做为依据继续递归,判断的方法是看它是否来自于Foundation框架
的类。
在ScottPropertyType
中添加一个属性:
/** 是否来源于Foundation框架,好比NSString,NSArray等 */ @property (nonatomic, readonly, getter=isFromFoundation) BOOL fromFoundation;
在- (void)getTypeCode:(NSString *)code
方法中添加这样一条:
else if (code.length > 3 && [code hasPrefix:@"@\""]){ // 去掉@"和",截取中间的类型名称 code = [code substringWithRange:NSMakeRange(2, code.length - 3)]; _typeClass = NSClassFromString(code); _numberType = (_typeClass == [NSNumber class] || [_typeClass isSubclassOfClass:[NSNumber class]]); // 判断是否来自于foundation框架 _fromFoundation = [NSObject isClassFromFoundation:_typeClass]; }
在NSObject+ScottProperty
分类中添加一个类方法:
// 用于判断当前类是否来自于foundation框架 + (BOOL)isClassFromFoundation:(Class)c;
那么问题来了,若是判断是否来自于Foundation框架
呢? 下图展现了Foundation框架(NSObject部分)
下的类结构:
用一个NSSet
(比用NSArray
检索效率更高),返回一些经常使用基本的Foundation框架
下继承自NSObject
的类。
static NSSet *foundationClasses_; + (NSSet *)foundationClass { if (foundationClasses_ == nil) { foundationClasses_ = [NSSet setWithObjects:[NSURL class], [NSDate class], [NSValue class], [NSData class], [NSArray class], [NSDictionary class], [NSString class], [NSMutableString class], nil]; } return foundationClasses_; }
因此判断是不是foundation框架
的类方法具体实现:
+ (BOOL)isClassFromFoundation:(Class)c { // 由于foundationClasses_里面的类都是继承NSObject,所以NSObject不能放到上面的集合,须要额外作判断 if (c == [NSObject class]) return YES; __block BOOL result = NO; [[self foundationClass] enumerateObjectsUsingBlock:^(Class foundationClass, BOOL *stop) { if ([c isSubclassOfClass:foundationClass]) { result = YES; *stop = YES; } }]; return result; }
获得结果后,须要在NSObject+ScottKeyValue
分类中的setKeyValues:
方法中添加以下
// 若是不是来自foundation框架的类而且不是基本数据类型 ,则递归,若是是基本数据类型,typeClass为nil if (!type.isFromFoundation && typeClass) { value = [typeClass objectWithKeyValues:value]; }
到这里,复杂字典转模型就算是完成了,具体调用的过程看源码文章结尾会给地址。
稍微复杂的一种状况是一个字典里面带有数组:
NSDictionary *dict = @{ @"statuses" : @[ @{ @"text" : @"今每天气真不错!", @"user" : @{ @"name" : @"Rose", @"icon" : @"nami.png" } }, @{ @"text" : @"明天去旅游了", @"user" : @{ @"name" : @"Jack", @"icon" : @"lufy.png" } } ], @"ads" : @[ @{ @"image" : @"ad01.png", @"url" : @"http://www.baidu.com" }, @{ @"image" : @"ad02.png", @"url" : @"http://www.sina.com" } ], @"totalNumber" : @"2014", @"previousCursor" : @"13476589", @"nextCursor" : @"13476599" };
上面定义了一个字典,建立一个ScottStatusResult
模型,里面有两个数组,另外还有其余3个键:
#import <Foundation/Foundation.h> @interface ScottStatusResult : NSObject /** 存放着某一页微博数据(里面都是Status模型) */ @property (nonatomic, strong) NSMutableArray *statuses; /** 存放着一堆的广告数据(里面都是Ad模型) */ @property (nonatomic, strong) NSArray *ads; /** 总数 */ @property (nonatomic, strong) NSNumber *totalNumber; /** 上一页的游标 */ @property (nonatomic, assign) long long previousCursor; /** 下一页的游标 */ @property (nonatomic, assign) long long nextCursor; @end
对于一个数组来讲,你必需要告诉方法里面装的是什么模型,才能将字典中值为数组的成员转成模型。
在MJExtension
中,提供了两种方式进行处理。
方式一:调用NSObject
分类中的类方法:
[ScottStatusResult setupObjectClassInArray:^NSDictionary *{ return @{ @"statuses" : @"ScottStatus", // 或者 @"statuses" : [ScottStatus class], @"ads" : @"ScottAd" // 或者 @"ads" : [ScottAd class] }; }];
方式二:在模型的.m文件中实现方法供回调:
+ (NSDictionary *)objectClassInArray { return @{ @"statuses" : @"ScottStatus", // 或者 @"statuses" : [ScottStatus class], @"ads" : @"ScottAd" // 或者 @"ads" : [ScottAd class] }; }
原理上都差很少,都是经过代码进行回调,这个主要实现方式二。
在NSObject+ScottKeyValue
分类中的ScottKeyValue
协议中添加一个方法
+ (NSDictionary *)objectClassInArray;
在NSObject+ScottKeyValue
分类中的setKeyValues:
方法中添加一种类型判断
// 若是不是来自foundation框架的类而且不是基本数据类型 ,则递归,若是是基本数据类型,typeClass为nil if (!type.isFromFoundation && typeClass) { value = [typeClass objectWithKeyValues:value]; }else if ([self.class respondsToSelector:@selector(objectClassInArray)]){ // 看该类是否实现了objectClassInArray方法 id objectClass; objectClass = [self.class objectClassInArray][property.name]; // 若是是NSString类型 if ([objectClass isKindOfClass:[NSString class]]) { objectClass = NSClassFromString(objectClass); } if (objectClass) { // 返回一个装了模型的数组 value = [objectClass objectArrayWithKeyValuesArray:value]; } }
返回一个装了模型的数组方法实现:
/** * 根据字典/JSON返回模型数组 * * @param keyValuesArray 字典/JSON数组 * * @return 模型数组 */ + (NSMutableArray *)objectArrayWithKeyValuesArray:(id)keyValuesArray { if ([self isClassFromFoundation:self]) return keyValuesArray; keyValuesArray = [keyValuesArray JSONObject]; NSMutableArray *modelArray = [NSMutableArray array]; // 遍历 for (NSDictionary *keyValues in keyValuesArray) { id model; model = [self objectWithKeyValues:keyValues]; if (model) { [modelArray addObject:model]; } } return modelArray; }
到这里,字典数组转模型就算是完成了,具体调用的过程看源码文章结尾会给地址。
在实际开发中,服务器一般返回一个字段名id
,或者description
的JSON
数据,而这两个名字在OC
中有特殊含义,在定义属性的时候并不能使用这类名称.这时属性名与字典key
再也不是直接对应的关系,须要加入一层转换。
这个在前面用该属性名做为键去字典中寻找对应的值讲到过,在次就再也不重复讲解。
将5个字典转模型的例子同时运行,在NSObject+ScottProperty
分类中的+ (NSArray *)properties
方法中添加一句打印NSLog(@"%@调用了properties方法",[self class]);
。另外,以前的例子都是有内存泄露的,这里添加了free(properties);
修复了这个问题。
+ (NSArray *)properties { NSLog(@"%@调用了properties方法",[self class]); NSMutableArray *propertiesArr = [NSMutableArray array]; unsigned int outCount = 0; objc_property_t *properties = class_copyPropertyList(self, &outCount); for (int i=0; i<outCount; i++) { objc_property_t property = properties[i]; ScottProperty *propertyObj = [ScottProperty propertyWithProperty:property]; [propertiesArr addObject:propertyObj]; } free(properties); return propertiesArr; }
运行程序,能够看到控制台输出:
能够看到,不少的类都不止一次调用了获取属性的方法,对于一个类来讲,要获取它的所有属性,只要获取一次就够了.获取到后将结果缓存起来,下次就没必要进行没必要要的计算。
下面进行优化:
// 设置一个全局字典用来将类的属性都缓存起来 static NSMutableDictionary *cachedProperties_; + (void)load { cachedProperties_ = [NSMutableDictionary dictionary]; }
将方法改写为:
+ (NSArray *)properties { NSMutableArray *propertiesArr = cachedProperties_[NSStringFromClass(self)]; if (!propertiesArr) { NSLog(@"%@调用了properties方法",[self class]); propertiesArr = [NSMutableArray array]; unsigned int outCount = 0; objc_property_t *properties = class_copyPropertyList(self, &outCount); for (int i=0; i<outCount; i++) { objc_property_t property = properties[i]; ScottProperty *propertyObj = [ScottProperty propertyWithProperty:property]; [propertiesArr addObject:propertyObj]; // NSLog(@"name:%@--type:%@",propertyObj.name,propertyObj.type.typeClass); // 为了之后方便使用,将C字符串转化成OC对象 // char *name = property_getName(property); // NSString *name = @(property_getName(property)); // char *att = property_getAttributes(property); // NSString *att = @(property_getAttributes(property)); // NSUInteger loc = 1; // NSUInteger len = [att rangeOfString:@","].location - loc; // NSString *type = [att substringWithRange:NSMakeRange(loc, len)]; // NSLog(@"%@",type); } free(properties); cachedProperties_[NSStringFromClass(self)] = propertiesArr; } return propertiesArr; }
此时,控制台输出:
能够看出每个类只通过一次就能够获取全部属性。
除了缓存属性外,提取类型编码的过程也能够进一步缓存优化性能。
在下面的方法中加上一句打印:
- (void)getTypeCode:(NSString *)code { NSLog(@"%@",code); ... }
能够看到控制台输出:
能够看到一些经常使用的类型例如NSString
屡次调用了该方法。提取类型时,只要知道类名(在这里也就是typeCode
),一个ScottPropertyType
就已经能够肯定了。
重写了- initWithTypeString:
方法:
static NSMutableDictionary *cacheTypes_; + (void)load { cacheTypes_ = [NSMutableDictionary dictionary]; } + (instancetype)propertiesWithAttributeString:(NSString *)att { return [[ScottPropertyType alloc] initWithTypeString:att]; } - (instancetype)initWithTypeString:(NSString *)typeString { if (self = [super init]) { NSUInteger loc = 1; NSUInteger len = [typeString rangeOfString:@","].location - loc; NSString *typeCode = [typeString substringWithRange:NSMakeRange(loc, len)]; if (!cacheTypes_[typeCode]) { [self getTypeCode:typeCode]; cacheTypes_[typeCode] = self; } } return self; }
输出结果:
OK,到这里,咱们的解读也算是完成了,因为是下班以后写的,因此花费了4天的时间,终于把此篇文章写完了,欢迎你们点评并讨论。
最后代码地址:--->戳这里