转自:http://devlxx.com/ioskai-fa-zhong-de-shu-ju-chu-li/
在一般的iOS中,有很大部分是把从服务端获取到的数据加载到UI上。但是在这个过程中如果处理,才能达到良好的可读性、可扩展、低耦合等效果类?
现在iOS中很大部分都是对JSON数据的处理,所以本文主要拿JSON进行说明。
1、NSNull造成的Crash
记得在刚开始学习iOS的时候,有一次程序莫名Crash掉了。检查后发现原来是本来返回的数据是NSString的,结果解析后得到的是一个NSNull对象,然后将其赋值给一个UILabel对象的text,然后就造成NSInvalidArgumentException异常导致crash。相信不少开发者,都被NSNull坑过,最常见的是服务器返回的json里面,说好的字典、数组、数字,结果返回的是空值。 我们一般做一次类型判断:
#pragma mark - 如果为NSNull,则转换为nil
- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict
{
id object = [dict objectForKey:aKey];
return [object isEqual:[NSNull null]] ? nil : object;
}
//解析数据
self.title = [self objectOrNilForKey:@”title” fromDictionary:dict];
这样就不会出现把NSNull当做NSString、NSNumber、NSArray等操作而造成的Crash。 后来感觉这个不够优雅,就把它改成了Category(也可改成宏):
@interface NSDictionary (TypeChecking)
- (id)objectOrNilForKey:(id)aKey;
@end
@implementation NSDictionary (TypeChecking)
- (id)objectOrNilForKey:(id)aKey
{
id object = [self objectForKey:aKey];
return [object isEqual:[NSNull null]] ? nil : object;
}
@end
//使用
self.title = [dict objectOrNilForKey:@”title”];
但是后来,了解了OC强大的Runtime。发现原来利用Rumtime我们可以更优雅的处理这个问题:
#define NSNullObjects @[@””,@0,@{},@[]]
@interface NSNull (InternalNullExtention)
@end
@implementation NSNull (InternalNullExtention)
(NSMethodSignature)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature signature = [super methodSignatureForSelector:selector];
if (!signature) {for (NSObject *object in NSNullObjects) { signature = [object methodSignatureForSelector:selector]; if (signature) { break; } }}
return signature;
}(void)forwardInvocation:(NSInvocation )anInvocation
{
SEL aSelector = [anInvocation selector];
for (NSObject object in NSNullObjects) {if ([object respondsToSelector:aSelector]) { [anInvocation invokeWithTarget:object]; return; }}
[self doesNotRecognizeSelector:aSelector];
}
@end
很高端的做法,通过消息转发机制来解决这个问题。如果无法理解这段代码的含义,我们可以看这篇博文Objective-C Runtime。
常规数据解析的不足
假如我们有这样一段JSON数据:
{
“lng” : 121.480263,
“dataSource” : “SHANGHAI”,
“lat” : 31.236295,
“menus” : “测试”,
“name” : “上海”
}
我们一般是创建一个对应的实体类来解析:
@interface City : NSObject
@property (nonatomic, assign) double lng;
@property (nonatomic, strong) NSString dataSource;
@property (nonatomic, assign) double lat;
@property (nonatomic, strong) NSString menus;
@property (nonatomic, strong) NSString *name;
- (instancetype)modelObjectWithDictionary:(NSDictionary *)dict;
- (instancetype)initWithDictionary:(NSDictionary *)dict;
- (NSDictionary *)dictionaryRepresentation;
@end
#import “City.h”
NSString const kCityLng = @”lng”;
NSString const kCityDataSource = @”dataSource”;
NSString const kCityLat = @”lat”;
NSString const kCityMenus = @”menus”;
NSString *const kCityName = @”name”;
@interface City ()
- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict;
@end
@implementation City
@synthesize lng = _lng;
@synthesize dataSource = _dataSource;
@synthesize lat = _lat;
@synthesize menus = _menus;
@synthesize name = _name;
- (instancetype)modelObjectWithDictionary:(NSDictionary *)dict
{
return [[self alloc] initWithDictionary:dict];
}
(instancetype)initWithDictionary:(NSDictionary *)dict
{
self = [super init];if(self && [dict isKindOfClass:[NSDictionary class]]) {
self.lng = [[self objectOrNilForKey:kCityLng fromDictionary:dict] doubleValue]; self.dataSource = [self objectOrNilForKey:kCityDataSource fromDictionary:dict]; self.lat = [[self objectOrNilForKey:kCityLat fromDictionary:dict] doubleValue]; self.menus = [self objectOrNilForKey:kCityMenus fromDictionary:dict]; self.name = [self objectOrNilForKey:kCityName fromDictionary:dict];}
return self;
}
(NSDictionary )dictionaryRepresentation
{
NSMutableDictionary mutableDict = [NSMutableDictionary dictionary];
[mutableDict setValue:[NSNumber numberWithDouble:self.lng] forKey:kCityLng];
[mutableDict setValue:self.dataSource forKey:kCityDataSource];
[mutableDict setValue:[NSNumber numberWithDouble:self.lat] forKey:kCityLat];
[mutableDict setValue:self.menus forKey:kCityMenus];
[mutableDict setValue:self.name forKey:kCityName];return [NSDictionary dictionaryWithDictionary:mutableDict];
}(NSString *)description
{
return [NSString stringWithFormat:@”%@”, [self dictionaryRepresentation]];
}
#pragma mark - Helper Method
- (id)objectOrNilForKey:(id)aKey fromDictionary:(NSDictionary *)dict
{
id object = [dict objectForKey:aKey];
return [object isEqual:[NSNull null]] ? nil : object;
}
#pragma mark - NSCoding Methods
(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
self.lng = [aDecoder decodeDoubleForKey:kCityLng];
self.dataSource = [aDecoder decodeObjectForKey:kCityDataSource];
self.lat = [aDecoder decodeDoubleForKey:kCityLat];
self.menus = [aDecoder decodeObjectForKey:kCityMenus];
self.name = [aDecoder decodeObjectForKey:kCityName];
return self;
}(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeDouble:_lng forKey:kCityLng];
[aCoder encodeObject:_dataSource forKey:kCityDataSource];
[aCoder encodeDouble:_lat forKey:kCityLat];
[aCoder encodeObject:_menus forKey:kCityMenus];
[aCoder encodeObject:_name forKey:kCityName];
}(id)copyWithZone:(NSZone )zone
{
City copy = [[City alloc] init];
if (copy) {copy.lng = self.lng; copy.dataSource = [self.dataSource copyWithZone:zone]; copy.lat = self.lat; copy.menus = [self.menus copyWithZone:zone]; copy.name = [self.name copyWithZone:zone];}
return copy;
}
@end
虽然说, 有这样的工具(JSON Accelerator)直接根据JSON来生成对应的类,但是我现在能想到的就有这些问题存在:
如果突然服务端说要加/减几个字段,改比较麻烦。
生成大量的同类型代码,不好维护
类嵌类的时候容易导致死循环
数据转换处理麻烦。如NSNumber直接转NSString,会出现精度问题。
其他问题。
那么我们如何解决这个问题类?
解决方案
在上面的City类中,我们可以发现,这其实是一个模板。如果我们根据这个模板去生成各种类,那么就会存在大量类似的代码。 那么我们如何把他们优雅的聚合在一起,做到高内聚,低耦合类? 答案就是Runtime。 而且轮子别人已经帮我们做好了。 现在在Github上有三个关于这方面的库是比较好的。分别是MJExtension、JSONModel、Mantle。他们的作用都是用于JSON解析。
Mantle 是老牌的,使用人数最多,久经考验。
JSONModel 使用人数居中,带有网络请求部分。
MJExtention 开源不久,但是使用人数也不少,性能最好,使用最简单(不需要你的模型类继承任何特殊基类,毫无污染,毫无侵入性),而且开源者是中国的,交流也比较方便。
在这里JSONModel及Mantle就不做介绍了,就对MJExtention做一个简单的说明,有兴趣的话可以点击链接直接在Github上看官方的文档,是中文的,而且写的很详细。 下面依然拿City类做例子:
简单使用
//只需要引入头文件,就可以使用这个库的各个方法了。
#import “MJExtension.h”
@interface City : NSObject
@property (nonatomic, assign) double lng;
@property (nonatomic, strong) NSString dataSource;
@property (nonatomic, assign) double lat;
@property (nonatomic, strong) NSString menus;
@property (nonatomic, strong) NSString *name;
@end
#import “City.h”
@implementation City
@end
只需要这样就可以实现上面那个类的全部功能了。
字段转模型
// 将字典转City模型
City city = [City objectWithKeyValues:dict];
将模型转为字典
// 新建模型
City city = [[City alloc] init];
city.dataSource = @”SHANGHAI”;
city.menus = @”测试”;
city.name = @”上海”;
city.lng = 121.480263;
city.lat = 31.236295;
// 将模型转为字典
NSDictionary cityDict = city.keyValues;
NSLog(@”%@”, cityDict);
/
{
“lng” : 121.480263,
“dataSource” : “SHANGHAI”,
“lat” : 31.236295,
“menus” : “测试”,
“name” : “上海”
}
*/
当然这些都只是简单用法,这个库还支持这些功能:
字典数组(JSON Array) –> 模型数组(Model Array)
模型数组(Model Array) –> 字典数组(JSON Array)
模型中嵌套模型
模型中有个数组属性,数组里面又要装着其他模型
模型中的属性名和字典中的key不相同(或者需要多级映射)
更多用法请参考对应的文档及自己摸索咯。