在前面的三個階段,咱們分別實現的功能:git
總之: 咱們實現了面向模型的數據庫增刪查改,以及數據庫升級。感受功能實現得差很少了,可是若是存得模型得成員變量裏面包含了另外得模型或者數組、字典,那麼咱們就無法存了。咱們要解決他,這就是本篇要作的。github
本篇咱們要實現:複雜數據類型的存儲,好比自定義對象、數組、字典等......而後咱們還要實現模型嵌套模型,數組、字典嵌套模型以及各類相互嵌套的狀況。 本篇思路有點繞,須要沉着冷靜而且實踐才行。先看一下咱們最終實現的結果,咱們向數據庫內存儲一個很是複雜的模型:數據庫
插入數據庫是成功的,可是插入成功不重要,重要的是,你取出來的時候,他是否是插入以前的樣子,下面咱們進行數據庫查詢,獲得如下結果: 從獲得的結果來看,各類類型嵌套的模型,咱們能過完美的插入數據庫,同時,咱們也能完美將它從數據庫取出來而且還原爲模型,灰常的牛逼。這個必定是咱們比FMDB,Realm這種航母級別的優點所在。你是否是火燒眉毛的想知道這是如何實現的?下面會一一講解。在實現功能以前,咱們必定要先考慮一下實現方式,考慮好了再開始動手,先看一下前輩們是如何作的,看過以後咱們總結出兩個方式: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側滑框架: 一行代碼集成超低耦合的側滑功能
啦啦啦啦。。生命不止。。推廣不斷😁