字典转模型需要吗?
如果是
Swift编程,要不要确实是一个问题;如果是Object-C,那么大概率需要。动态特性很有优势,这是一个很典型的应用场景。后台返回的字段确实太多,并且还有很多没有用的字段,有这么一个自动转模型的工具,确实方便。
用模型比直接用字典在使用中要方便很多。
选哪个第三方库?
自己写这样一个工具?难度有点大,尽量不要。还是选择成熟的第三方库比较靠谱。
YYModel作者自己写的比较文章能帮助我们决策,非常赞:
iOS JSON 模型转换库评测类的静态方法是使用起来最方便的,不过这样接口的第三方库没有。
类别,确实是
Object-C中的一大特色,感觉有点黑科技,不过使用起来确实还是蛮方便的。利用已有的对象,直接使用方法,省去了特意创建对象的麻烦。直接在
NSObject上添加方法,而这又是Object-C中所有对象的基类,应用范围确实非常广。简单好用,在实际使用中也没有遇到不适用的场景,所以就选这个了。
YYModel
如何封装?
想把类别接口转换为类的静态方法,感觉没有必要。还要多传一个类名的字符串,增加了麻烦。
既然保留类别的方式,那么就改一下类别的名字,比如
NSObject+KJTModel
封装哪些方法?
- 网络返回的
json对象一般有NSDictionary,NSStringorNSData三种,工程中使用的是NSDictionary,那么就不要用id类型参数了,直接把类型确定为NSDictionary,所以封装接口+ (nullable instancetype)yy_modelWithDictionary:(NSDictionary *)dictionary;
当然,名字要换一个,比如下面这样的:
+ (nullable instancetype)kjtModelWithDictionary:(NSDictionary *)dictionary {
return [self yy_modelWithDictionary:dictionary];
}
这是自定义类的静态方法,不是
YYModel的静态方法,也不是KJTModel的静态方法。比如User *user = [User kjtModelWithDictionary: dictionary];
- 函数
- (nullable id)yy_modelToJSONObject;不封装。一般输入参数比较简单,比如一个id之类的。另外输入参数转json对象,一般网络库在创建NSRULRequest的时候都会做,这里没有必要重复
封装哪些方便方法?
下面这些方便方法是YYModel顺便提供的,不过在实际应用中会用到,因此这里也中转一下。
- 自定义类型
copy,这样就没有必要一行一行写了。
- (nullable id)kjtObjectCopy {
return [self yy_modelCopy];
}
- 序列化或者缓存要实现的
KVC编解码函数:
- (void)kjtEncodeWithCoder:(NSCoder *)aCoder {
[self yy_modelEncodeWithCoder:aCoder];
}
- (id)kjtInitWithCoder:(NSCoder *)aDecoder {
return [self yy_modelInitWithCoder:aDecoder];
}
- 自定义对象的比较:
- (BOOL)kjtIsEqualToObject:(id)object {
return [self yy_modelIsEqual:object];
}
- 自定义对象的成员名称,以后断点调试的时候,只要
[self kjtObjectDescription]一下,就再也不仅仅是一个看不懂的指针地址了:
- (NSString *)kjtObjectDescription {
return [self yy_modelDescription];
}
NSArray的方法
有些时候,需要返回一个列表,回来的结果可能是这样的:
@{@"list":
@[@{@"name": @"张三"},
@{@"name": @"李四"},
@{@"name": @"王五"}]
};
这时候如果定义两个Model,第一级只有一个list成员,感觉意义不大。所以,不如先把list的数组先拿出来,再转Model,更有意义一点。
所以,YYModel提供了一个NSArray的类别,来满足这种需求,感觉挺有用的。
+ (nullable NSArray *)kjtModelArrayWithClass:(Class)cls json:(id)json {
return [NSArray yy_modelArrayWithClass:cls json:json];
}
这里是指
NSArray的静态方法[NSArray kjtModelArrayWithClass:cls json: json];
NSObject的协议函数:
增加了几个可选的协议函数,让用户自定义,实现一些有用功能。
@protocol YYModel <NSObject>
@optional
...
@end
- 如果后台定义的字段叫做
id该怎么办呢?这是Object-C中的关键字。最好的方法当然是跟后台协商,把名字换成比如userId之类的。如果后台不配合,那么可以实现下面的协议函数:
@implementation User
+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"id" : @"UserId"};
}
@end
函数的原型是这样的,作者也给出了很好的注释:
/**
Custom property mapper.
@discussion If the key in JSON/Dictionary does not match to the model's property name,
implements this method and returns the additional mapper.
Example:
json:
{
"n":"Harry Pottery",
"p": 256,
"ext" : {
"desc" : "A book written by J.K.Rowling."
},
"ID" : 100010
}
model:
@interface YYBook : NSObject
@property NSString *name;
@property NSInteger page;
@property NSString *desc;
@property NSString *bookID;
@end
@implementation YYBook
+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"name" : @"n",
@"page" : @"p",
@"desc" : @"ext.desc",
@"bookID": @[@"id", @"ID", @"book_id"]};
}
@end
@return A custom mapper for properties.
*/
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;
- 另外一个是容器字段,比如有个字段是
NSArray类型的books,那么books包含哪些字段呢?这个作者提供了另外一个协议函数来描述,注释也很丰富:
/**
The generic class mapper for container properties.
@discussion If the property is a container object, such as NSArray/NSSet/NSDictionary,
implements this method and returns a property->class mapper, tells which kind of
object will be add to the array/set/dictionary.
Example:
@class YYShadow, YYBorder, YYAttachment;
@interface YYAttributes
@property NSString *name;
@property NSArray *shadows;
@property NSSet *borders;
@property NSDictionary *attachments;
@end
@implementation YYAttributes
+ (NSDictionary *)modelContainerPropertyGenericClass {
return @{@"shadows" : [YYShadow class],
@"borders" : YYBorder.class,
@"attachments" : @"YYAttachment" };
}
@end
@return A class mapper.
*/
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
另外还有几个,比如黑白名单之类的,感觉作用没有前面两个普遍,这里就不列举了。
这些协议函数,只要用户在自己的自定义类型里面定义了,在
YYModel内部进行转化的时候,会调用到。如果要封装,换函数名字是不行的,换一个协议的名字倒是可以了。比如,创建一个头文件KJTModelProtocol.h将上面那两个协议函数的名字复制过来,起到类似函数说明的作用。
//
// KJTModelProtocol.h
// HaiLeBao
//
// Created by zxs on 2018/7/30.
// Copyright © 2018年 KJT. All rights reserved.
//
#ifndef KJTModelProtocol_h
#define KJTModelProtocol_h
/**
If the default model transform does not fit to your model class, implement one or
more method in this protocol to change the default key-value transform process.
There's no need to add '<YYModel>' to your class header.
*/
// 这里只是换了一下协议的名字,也不影响原来的名称YYModel
@protocol KJTModelProtocol <NSObject>
@optional
/**
Custom property mapper.
@discussion If the key in JSON/Dictionary does not match to the model's property name,
implements this method and returns the additional mapper.
Example:
json:
{
"n":"Harry Pottery",
"p": 256,
"ext" : {
"desc" : "A book written by J.K.Rowling."
},
"ID" : 100010
}
model:
@interface YYBook : NSObject
@property NSString *name;
@property NSInteger page;
@property NSString *desc;
@property NSString *bookID;
@end
@implementation YYBook
+ (NSDictionary *)modelCustomPropertyMapper {
return @{@"name" : @"n",
@"page" : @"p",
@"desc" : @"ext.desc",
@"bookID": @[@"id", @"ID", @"book_id"]};
}
@end
@return A custom mapper for properties.
*/
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;
/**
The generic class mapper for container properties.
@discussion If the property is a container object, such as NSArray/NSSet/NSDictionary,
implements this method and returns a property->class mapper, tells which kind of
object will be add to the array/set/dictionary.
Example:
@class YYShadow, YYBorder, YYAttachment;
@interface YYAttributes
@property NSString *name;
@property NSArray *shadows;
@property NSSet *borders;
@property NSDictionary *attachments;
@end
@implementation YYAttributes
+ (NSDictionary *)modelContainerPropertyGenericClass {
return @{@"shadows" : [YYShadow class],
@"borders" : YYBorder.class,
@"attachments" : @"YYAttachment" };
}
@end
@return A class mapper.
*/
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
@end
#endif /* KJTModelProtocol_h */
当然,这个文件完全是可有可无的,不提供完全没问题。
工程目录:
最后的输出文件可以叫做KJTModel.h,作为整体KJTKit.h的一部分。目录的样子也很简单:

虽然对外的接口是
NSObject的类别,不过这块功能比较特殊,所以当做一种组件库单列出来,并没有并入KJTCatagory.h中