3.I/O操作

首先是I/O操作的业务归属问题。假设我们的M采用了序列归档的持久化方案,那么M层应该实现NSCoding协议:

@interface LXDRecord: NSObject<NSCoding>
@end

@implementation LXDRecord

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject: _content forKey: @"content"];
    [aCoder encodeObject: _recorder forKey: @"recorder"];
    [aCoder encodeObject: _createDate forKey: @"createDate"];
    [aCoder encodeObject: _updateDate forKey: @"updateDate"];
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super init]) {
        _content = [aDecoder decodeObjectForKey: @"content"];
        _recorder = [aDecoder decodeObjectForKey: @"recorder"];
        _createDate = [aDecoder decodeObjectForKey: @"createDate"];
        _updateDate = [aDecoder decodeObjectForKey: @"updateDate"];
    }
    return self;
}

@end

从序列化归档的实现中我们可以看到这些核心代码是放在模型层中实现的,虽然还要借助NSKeyedArchiver来完成存取操作,但是在这些实现上将I/O操作归属为M层的业务也算的上符合情理。另一方面,合理的将这一业务放到模型层中既减少了控制器层的代码量,也让模型层不仅仅是花瓶角色。通常情况下,我们的I/O操作不会直接放在控制器的代码中,而是会将这部分操作封装成一个数据库管理者来执行:

@interface LXDDataManager: NSObject

+ (instancetype)sharedManager;

- (void)insertData: (LXDRecord *)record;
- (NSArray<LXDRecord *> *)storedRecord;

@end

这是一段非常常见的数据库管理者的代码,缺点是显而易见的:I/O操作的业务实现对象过于依赖数据模型的结构,这使得这部分业务几乎不可复用,仅能服务指定的数据模型。解决的方案之一采用数据库关键字<->属性变量名映射的方式传入映射字典:

@interface LXDDataManager: NSObject

+ (instancetype)managerWithTableName: (NSString *)tableName;

- (void)insertData: (id)dataObject mapper: (NSDictionary *)mapper;    

@end

@implementation LXDDataManager

- (void)insertData: (id)dataObject mapper: (NSDictionary *)mapper {
    NSMutableString * insertSql = [NSMutableString stringWithFormat: @"insert into %@ (", _tableName];
    NSMutableArray * keys = @[].mutableCopy;
    NSMutableArray * values = @[].mutableCopy;
    NSMutableArray * valueSql = @[].mutableCopy;

    for (NSString * key in mapper) {
        [keys addObject: key];
        [values addObject: ([dataObject valueForKey: key] ?: [NSNull null])]; 
        [valueSql addObject: @"?"];
    }

    [insertSql appendString: [keys componentsJoinedByString: @","];
    [insertSql appendString @") values ("];
    [insertSql appendString: [valueSql componentsJoinedByString: @","];
    [insertSql appendString: @")"];

    [_database executeUpdate: insertSql withArgumentsInArray: values];
}

@end

通过键值对映射的方式让数据管理者可以动态的插入不同的数据模型,这样可以减少I/O操作业务中对数据模型结构的依赖,使得其更易用。更进一步还可以将这段代码中mapper的映射任务分离出来,通过声明一个映射协议来完成这一工作:

@protocol LXDModelMapper <NSObject>

- (NSArray<NSString *> *)insertKeys;
- (NSArray *)insertValues;

@end

@interface LXDDataManager: NSObject

+ (instancetype)managerWithTableName: (NSString *)tableName;

- (void)insertData: (id<LXDModelMapper>)dataObject;    

@end

@implementation LXDDataManager

- (void)insertData: (id<LXDModelMapper>)dataObject mapper: (NSDictionary *)mapper {
    NSMutableString * insertSql = [NSMutableString stringWithFormat: @"insert into %@ (", _tableName];
    NSMutableArray * keys = [dataObject insertKeys];
    NSMutableArray * valueSql = @[].mutableCopy;

    for (NSInteger idx = 0; idx < keys.count; idx++) {
        [valueSql addObject: @"?"];
    }

    [insertSql appendString: [keys componentsJoinedByString: @","];
    [insertSql appendString @") values ("];
    [insertSql appendString: [valueSql componentsJoinedByString: @","];
    [insertSql appendString: @")"];
    [_database executeUpdate: insertSql withArgumentsInArray: [dataObject insertValues]];
}

@end

将这些逻辑分离成协议来实现的好处包括:

  • 移除了I/O业务中不必要的逻辑,侵入性更低

  • 让开发者实现协议返回的数据排序会更对齐

  • 扩展支持I/O操作的数据模型

总结一下M层可以做的事情:

  1. 提供接口来提供数据->展示内容的实现,尽可能以category的方式完成

  2. 对于M层统一的业务比如存取可以以协议实现的方式提供所需信息

文章导航