MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C中的Mantle模型层数据转换

2021-08-035.5k 阅读

一、Mantle 简介

1.1 Mantle 是什么

Mantle 是由 GitHub 开发并开源的一个轻量级框架,用于在 Objective-C 项目中简化模型层数据转换的过程。在开发 iOS 或 macOS 应用时,经常需要在服务器返回的 JSON 数据与本地的模型对象之间进行转换,以及对模型对象进行序列化和反序列化操作。Mantle 提供了一种便捷、高效且优雅的方式来处理这些任务。

1.2 Mantle 的优势

  • 简单易用:Mantle 提供了简洁的 API,使得开发者可以轻松地将 JSON 数据映射到模型对象,反之亦然。不需要编写大量复杂的手动转换代码。
  • 灵活性高:可以自定义数据转换规则,以适应各种复杂的数据结构和业务需求。例如,当 JSON 中的字段名与模型对象的属性名不一致时,可以通过 Mantle 进行灵活映射。
  • 性能良好:Mantle 在设计上注重性能,能够快速地处理大量数据的转换,不会给应用带来明显的性能开销。

二、Mantle 的基本使用

2.1 安装 Mantle

在项目中使用 Mantle 有多种方式,较为常用的是通过 CocoaPods 进行安装。在项目的 Podfile 文件中添加以下内容:

pod 'Mantle'

然后在终端中执行 pod install 命令,CocoaPods 会自动下载并集成 Mantle 到项目中。

2.2 创建模型类

假设我们有一个简单的用户模型,包含用户名、年龄和邮箱。首先创建一个继承自 MTLModel 的模型类 User

#import <Mantle/MTLModel.h>
#import <Mantle/MTLJSONSerializing.h>

@interface User : MTLModel <MTLJSONSerializing>

@property (nonatomic, strong) NSString *username;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) NSString *email;

@end

@implementation User

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
        @"username": @"user_name",
        @"age": @"user_age",
        @"email": @"user_email"
    };
}

@end

在上述代码中:

  • User 类继承自 MTLModel 并遵循 MTLJSONSerializing 协议,这是使用 Mantle 进行 JSON 数据转换的必要条件。
  • + (NSDictionary *)JSONKeyPathsByPropertyKey 方法用于指定 JSON 数据中的键与模型对象属性之间的映射关系。例如,JSON 中的 user_name 字段对应模型对象的 username 属性。

2.3 JSON 数据转模型对象

假设我们从服务器获取到以下 JSON 数据:

{
    "user_name": "JohnDoe",
    "user_age": 25,
    "user_email": "johndoe@example.com"
}

我们可以使用以下代码将其转换为 User 模型对象:

NSData *jsonData = [@"{\"user_name\":\"JohnDoe\",\"user_age\":25,\"user_email\":\"johndoe@example.com\"}" dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
User *user = [MTLJSONAdapter modelOfClass:[User class] fromJSONData:jsonData error:&error];
if (user) {
    NSLog(@"Username: %@, Age: %ld, Email: %@", user.username, (long)user.age, user.email);
} else {
    NSLog(@"Error: %@", error);
}

在这段代码中,MTLJSONAdapter 类的 modelOfClass:fromJSONData:error: 方法将 JSON 数据转换为 User 模型对象。如果转换成功,会打印出用户的信息;如果失败,会打印错误信息。

2.4 模型对象转 JSON 数据

将模型对象转换为 JSON 数据同样简单。假设我们已经有一个 User 模型对象,如下代码可以将其转换为 JSON 数据:

User *user = [[User alloc] init];
user.username = @"JaneDoe";
user.age = 30;
user.email = "janedoe@example.com";

NSError *error;
NSData *jsonData = [MTLJSONAdapter JSONDataFromModel:user error:&error];
if (jsonData) {
    NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    NSLog(@"JSON String: %@", jsonString);
} else {
    NSLog(@"Error: %@", error);
}

这里使用 MTLJSONAdapter 类的 JSONDataFromModel:error: 方法将 User 模型对象转换为 JSON 数据。如果转换成功,会将 JSON 数据打印出来;如果失败,会打印错误信息。

三、复杂数据结构的处理

3.1 嵌套模型

在实际开发中,数据结构往往比较复杂,可能存在嵌套模型的情况。例如,一个订单模型可能包含多个商品模型。 首先创建商品模型 Product

#import <Mantle/MTLModel.h>
#import <Mantle/MTLJSONSerializing.h>

@interface Product : MTLModel <MTLJSONSerializing>

@property (nonatomic, strong) NSString *productName;
@property (nonatomic, assign) CGFloat price;

@end

@implementation Product

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
        @"productName": @"product_name",
        @"price": @"product_price"
    };
}

@end

然后创建订单模型 Order,其中包含一个商品数组:

