## 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`层统一的业务比如存取可以以协议实现的方式提供所需信息