在前面的三个阶段,咱们分别实现的功能:git
总之: 咱们实现了面向模型的数据库增删查改,以及数据库升级。感受功能实现得差很少了,可是若是存得模型得成员变量里面包含了另外得模型或者数组、字典,那么咱们就无法存了。咱们要解决他,这就是本篇要作的。github
本篇咱们要实现:复杂数据类型的存储,好比自定义对象、数组、字典等......而后咱们还要实现模型嵌套模型,数组、字典嵌套模型以及各类相互嵌套的状况。 本篇思路有点绕,须要沉着冷静而且实践才行。先看一下咱们最终实现的结果,咱们向数据库内存储一个很是复杂的模型:数据库
在实现功能以前,咱们必定要先考虑一下实现方式,考虑好了再开始动手,先看一下前辈们是如何作的,看过以后咱们总结出两个方式:json
最终咱们使用第二种方式。接下来,咱们逐条实现对应的功能,过程会比较绕逻辑,讲得不太明白的建议直接看代码,反正是绕了我挺久的😓。数组
这个过程咱们要先把模型转成字典,而后在将字典转成字符串。 先来一种很是简单的状况,好比下面这个模型里面只有两个基本数据类型的成员变量:安全
@interface School : NSObject
@property (nonatomic,copy) NSString *name; // 名字 (值:清华大学)
@property (nonatomic,assign) NSInteger schoolId; // 学校id (值:1)
@end
复制代码
咱们首先将他转成如下字典格式:bash
{
name = "清华大学";
schoolId = 1;
}
复制代码
思路1:首先取模型全部成员变量,根据成员变量的名字经过KVC从模型中取值,以School的第一个成员变量name为例,咱们根据成员变量的名字(name)经过KVC从模型中取值(id类型的@“清华大学”),而后根据成员变量的类型(name字段对应的类型为NSString)将值转换成对应类型的值,以成员变量的名字(name)为字典key,值(@"清华大学")为字典value,逐条组成字典。多线程
实现: 固然还有模型嵌套模型的状况,这种状况就在思路1的加黑部分取处理,首先从大的模型里逐个成员变量转换到字典内,若是当类型是模型,那么咱们先将里面这个模型转成字符串再存到字典内,也就是重复以上步骤了,相似递归,说得可能有点绕,直接上代码了。框架
#pragma mark 模型转字典
+ (NSDictionary *)dictWithModel:(id)model {
// 获取类的全部成员变量的名称与类型 {name : NSString}
NSDictionary *nameTypeDict = [CWModelTool classIvarNameAndTypeDic:[model class]];
// 获取模型全部成员变量 @[name,schollId]
NSArray *allIvarNames = nameTypeDict.allKeys;
NSMutableDictionary *allIvarValues = [NSMutableDictionary dictionary];
// 获取全部成员变量对应的值
for (NSString *ivarName in allIvarNames) {
id value = [model valueForKeyPath:ivarName];
NSString *type = nameTypeDict[ivarName];
value = [CWModelTool formatModelValue:value type:type isEncode:YES];
allIvarValues[ivarName] = value;
}
return allIvarValues;
}
#pragma mark - 格式化字段数据,咱们的宗旨:一切不可识别的对象,都转字符串
+ (id)formatModelValue:(id)value type:(NSString *)type isEncode:(BOOL)isEncode{
if (isEncode && value == nil) { // 只有对象才能为nil,基本数据类型没值时为0
return @"";
}
if (!isEncode && [value isKindOfClass:[NSString class]] && [value isEqualToString:@""]) {
return [NSClassFromString(type) new];
}
if([type isEqualToString:@"i"]||[type isEqualToString:@"I"]||
[type isEqualToString:@"s"]||[type isEqualToString:@"S"]||
[type isEqualToString:@"q"]||[type isEqualToString:@"Q"]||
[type isEqualToString:@"b"]||[type isEqualToString:@"B"]||
[type isEqualToString:@"c"]||[type isEqualToString:@"C"]|
[type isEqualToString:@"l"]||[type isEqualToString:@"L"] || [value isKindOfClass:[NSNumber class]]) {
return value;
}else if([type isEqualToString:@"f"]||[type isEqualToString:@"F"]||
[type isEqualToString:@"d"]||[type isEqualToString:@"D"]){
return value;
}else if ([type containsString:@"Data"]) {
return value;
}else if ([type containsString:@"String"]) {
if ([type containsString:@"AttributedString"]) {
if (isEncode) {
NSData *data = [[NSKeyedArchiver archivedDataWithRootObject:value] base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength];
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}else {
NSData* data = [[NSData alloc] initWithBase64EncodedString:value options:NSDataBase64DecodingIgnoreUnknownCharacters];
return [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
}
return value;
}else if ([type containsString:@"Dictionary"] && [type containsString:@"NS"]) {
if (isEncode) {
return [self stringWithDict:value];
}else {
return [self dictWithString:value type:type];
}
}else if ([type containsString:@"Array"] && [type containsString:@"NS"] ) {
if (isEncode) {
return [self stringWithArray:value];
}else {
return [self arrayWithString:value type:type];
}
}else { // 当模型处理
if (isEncode) { // 模型转json字符串
NSDictionary *modelDict = [self dictWithModel:value];
return [self stringWithDict:modelDict];
}else { // 字符串转模型
NSDictionary *dict = [self dictWithString:value type:type];
return [self model:NSClassFromString(type) Dict:dict];
}
}
return @"";
}
复制代码
而后咱们再将这个字典转成JSON字符串:ide
{
"name" : "清华大学",
"schoolId" : 1,
}
复制代码
思路2:直接调用NSJSONSerialization的方法转成Data,而后再转成字符串就👌(暂时只须要关注下面方法的if下的状况):
// 字典转字符串
+ (NSString *)stringWithDict:(NSDictionary *)dict {
if ([NSJSONSerialization isValidJSONObject:dict]) {
// dict -> data
NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];
// data -> NSString
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}else { // 这里是字典嵌套对象的状况
NSMutableDictionary *dictM = [NSMutableDictionary dictionary];
for (NSString *key in dict.allKeys) {
id value = dict[key];
id result = [self formatModelValue:value type:NSStringFromClass([value class]) isEncode:YES];
NSDictionary *valueDict = @{NSStringFromClass([value class]) : result};
[dictM setValue:valueDict forKey:key];
}
return [[self stringWithDict:dictM] stringByAppendingString:@"CWCustomCollection"];
}
}
复制代码
这样,咱们就能将School这个对象转成字符串当成值存入数据库了。。
而后咱们查询的时候,只须要将过程反转就OK了,首先将字符串经过JSON的方法转成字典,而后经过字典转成对应模型,字符串转字典代码就不贴了,咱们直接上字典转模型的代码:
#pragma mark 字典转模型
+ (id)model:(Class)cls Dict:(NSDictionary *)dict {
id model = [cls new];
// 获取全部属性名
NSArray *ivarNames = [CWModelTool allIvarNames:cls];
// 获取全部属性名和类型的字典 {ivarName : type}
NSDictionary *nameTypeDict = [CWModelTool classIvarNameAndTypeDic:cls];
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
id value = obj;
// 判断数据库查询到的key 在当前模型中是否存在,存在才赋值
if ([ivarNames containsObject:key]) {
NSString *type = nameTypeDict[key];
value = [CWModelTool formatModelValue:value type:type isEncode:NO];
if (value == nil) {
value = @(0);
}
[model setValue:value forKeyPath:key];
}
}];
return model;
}
复制代码
这个方法的第一个入口,放在从数据库查询到数据对应的字典(这个在第二篇文章有说到)将该字典转换成模型的解析函数内+ (NSArray *)parseResults:(NSArray <NSDictionary >)results withClass:(Class)cls;
而后咱们对模型-->字典-->字符串-->字典-->模型,这整个方法进行单独测试:
- (void)testDictWithModel {
School *school = [[School alloc] init];
school.name = @"清华大学";
school.schoolId = 1;
Student *stu = [[Student alloc] init];
stu.stuId = 10000;
stu.name = @"Baidu";
stu.age = 100;
stu.height = 190;
stu.weight = 140;
// stu.dict = @{@"name" : @"chavez"};
// stu.arrayM = [@[@"chavez",@"cw",@"ccww"] mutableCopy];
NSAttributedString *attributedStr = [[NSAttributedString alloc] initWithString:@"attributedStr,attributedStr"];
stu.attributedString = attributedStr;
// 模型嵌套模型
stu.school = school;
// 模型转字典
NSDictionary *dict = [CWModelTool dictWithModel:stu];
NSLog(@"-----%@",dict);
// 字典转字符串
NSString *jsonStr = [CWModelTool stringWithDict:dict];
NSLog(@"=====%@",jsonStr);
// 字符串转字典
NSDictionary *dict1 = [CWModelTool dictWithString:jsonStr type:NSStringFromClass([stu class])];
NSLog(@"-----%@",dict);
// 字典转模型
id model = [CWModelTool model:[stu class] Dict:dict1];
NSLog(@"=====%@",model);
}
复制代码
咱们比较各个阶段获得的数据,最后解析出的model和刚开始进行解析的stu数据是一一对应的,测试结果咱们就不贴了(能够尝试测试更多的场景,我这里就没贴代码了,测试必定要充足)。
数组转字符串分为两种状况,一种是能直接转JSON字符串的,另外一种就是数组内的元素不是单纯的基本数据类型,有可能嵌套模型,数组,字典的状况,这时候咱们要先深刻把嵌套的模型,数组,字典转成字符串再来把数组转JSON字符串。
贴上咱们数组转字符串的代码:
#pragma mark 集合类型转JSON字符串
// 数组转字符串
+ (NSString *)stringWithArray:(id)array {
if ([NSJSONSerialization isValidJSONObject:array]) {
// array -> Data
NSData *data = [NSJSONSerialization dataWithJSONObject:array options:NSJSONWritingPrettyPrinted error:nil];
// data -> NSString
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}else {
NSMutableArray *arrayM = [NSMutableArray array];
for (id value in array) {
id result = [self formatModelValue:value type:NSStringFromClass([value class]) isEncode:YES];
NSDictionary *dict = @{NSStringFromClass([value class]) : result};
[arrayM addObject:dict];
}
return [[self stringWithArray:arrayM] stringByAppendingString:@"CWCustomCollection"];
}
}
复制代码
上面代码中,if下也就是第一种能直接转的状况,第二种为不能直接转的状况,咱们须要先对每一个元素进行转换,在转换的时候,咱们把转换以后的结果,用一个字典保存,字典的key 为这个值所属的类,value即为值,为何要这么设计,由于咱们最终都会把值变成字符串存,当咱们要反过来解析的时候,整个数组都是字符串,咱们查询出来的东西就没法还原到以前的类型,另外一个咱们在转换成功的字符串末尾追加@"CWCustomCollection",也是由于从数据库查询取出的数据时,咱们要分辨有些能够直接从字符串转到数组(也就是if下第一种状况),有些并不行,咱们须要按照咱们的规则本身进行转换回来。总之,这样设计是为了以后能准确转换回来,说到这,可能你仍是一脸懵逼,实际上是正常的,俗话都说实践出真知,光看确定不行的,最好是本身写一个测试场景,而后思考一下如何实现,再尝试写一写,并且咱们这个规则也是在咱们发现查询的时候无法实现而加上去的,因此并非一开始就能想到要这样作,而是打补丁打上去的
字符串转数组咱们也要分为两种状况,一种是字符串的末尾带有@"CWCustomCollection",这种表示是咱们自定义的规则转换过来的,里面嵌套了复杂的数据类型,另外一种是不带@"CWCustomCollection"这种咱们能够直接调用json的方法转回来就OK了。上代码:
#pragma mark JSON字符串转集合类型
// 字符串转数组(还原)
+ (id)arrayWithString:(NSString *)str type:(NSString *)type{
if ([str hasSuffix:@"CWCustomCollection"]) {
NSUInteger length = @"CWCustomCollection".length;
str = [str substringToIndex:str.length - length];
NSJSONReadingOptions options = kNilOptions; // 是否可变
if ([type containsString:@"Mutable"] || [type containsString:@"NSArrayM"]) {
options = NSJSONReadingMutableContainers;
}
NSMutableArray *resultArr = [NSMutableArray array];
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
id result = [NSJSONSerialization JSONObjectWithData:data options:options error:nil];
id value;
for (NSDictionary *dict in result) {
value = [self formatModelValue:dict.allValues.firstObject type:dict.allKeys.firstObject isEncode:NO];
[resultArr addObject:value];
}
if (options == kNilOptions) {
resultArr = [resultArr copy]; // 不可变数组
}
return resultArr;
}else {
return [self formatJsonArrayAndJsonDict:str type:type];
}
}
复制代码
首先,截取掉咱们本身加的字符串@"CWCustomCollection",而后咱们将字符串转成对应的数组,再遍历数组,分别处理解析各个元素,将获得的值添加到一个新的数组返回。
这个相似于数组转字符串,逻辑差很少,就不贴代码了,由于我知道一、我废话一大堆也不必定能表述清楚(我上面就有点表述不太好,可是我尽力了),二、想了解的必定会本身去看源码。。唉。。感受嘴巴已经打结了
最终的测试结果,贴在了开头。
在此,咱们实现了复杂的数据类型以及字典、数组、模型相互嵌套场景数据的存储并合并到了插入数据的方法内,再一次成为了用户背后默默付出的女人。下一篇文章,咱们会对多线程安全进行处理(多是终结篇),欢迎围观。
github地址 本次的代码,tag为1.3.0,你能够在release下找到对应的tag下载下来(注意:若是要直接运行,必须在CWDatabase.m的位置修改数据库存放的路径,开发调试阶段我写在了我电脑的桌面,不修改会出现路径错误,致使失败)
最后以为有用的同窗,但愿能给本文点个喜欢,给github点个star以资鼓励,谢谢你们。
PS: 由于我也是一边封装,一边写文章。效率可能比较低,问题也会有,欢迎你们向我抛issue,有更好的思路也欢迎你们留言!
最后再为你们提供咱们一步一个脚印走到今天以前文章的地址:在本文的开头😁
以及一个0耦合的仿QQ侧滑框架: 一行代码集成超低耦合的侧滑功能
啦啦啦啦。。生命不止。。推广不断😁