#import <Mantle/MTLModel.h>
#import <Mantle/MTLJSONSerializing.h>
#import "Product.h"

@interface Order : MTLModel <MTLJSONSerializing>

@property (nonatomic, strong) NSString *orderId;
@property (nonatomic, strong) NSArray<Product *> *products;

@end

@implementation Order

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
        @"orderId": @"order_id",
        @"products": @"order_products"
    };
}

+ (NSValueTransformer *)productsJSONTransformer {
    return [MTLJSONAdapter arrayTransformerWithModelClass:[Product class]];
}

@end

Order 模型中:

  • + (NSDictionary *)JSONKeyPathsByPropertyKey 方法指定了 JSON 键与模型属性的映射关系。
  • + (NSValueTransformer *)productsJSONTransformer 方法为 products 属性提供了一个值转换器,用于将 JSON 中的 order_products 数组转换为 Product 模型对象数组。

假设我们有如下 JSON 数据:

{
    "order_id": "12345",
    "order_products": [
        {
            "product_name": "iPhone",
            "product_price": 999.99
        },
        {
            "product_name": "MacBook",
            "product_price": 1999.99
        }
    ]
}

可以使用以下代码将其转换为 Order 模型对象:

NSData *jsonData = [@"{\"order_id\":\"12345\",\"order_products\":[{\"product_name\":\"iPhone\",\"product_price\":999.99},{\"product_name\":\"MacBook\",\"product_price\":1999.99}]}" dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
Order *order = [MTLJSONAdapter modelOfClass:[Order class] fromJSONData:jsonData error:&error];
if (order) {
    NSLog(@"Order ID: %@", order.orderId);
    for (Product *product in order.products) {
        NSLog(@"Product Name: %@, Price: %.2f", product.productName, product.price);
    }
} else {
    NSLog(@"Error: %@", error);
}

3.2 字典中的模型对象

有时候 JSON 数据中可能会有一个字典,其中的值是模型对象。例如,一个包含用户信息的字典,每个用户是一个 User 模型对象。 假设 JSON 数据如下:

{
    "users": {
        "user1": {
            "user_name": "User1",
            "user_age": 20,
            "user_email": "user1@example.com"
        },
        "user2": {
            "user_name": "User2",
            "user_age": 22,
            "user_email": "user2@example.com"
        }
    }
}

首先创建一个包含用户字典的模型 UserContainer

#import <Mantle/MTLModel.h>
#import <Mantle/MTLJSONSerializing.h>
#import "User.h"

@interface UserContainer : MTLModel <MTLJSONSerializing>

@property (nonatomic, strong) NSDictionary<NSString *, User *> *users;

@end

@implementation UserContainer

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
        @"users": @"users"
    };
}

+ (NSValueTransformer *)usersJSONTransformer {
    return [MTLValueTransformer transformerUsingForwardBlock:^id(NSDictionary *dictionary) {
        NSMutableDictionary *result = [NSMutableDictionary dictionary];
        [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSDictionary *userDict, BOOL *stop) {
            User *user = [MTLJSONAdapter modelOfClass:[User class] fromJSONDictionary:userDict error:nil];
            if (user) {
                result[key] = user;
            }
        }];
        return result;
    }];
}

@end

UserContainer 模型中:

  • + (NSDictionary *)JSONKeyPathsByPropertyKey 方法指定了 JSON 键与模型属性的映射关系。
  • + (NSValueTransformer *)usersJSONTransformer 方法为 users 属性提供了一个值转换器,将 JSON 中的 users 字典转换为包含 User 模型对象的字典。

以下代码用于将 JSON 数据转换为 UserContainer 模型对象:

NSData *jsonData = [@"{\"users\":{\"user1\":{\"user_name\":\"User1\",\"user_age\":20,\"user_email\":\"user1@example.com\"},\"user2\":{\"user_name\":\"User2\",\"user_age\":22,\"user_email\":\"user2@example.com\"}}}" dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
UserContainer *container = [MTLJSONAdapter modelOfClass:[UserContainer class] fromJSONData:jsonData error:&error];
if (container) {
    [container.users enumerateKeysAndObjectsUsingBlock:^(NSString *key, User *user, BOOL *stop) {
        NSLog(@"User Key: %@, Username: %@, Age: %ld, Email: %@", key, user.username, (long)user.age, user.email);
    }];
} else {
    NSLog(@"Error: %@", error);
}

四、自定义数据转换

4.1 日期格式转换

在 JSON 数据中,日期通常以字符串形式表示。假设 JSON 中的日期格式为 yyyy - MM - dd,而我们希望在模型对象中使用 NSDate 类型。 首先创建一个日期转换的工具类 DateTransformer

#import <Foundation/Foundation.h>
#import <Mantle/MTLValueTransformer.h>

@interface DateTransformer : MTLValueTransformer

@end

@implementation DateTransformer

+ (Class)transformedValueClass {
    return [NSDate class];
}

+ (BOOL)allowsReverseTransformation {
    return YES;
}

- (id)transformedValue:(id)value {
    if (!value) return nil;
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"yyyy - MM - dd"];
    return [formatter dateFromString:value];
}

