公司项目以前的model层代码是我使用JSON工具直接生成Objective-C代码的,当时仍是以为至关省事的,毕竟我经历过无model层的NSDictionary“黑暗”时期。可是随着项目的推动,问题开始慢慢显现出来。git
因而,在一个多月前,我在Objc.io上看到说起了Mantle,花了一点时间看了一下,决定先在分支上全套改用Mantle。使用了一段时间,性能没形成什么瓶颈,稳定性仍是能够接受的。后来也基于Mantle、MK和RAC,把网络的请求整合在一块儿,在新项目上全面使用。一开始时也是没什么问题的,但后来我也逐渐发现了Mantle虽好,但不至于能解决一切问题。最近看到了《为何唱吧iOS 6.0选择了Mantle》文章,我决定写博客记下一些model层的坑。github
简要分析数组
先来简要分析一下各类构建model层方法的优势和缺点:网络
1、工具生成model数据结构
优势:框架
一、简单易用,新手也能够10秒上手工具
二、有必定的容错代码性能
三、代码生成相对工整和规范,部分工具还能够选择是否使用ARCui
四、生成简单model耗时少spa
缺点:
一、工具生成的类名或者属性名不太符合要求,每每须要自行修改,可是修改起来至关麻烦,却须要至关专一以防有什么地方忘记修改。
二、生成的代码至关冗长。
三、对适应字段变化比较麻烦,一旦属性须要修改字段时,要么人工修改,要么从新生成,可是极有可能须要重复缺点1的步骤。
四、model之间一些继承关系仍是须要自行修改继承来实现。
2、基于运行时生成的model(Mantle这类)
优势:
一、减小大量模版代码
二、修改字段映射时至关简便
三、扩展时相对方便
四、能够实现更多复杂的映射关系和数值转换
五、调试时的异常能够较好地发现问题
六、实现了NSCopying和NSCoding协议,能够轻松序列化
缺点:
一、基于运行时属性映射,对性能有必定影响
二、有部分容错处理须要自行解决,不然极可能崩溃(下文详解)
三、框架代码很多
3、NSDictionary型model
优势:
一、无需任何基础,直接可用
二、容错性相对较高
三、无视任何数据结构,均能适应
缺点:
一、维护成本昂贵
二、编译器没法检查拼写,须要定义大量key的常量,不然极其容易写错
三、调试相对麻烦
其实,NSDictionary型model仍是有必定用途的,毕竟有些状况下,不须要浪费精力去构建一个很短小或者很快就会被释放的model。但大多数状况下,仍是须要去构建一个合理的model,来保证项目的健壮性和开发效率。之前,我老大和我说,iOS应用MVC三层,M这一层其实服务端已经帮你完成了大部分,来到客户端再本身处理model,既消耗性能又下降了开发效率。当时,我以为仍是比较正确的,但随着MVC的C变得臃肿不堪,M变得愈来愈轻量的时候。不少东西都耦合在controller,model这层能作好的话,就能必定程度上减轻了controller的复杂度。加上工做了之后发现,一个只有NSDictionary,无真正model的商业应用,真的很是不利于维护。
基于上述的种种理由,我仍是决定了正式全面使用Mantle。但Mantle不是万能的,我仍是遇到了几个问题。
null值
若是你的属性是基本数值类型的话,JSON返回一个null值,那么在Mantle生成model的时候,果断崩溃了。这个问题和解决方案跟《为何唱吧iOS 6.0选择了Mantle》中的同样,model中实现一下setNilValueForKey
:方法便可。建议使用基类继承,那么写一次这个方法就全部model都解决了这个问题。
键值的合理映射
1 { 2 "code": 1 3 "result":{ 4 "access_token":"m_xxxxxxx", 5 "user_id":1111 6 } 7 }
例如上述JSON,假设整个JSON是一个model,那么若是直接按照JSON的格式来映射,就要新建一个“result”额外的model类。但或许不须要这么繁复,其实能够这么写
1 + (NSDictionary *)JSONKeyPathsByPropertyKey 2 { 3 return @{@"accessToken": @"result.access_token", 4 @"userId": @"result.user_id"}; 5 }
这样作就能够很方便地映射到对应的属性上,同时也不须要额外新建一个model类。这里为何没写“code”的映射呢,由于若是属性名和JSON的键名一致时,是能够省略不写映射的,具体你们能够看看Mantle的源码。
值的类型问题
这个问题是最棘手的,不能说后台坑队友,可是JSON的数值类型和文档不符乃屡见不鲜。做为和用户最近的前线,我只能想尽办法去收尾,不能彻底听任无论吧。常规的方法不外乎如下几种:
一、转换model前先进行预处理JSON数据,把类型不符的值转换或者删除掉
二、为这些容易崩溃的值,都写上NSValueTransformer的转换
这些的确都能解决问题,可是效率就降低了不少,你得关注各类各样的可能出现的状况。相信我,要是你这么作,你连睡觉都睡很差。
我列举一下几种类型不符会致使的异常:
一、属性是BOOL类型,返回值是string类型。
二、属性是NSString类型,返回值是number类型,Mantle只会转换出NSNumber类型。你调用length等NSString的专用方法时,你懂的。
三、使用了相似上面"result.access_token"的映射,但返回值不是object类型(例如array类型)。
四、属性是NSArray,使用了转换,返回值是object。
放心,实际状况中,绝对不会只有上述4种可能的。不过幸运的是,Mantle帮你们处理了一、三、4这些状况(若是object和array都是使用了转换方法的话,在转换的时候会处理这些异常的),只会在调试模式下抛出异常,Release的时候是不会崩溃的。若是你们不想在调试的时候被这些异常打断的话,能够注释掉MTLValidateAndSetValue这个方法中的对应代码。
接下来你们可能以为太匪夷所思了,为何一个小小的客户端还得由于用个Mantle就要去规避这么多陷阱。我就是遇到这么多陷阱,想到了解决方法,本身也是成长了。针对JSON的类型,我有了如下的考虑:
JSON其实就是只有4种类型,string、number(int 、bool…)、object、array。在Objective-C对应也就是,NSString、NSNumber、NSDictionary、NSArray,所以要规避类型问题也是从这几个类着手。这里要说一下为何属性有int、bool等,我只归了一类NSNumber。由于实质上,Mantle只是转换了NSNumber类型的对象出来,在setValue的时候,是由系统根据类型调用了对应的NSNumber方法。因为number和string的类型错误是最多见,同时也是最隐蔽的(调用相似intValue的方法看不出端倪),为此我写了一个AvoidMTLModelCrash的category,github上的地址,使用了这个category之后,关于NSString和NSNumber类型问题的崩溃基本均可以解决。若是是NSArray或者NSDictionary设置到NSString的属性也是没法检测的,可是面对如此严重的类型问题,我建议仍是和小伙伴一块儿坐下来,好好谈谈“人生”吧。
灵活的转换
例如某些接口返回的数据是数组,但不少时候只须要用到这个数组的第一个元素。咱们能够直接将数组里面的一个元素影射出来。
1 + (NSValueTransformer *)pointListJSONTransformer 2 { 3 return [MTLValueTransformer transformerWithBlock:^id(NSArray *array) { 4 return [array firstObject]; 5 }]; 6 }
除了属性映射和每一个属性固定的类型转换,MTLJSONSerializing的协议还有classForParsingJSONDictionary这么一个方法能够改变解释后的类。例如B、C均继承A,用Mantle生成A类对象。A类能够经过这个方法,选择不一样的子类生成对象。可是对外可见的接口也是A类的接口,这样就相似NSArray同样(NSArray其实也是有不少子类的)。
序列化
因为,Mantle已经实现了序列化的协议,因此不须要额外的代码便可直接序列化。可是,若是你的属性中有不能直接序列化的类型或者不想使用序列化的时候,能够声明为私有成员,或者使用BlockKit的
小小的总结
model的框架对项目影响深远,若是为了避免在之后的项目留下坑,那model这一层仍是要多加思考,省得形成了维护不能的境地。