從0開始弄一個面向OC數據庫(四)--複雜數據模型存儲

前言

在前面的三個階段,咱們分別實現的功能:git

總之: 咱們實現了面向模型的數據庫增刪查改,以及數據庫升級。感受功能實現得差很少了,可是若是存得模型得成員變量裏面包含了另外得模型或者數組、字典,那麼咱們就無法存了。咱們要解決他,這就是本篇要作的。github

本篇咱們要實現:複雜數據類型的存儲,好比自定義對象、數組、字典等......而後咱們還要實現模型嵌套模型,數組、字典嵌套模型以及各類相互嵌套的狀況。 本篇思路有點繞,須要沉着冷靜而且實踐才行。先看一下咱們最終實現的結果,咱們向數據庫內存儲一個很是複雜的模型:數據庫

插入數據庫
插入數據庫是成功的,可是插入成功不重要,重要的是,你取出來的時候,他是否是插入以前的樣子,下面咱們進行數據庫查詢,獲得如下結果:
從獲得的結果來看,各類類型嵌套的模型,咱們能過完美的插入數據庫,同時,咱們也能完美將它從數據庫取出來而且還原爲模型,灰常的牛逼。這個必定是咱們比FMDB,Realm這種航母級別的優點所在。你是否是火燒眉毛的想知道這是如何實現的?下面會一一講解。

功能實現

在實現功能以前,咱們必定要先考慮一下實現方式,考慮好了再開始動手,先看一下前輩們是如何作的,看過以後咱們總結出兩個方式:json

  • 一種是將無論三七二十一統統轉成NSData,轉不了的就歸檔轉!而後存,取數據的時候就解檔取。
  • 另外就是統統轉成字符串,有一些能夠直接轉成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側滑框架: 一行代碼集成超低耦合的側滑功能

啦啦啦啦。。生命不止。。推廣不斷😁

相關文章
相關標籤/搜索