- (id)reverseTransformedValue:(id)value {
    if (!value) return nil;
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"yyyy - MM - dd"];
    return [formatter stringFromDate:value];
}

@end

然后在模型类中使用这个转换器。假设我们有一个包含日期属性的 Event 模型:

#import <Mantle/MTLModel.h>
#import <Mantle/MTLJSONSerializing.h>

@interface Event : MTLModel <MTLJSONSerializing>

@property (nonatomic, strong) NSString *eventName;
@property (nonatomic, strong) NSDate *eventDate;

@end

@implementation Event

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
        @"eventName": @"event_name",
        @"eventDate": @"event_date"
    };
}

+ (NSValueTransformer *)eventDateJSONTransformer {
    return [DateTransformer new];
}

@end

Event 模型中,+ (NSValueTransformer *)eventDateJSONTransformer 方法为 eventDate 属性指定了 DateTransformer 作为值转换器,这样在 JSON 数据与模型对象转换时,日期会按照我们定义的格式进行处理。

4.2 枚举类型转换

如果模型中有枚举类型,也可以通过自定义转换器进行处理。假设我们有一个表示用户性别类型的枚举:

typedef NS_ENUM(NSInteger, Gender) {
    GenderMale,
    GenderFemale
};

创建一个枚举转换器 GenderTransformer

#import <Foundation/Foundation.h>
#import <Mantle/MTLValueTransformer.h>

@interface GenderTransformer : MTLValueTransformer

@end

@implementation GenderTransformer

+ (Class)transformedValueClass {
    return [NSNumber class];
}

+ (BOOL)allowsReverseTransformation {
    return YES;
}

- (id)transformedValue:(id)value {
    if ([value isEqualToString:@"male"]) {
        return @(GenderMale);
    } else if ([value isEqualToString:@"female"]) {
        return @(GenderFemale);
    }
    return nil;
}

- (id)reverseTransformedValue:(id)value {
    NSInteger gender = [value integerValue];
    if (gender == GenderMale) {
        return @"male";
    } else if (gender == GenderFemale) {
        return @"female";
    }
    return nil;
}

@end

然后在 User 模型中添加性别属性并使用这个转换器:

#import <Mantle/MTLModel.h>
#import <Mantle/MTLJSONSerializing.h>

@interface User : MTLModel <MTLJSONSerializing>

@property (nonatomic, strong) NSString *username;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) NSString *email;
@property (nonatomic, assign) Gender gender;

@end

@implementation User

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
        @"username": @"user_name",
        @"age": @"user_age",
        @"email": @"user_email",
        @"gender": @"user_gender"
    };
}

+ (NSValueTransformer *)genderJSONTransformer {
    return [GenderTransformer new];
}

@end

这样在 JSON 数据与 User 模型对象转换时,user_gender 字段会按照我们定义的规则转换为 Gender 枚举类型。

五、性能优化与注意事项

5.1 性能优化

  • 批量转换:如果需要处理大量的 JSON 数据转换,可以考虑批量进行转换,而不是逐个转换模型对象。例如,在处理包含多个用户的 JSON 数组时,可以一次性将整个数组转换为 User 模型对象数组,这样可以减少转换的开销。
  • 缓存转换器:对于一些常用的自定义转换器,如日期转换器、枚举转换器等,可以进行缓存,避免每次转换时都重新创建转换器对象,从而提高性能。

5.2 注意事项

  • 数据验证:在进行 JSON 数据到模型对象的转换时,要注意对数据进行验证。因为 JSON 数据可能来自不可信的数据源,可能存在格式错误或数据缺失的情况。可以在模型类中添加一些验证方法,确保转换后的数据是有效的。
  • 内存管理:当处理大量的模型对象时,要注意内存管理。及时释放不再使用的模型对象,避免内存泄漏。可以使用自动释放池来管理临时创建的对象。
  • 版本兼容性:如果服务器返回的 JSON 数据结构发生变化,要及时更新模型类中的映射关系和自定义转换器,以确保数据转换的正确性。同时,要考虑到版本兼容性,尽量采用兼容旧版本数据结构的方式进行更新,避免对旧版本客户端造成影响。

通过以上对 Mantle 在 Objective - C 中模型层数据转换的详细介绍,相信开发者能够熟练运用 Mantle 来高效处理各种数据转换任务,提升应用开发的效率和质量。无论是简单的数据结构还是复杂的嵌套模型、自定义数据转换,Mantle 都提供了强大而灵活的解决方案。