## 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`層統一的業務比如存取可以以協議實現的方式提供所需信息