OCMapper 是一个 Objective-C 数据映射库,可以将 NSDictionary 转换为 NSObject。我编写 OCMapper 的灵感是想实现以下两点:
OCMapper 利用 Objective-C 运行时 API,只与从 NSObject 继承的类一起工作
@objc public class User: NSObject {
var id: String?
var name: String?
var category: String?
var count: NSNumber?
var address: Address?
var photos: [Photo]?
}
var user = ObjectMapper.sharedInstance().objectFromSource(json, toInstanceOfClass: User.self) as User?
https://github.com/Alamofire/Alamofire
public extension Request {
public func responseObjects<T: NSObject> (type: T.Type, completion: (NSURLRequest, NSURLResponse?, [T]?, NSError?)->()) -> Self {
return response(serializer: Request.JSONResponseSerializer(options: .AllowFragments)) { request, response, json, error in
if let error = error {
completion(request, response, nil, error)
}
else {
let objects = ObjectMapper.sharedInstance().objectFromSource(json, toInstanceOfClass: type) as? [T]
completion(request, response, objects, nil)
}
}
}
public func responseObject<T: NSObject> (type: T.Type, completion: (NSURLRequest, NSURLResponse?, T?, NSError?)->()) -> Self {
return response(serializer: Request.JSONResponseSerializer(options: .AllowFragments)) { request, response, json, error in
if let error = error {
completion(request, response, nil, error)
}
else {
let object = ObjectMapper.sharedInstance().objectFromSource(json, toInstanceOfClass: type) as? T
completion(request, response, object, nil)
}
}
}
}
扩展使用
let request = Manager.sharedInstance.request(requestWithPath("example.com/users", method: .GET, parameters: nil))
request.responseObjects(User.self) { request, response, users, error in
// users is an array of User objects
}
let request = Manager.sharedInstance.request(requestWithPath("example.com/users/5", method: .GET, parameters: nil))
request.responseObject(User.self) { request, response, user, error in
// user is an instance of User
}
@interface User
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) NSString *lastName;
@property (nonatomic, strong) NSNumber *age;
@property (nonatomic, strong) NSDate *dateOfBirth;
@property (nonatomic, strong) NSDate *accountCreationDate;
@property (nonatomic, strong) Address *address;
@property (nonatomic, strong) NSMutableArray *posts;
@end
@interface Address
@property (nonatomic, strong) NSString *city;
@property (nonatomic, strong) NSString *country;
@end
@interface Post
@property (nonatomic, strong) NSString *title;
@property (nonatomic, strong) User *author;
@property (nonatomic, strong) NSDate *datePosted;
@end
{
"firstName" : "Aryan",
"lastName" : "Ghassemi",
"age" : 26,
"dateOfBirth" : "01/01/2013"
}
User *user = [User objectFromDictionary:aDictionary];
在这种情况下,所有键/值都是自动映射的,因为它们都是相似的。 "address" 会自动转换为 "Address" 对象。 "posts" 会自动转换为 "Post" 对象数组。库会检测复数名词,并找到用于映射的正确类。
{
"firstName" : "Aryan",
"lastName" : "Ghassemi",
"age" : 26,
"dateOfBirth" : "01/01/2013",
"address" : {
"city" : "San Diego",
"country" : "US"
},
"posts" : [
{
"title" : "Post 1 title",
"datePosted : "04/15/2013",
},
{
"title" : "Post 2 title",
"datePosted : "04/12/2013",
}
]
}
User *user = [User objectFromDictionary:aDictionary];
这是一个更复杂的场景,其中字典键不匹配模型属性名称。出生日期的键从 "dateOfBirth" 更改为 "dob"。每篇帖子都有一个作者,并且用于转换的类(User)没有具有该名称的属性。
{
"firstName" : "Aryan",
"lastName" : "Ghassemi",
"age" : 26,
"dob" : "01/01/2013",
"address" : {
"city" : "San Diego",
"country" : "US"
},
"posts" : [
{
"title" : "Post 1 title",
"datePosted : "04/15/2013",
"author" : {
"firstName" : "Chuck",
"lastName" : "Norris"
}
},
{
"title" : "Post 2 title",
"datePosted : "04/12/2013",
"author" : {
"firstName" : "Chuck",
"lastName" : "Norris"
}
}
]
}
// Handle different key for dateOfBirth
[inCodeMappingProvider mapFromDictionaryKey:@"dob" toPropertyKey:@"dateOfBirth" forClass:[User class]];
// Handle conversion of "author" to a "User" object
// Mapping would NOT be required if both dictionary an drpopery were named 'user'
[inCodeMappingProvider mapFromDictionaryKey:@"author" toPropertyKey:@"author" withObjectType:[User class] forClass:[Comment class]];
User *user = [User objectFromDictionary:aDictionary];
[
{
"firstName" : "Aryan",
... rest of JSON data ...
},
{
"firstName" : "Chuck",
... rest of JSON data ...
},
]
NSArray *users = [User objectFromDictionary:aDictionary];
为了提高性能,默认情况下不再启用此功能。在ObjectMapper
类中有一个名为normalizeDictionary
的属性,允许您在需要时打开此功能。
{
"firstName" : "Aryan",
"city" : "San Diego"
"country" : "United States"
}
// We map city and country to a nested object called 'address' inside the 'user' object
[self.mappingProvider mapFromDictionaryKey:@"city" toPropertyKey:@"address.city" forClass:[User class]];
[self.mappingProvider mapFromDictionaryKey:@"country" toPropertyKey:@"address.country" forClass:[User class]];
User *user = [User objectFromDictionary:aDictionary];
NSLog(@"FirstName:%@ City:%@ Country:%@",
user.firstName,
user.address.city,
user.address.coutnry);
Automapper有一个名为defaultDateFormatter的属性,当设置该属性时,将使用此NSDateFormatter对所有NSDate属性进行日期转换。为了最佳性能,建议设置defaultDateFormatter。请注意,自定义dateFormatters比默认dateFormatters有优先级。
[inCodeMappingProvider setDefaultDateFormatter:aDefaultDateFormatter];
ObjectMapper使用一组通用的NSDateFormatters将字符串转换为NSDate。以下是默认支持的常见日期格式的列表
"yyyy-MM-dd"
"MM/dd/yyyy"
"yyyy-MM-dd'T'HH:mm:ss.SSSSSSSZ"
"yyyy-MM-dd HH:mm:ss"
"MM/dd/yyyy HH:mm:ss aaa"
"yyyy-MM-dd'T'HH:mm:ss'Z'"
您还可以为特定于类和属性的NSDateFormatter定义自定义格式
注意:只要所有日期格式都是标准的,或者默认dateFormatters知道如何解析日期,则无需设置DateFormatter。下面的代码只是为了演示有可能设置自定义dateFormatters,但在本例中,由于使用了标准日期格式,因此这不是必需的。
{
"firstName" : "Aryan",
"accountCreationDate" : "01/21/2005"
"dateOfBirth" : "2005-21-01"
}
// Custom formatter for account creation date
NSDateFormatter *accountCreationFormatter = [[NSDateFormatter alloc] init];
[accountCreationFormatter setDateFormat:@"MM/dd/yyyy"];
[inCodeMappingProvider setDateFormatter:accountCreationFormatter forProperty:@"accountCreationDate" andClass:[User class]];
// Custom formatter for date of birth
NSDateFormatter *dateOfBirthFormatter = [[NSDateFormatter alloc] init];
[dateOfBirthFormatter setDateFormat:@"yyyy-dd-MM"];
[inCodeMappingProvider setDateFormatter:dateOfBirthFormatter forProperty:@"dateOfBirth" andClass:[User class]];
User *user = [User objectFromDictionary:aDictionary];
数据转换器允许您捕获映射的一部分,并手动映射它。它还打开了多态映射的空间。
将字段转换为另一个
{
"firstName" : "Aryan",
"country" : "United States"
}
@implementation User
@property(nonatomic, strong) NSString *firstName;
@property(nonatomic, strong) Country *country;
@end
[mappingProvider mapFromDictionaryKey:@"country" toPropertyKey:@"country" forClass:[User class] withTransformer:^id(id currentNode, id parentNode) {
return [[Country alloc] initWithName:currentNode];
}];
使用转换器进行多态关系
{
"firstName" : "Aryan",
"vehicleType" : "car",
"vehicle" : { /*specific product Info*/ },
}
@implementation User
@property(nonatomic, strong) NSString *firstName;
@property(nonatomic, strong) Vehicle *vehicle;
@end
[mappingProvider mapFromDictionaryKey:@"vehicle" toPropertyKey:@"vehicle" forClass:[User class] withTransformer:^id(id currentNode, id parentNode) {
NSString *productType = [parentNode objectForKey:@"vehicleType"];
Vehicle *vehicle;
if ([productType isEqual:@"car"])
{
vehicle = [Car objectFromDictionary:currentNode];
}
else if ([productType isEqual:@"bike"])
{
vehicle = [Bike objectFromDictionary:currentNode];
}
return vehicle;
}];
// Or event better
[mappingProvider mapFromDictionaryKey:@"vehicle" toPropertyKey:@"vehicle" forClass:[User class] withTransformer:^id(id currentNode, id parentNode) {
NSString *productType = [parentNode objectForKey:@"vehicleType"];
Class class = NSClassFromString(productType.capitalizedString);
return [class objectFromDictionary:currentNode];
}];
转换器的其他可能性
{
"firstName" : "Aryan",
"image" : "BASE64_ENCODED_STRING"
}
@implementation User
@property(nonatomic, strong) NSString *firstName;
@property(nonatomic, strong) UIImage *image;
@end
[mappingProvider mapFromDictionaryKey:@"image" toPropertyKey:@"image" forClass:[User class] withTransformer:^id(id currentNode, id parentNode) {
return [UIImage imageFromBase64String:currentNode];
}];
逆映射是指将对象映射到字典。这与标准字典到属性映射非常相似。以下方法可以用于设置针对对象到字典映射的定制映射。
- (void)mapFromPropertyKey:(NSString *)propertyKey toDictionaryKey:(NSString *)dictionaryKey forClass:(Class)class;
- (void)mapFromPropertyKey:(NSString *)propertyKey toDictionaryKey:(NSString *)dictionaryKey forClass:(Class)class withTransformer:(MappingTransformer)transformer;
- (void)setDateFormatter:(NSDateFormatter *)dateFormatter forDictionary:(NSString *)property andClass:(Class)class;
InCodeMappingProvider
类有一个名为automaticallyGenerateInverseMapping
的属性。默认情况下,此属性设置为true,这意味着每当设置字典到属性的映射时,将自动生成逆映射,因此无需手动编写对象到字典映射的映射。唯一例外是,使用数据转换器映射字典到属性不能自动反转。
当automaticallyGenerateInverseMapping设置为true时,还会为逆关系创建date formatters。
要使用core data,您可以向ObjectMapper添加一个ManagedObjectInstanceProvider
ManagedObjectInstanceProvider *instanceProvider = [[ManagedObjectInstanceProvider alloc] initWithManagedObjectContext:moc];
[[ObjectMapper sharedInstance] addInstanceProvider:instanceProvider];
默认情况下,Object mapper在每次映射时都会创建新的NSManagedObject实例。为了更新现有记录,您可以提供给定类的唯一键,Object mapper将自动更新现有记录。
[managedObjectInstanceProvider setUniqueKeys:@[@"userId"] forClass:[User class] withUpsertMode:UpsertModePurgeExistingObject];
为类分配键时,OCMapper还要求一个枚举来描述Object mapper应该如何更新现有记录。
UpsertModeUpdateExistingObject:此选项创建一个新的临时管理对象实例,然后根据提供的键尝试找到现有记录。如果找到一条记录,它将更新现有托管对象的全部属性,最后删除临时对象。在启用此upsert模式时,不会调用任何相关托管对象的删除操作,因此记录将保留在内存中。例如,如果用户的地址从A改为B,地址将正确更新,但两条记录都将保留在core data中。
UpsertModePurgeExistingObject: 此选项会创建一个新的管理对象实例,然后基于给定的键尝试找到现有的记录。如果找到一条记录,则会调用该记录的删除操作,然后将新创建的对象插入上下文。使用此upsert模式时,将调用现有管理对象的删除操作,因此核心数据会删除所有相关的关联对象,并且在核心数据模型中应用所有的“删除规则”。例如,如果用户的信息被更新,电话号码从A变为B,并且如果删除规则设置为级联,则将移除地址A并将地址B分配给用户。
// Using ObjectMapper Directly
ObjectMapper *mapper = [[ObjectMapper alloc] init];
Urse *user = [mapper objectFromSource:dictionary toInstanceOfClass:[User class]];
// Using ObjectMapper Singleton Instance
Urse *user = [[ObjectMapper sharedInstance] objectFromSource:dictionary toInstanceOfClass:[User class]];
为了使用这些类别,您必须将映射提供程序添加到单例实例中
// Using NSObject Category
User *user = [User objectFromDictionary:aDictionary];
// Using NSDictionary Category
User *user = [aDictionary objectForClass:[User class]];
修复了在1.8版本中引入的错误,其中两个单词的类名没有自动映射。例如(sessionUser会尝试映射到Sessionuser类,而不是SessionUser,并会失败映射)
自动执行NSString到NSNumber和NSNumber到NSString的转换
注意
自动映射将嵌套数组区分对待。ObjectMapper在检查转换类之前会将对字典键的第一个字母大写。