(原理篇)基于SQLite3輕量級封裝,一行代碼實現增刪改查

最近寫的項目中有用到數據庫,寫了不少蛋疼的sql語句,每次都是好幾行代碼,而且每次都是重復的沒有一點技術含量的代碼,雖然也有不少基于sqlite的封裝,不過用起來還是感覺不夠面向對象!
為了不再寫重復的代碼,花了幾天時間,基于SQLite3簡單封裝了下,實現了一行代碼解決增刪改查等常用的功能!并沒有太過高深的知識,主要用了runtime和KVC:

首先我們創建個大家都熟悉的Person類,并聲明兩個屬性,下面將以類此展開分析

@interface Person : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) NSInteger age;
@end

創建表格

相信下面這句創表語句大家都熟悉吧,就不做介紹了

create table if not exists Person (id integer primary key autoincrement,name text,age integer)

然而開發中我們都是基于模型開發的,基本上都是一個模型對應數據庫的一張表,那么每個模型的屬性都不一樣,那么我們又該如何生成類似上面的語句呢? 我想到了runtime,通過runtime獲取一個類的屬性列表,所以有了下面這個方法:

/// 獲取當前類的所有屬性
+ (NSArray *)getAttributeListWithClass:(id)className {
    // 記錄屬性個數
    unsigned int count;
    objc_property_t *properties = class_copyPropertyList([className class], &count);
    NSMutableArray *tempArrayM = [NSMutableArray array];
    for (int i = 0; i < count; i++) {
        // objc_property_t 屬性類型
        objc_property_t property = properties[i];
        // 轉換為Objective C 字符串
        NSString *name = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
        NSAssert(![name isEqualToString:@"index"], @"禁止在model中使用index作為屬性,否則會引起語法錯誤");
        if ([name isEqualToString:@"hash"]) {
            break;
        }
        [tempArrayM addObject:name];
    }
    free(properties);
    return [tempArrayM copy];
}

通過這個方法我們可以獲取一個類的所有屬性列表并將其保存到數組中(index是數據庫中保留的關鍵字,所以在這里用了個斷言),然而僅僅是拿到屬性列表還是不夠的,我們還需要將對應的OC類型轉換為SQL對應的數據類型,相信通過上面獲取屬性名的方法,大家也知道通過runtime能拿到屬性對應的數據類型了,那么我們可以通過下面方法將其轉換為SQLite需要的類型

/// OC類型轉SQL類型
+ (NSString *)OCConversionTyleToSQLWithString:(NSString *)String {
    if ([String isEqualToString:@"long"] || [String isEqualToString:@"int"] || [String isEqualToString:@"BOOL"]) {
        return @"integer";
    }
    if ([String isEqualToString:@"NSData"]) {
        return @"blob";
    }
    if ([String isEqualToString:@"double"] || [String isEqualToString:@"float"]) {
        return @"real";
    }
    // 自定義數組標記
    if ([String isEqualToString:@"NSArray"] || [String isEqualToString:@"NSMutableArray"]) {
        return @"customArr";
    }
    // 自定義字典標記
    if ([String isEqualToString:@"NSDictionary"] || [String isEqualToString:@"NSMutableDictionary"]) {
        return @"customDict";
    }
    return @"text";
}

通過上面方法我們將OC的數據類型轉換為了SQL的數據類型并保存到了數組中(上面有兩個自定義的類型,后面使用到的時候再做介紹),通過上面的方法我們成功的拿到了一個模型類的屬性名和對應的SQL數據類型,然后使用鍵值對的形式將其保存到了一個字典中,比如:

@{@"name" : @"text",@"age":"integer"}; 

獲取到這些之后那么創表語句就不難了吧,

// 該方法接收一個類型,內部通過遍歷類的屬性,字符串拼接獲取完整的創表語句,并在內部執行sql語句,并返回結果
- (BOOL)creatTableWithClassName:(id)className;

介紹完了怎么創表,那么我們再來說說怎么將數據插入到數據庫中:
我們先看一看插入數據的sql語句:insert into Person (name,age) values ('花菜ChrisCai98',89);
前面都是固定格式的,同樣我們可以通過字符串的拼接獲取完整的創表語句;
在上面我們已經可以拿到Person類的所有屬性列表,那么我們如何拼接sql語句呢? 在這里我定義了這么一個方法

/// 該方法接收一個對象作為參數(模型對象),并返回是否插入成功
- (BOOL)insertDataFromObject:(id)object;
/// 我們可以這樣
Person * p = [[Person alloc]init];
p.name = @"花菜ChrisCai";
p.age = 18;
[[GKDatabaseManager sharedManager] insertDataFromObject:p];

插入數據

通過上面這么簡單的一句代碼實現將數據插入到數據庫中,在該方法內部我們通過上面所述的方法獲取Person類的所有屬性列表,那么我們可以就可以拼接插入語句的前半句了,然后通過KVC的形式完成后半部分賦值的操作;

/// 插入數據
- (BOOL)insertDataFromObject:(id)object {
    // 創建可變字符串用于拼接sql語句
    NSMutableString * sqlString = [NSMutableString stringWithFormat:@"insert into %@ (",NSStringFromClass([object class])];
    [[GKObjcProperty getUserNeedAttributeListWithClass:[object class]] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        // 拼接字段名
        [sqlString appendFormat:@"%@,",obj];
    }];
    // 去掉后面的逗號
    [sqlString deleteCharactersInRange:NSMakeRange(sqlString.length-1, 1)];
    // 拼接values
    [sqlString appendString:@") values ("];
    
    // 拼接字段值
    [[GKObjcProperty getSQLProperties:[object class]] enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        // 拼接屬性
        if ([object valueForKey:key]){
            if ([obj isEqualToString:@"text"]) {
                [sqlString appendFormat:@"'%@',",[object valueForKey:key]];
            } else if ([obj isEqualToString:@"customArr"] || [obj isEqualToString:@"customDict"]) { // 數組字典轉處理
                NSData * data = [NSJSONSerialization dataWithJSONObject:[object valueForKey:key] options:0 error:nil];
                NSString * jsonString = [[NSString alloc] initWithData:data encoding:(NSUTF8StringEncoding)];
                [sqlString appendFormat:@"'%@',",jsonString];
            }else if ([obj isEqualToString:@"blob"]){ // NSData處理
                NSString * jsonString = [[NSString alloc] initWithData:[object valueForKey:key] encoding:(NSUTF8StringEncoding)];
                [sqlString appendFormat:@"'%@',",jsonString];
            }else {
                [sqlString appendFormat:@"%@,",[object valueForKey:key]];
            }
        }else {// 沒有值就存NULL
            [sqlString appendFormat:@"'%@',",[object valueForKey:key]];
        }
    }];
    // 去掉后面的逗號
    [sqlString deleteCharactersInRange:NSMakeRange(sqlString.length-1, 1)];
    // 添加后面的括號
    [sqlString appendFormat:@");"];
    // 執行語句
    return [self executeSqlString:sqlString];
}

在上面方法中,我們用到了之前提到的自定義的類型,通過該自定的類型我們知道需要存儲的是字典或者數組,在這里,我們將數組和字典轉換為JSON字符串的形式存入數據庫中;

到此我們完成了創表和插入向表格中插入數據的操作,下面我們再看看如何從實現一行代碼從數據庫中將值取出來,在這里我們提供了6中查詢的接口,

  • 提供的接口如下:
- (NSArray *)selecteDataWithClass:(id)className;// 根據類名查詢對應表格內所有數據
- (NSInteger)getTotalRowsFormClass:(id)className; // 獲取表的總行數
- (id)selecteFormClass:(id)className index:(NSInteger)index;// 獲取指定行數據
- (NSArray *)selectObject:(Class)className key:(id)key operate:(NSString *)operate value:(id)value;// 指定條件查詢
- (NSArray *)selecteDataWithSqlString:(NSString *)sqlString class:(id)className;// 自定義語句查詢
- (NSArray *)selectObject:(Class)className propertyName:(NSString *)propertyName type:(GKDatabaseSelectLocation)type content:(NSString *)content;// 模糊查詢

通過第一個方法(該方法接收一個類名作為參數)就能簡單的實現一行代碼查詢表格中的數據了

 NSArray * persons = [[GKDatabaseManager sharedManager] selecteDataWithClass:[Person class]];

下面我們著重介紹下核心方法,其他所有方法都是基于該方法實現的

/// 自定義語句查詢
- (NSArray *)selecteDataWithSqlString:(NSString *)sqlString class:(id)className  {
    // 創建模型數組
    NSMutableArray *models = nil;
    // 1.準備查詢
    sqlite3_stmt *stmt; // 用于提取數據的變量
    int result = sqlite3_prepare_v2(database, sqlString.UTF8String, -1, &stmt, NULL);
    // 2.判斷是否準備好
    if (SQLITE_OK == result) {
        models = [NSMutableArray array];
        // 獲取屬性列表名數組 比如name
        NSArray * arr = [GKObjcProperty getUserNeedAttributeListWithClass:[className class]];
        // 獲取屬性列表名和sql數據類型 比如  name : text
        NSDictionary * dict = [GKObjcProperty getSQLProperties:[className class]];
        // 準備好了
        while (SQLITE_ROW == sqlite3_step(stmt)) { // 提取到一條數據
            __block id objc = [[[className class] alloc]init];
            for ( int i = 0; i < arr.count; i++) {
                // 默認第0個元素為表格主鍵 所以元素從第一個開始
                // 使用KVC完成賦值
                if ([dict[arr[i]] isEqualToString:@"text"]) {
                    [objc setValue:[NSString stringWithFormat:@"%@",[self textForColumn:i + 1  stmt:stmt]] forKey:arr[i]];
                    
                } else if ([dict[arr[i]] isEqualToString:@"real"]) {
                    [objc setValue:[NSString stringWithFormat:@"%f",[self doubleForColumn:i + 1  stmt:stmt]] forKey:arr[i]];
                    
                } else if ([dict[arr[i]] isEqualToString:@"integer"]) {
                    
                    [objc setValue:[NSString stringWithFormat:@"%i",[self intForColumn:i + 1  stmt:stmt]] forKey:arr[i]];
                    
                } else if ([dict[arr[i]] isEqualToString:@"customArr"]) { // 數組處理
                    
                    NSString * str = [self textForColumn:i + 1 stmt:stmt];
                    NSData * data = [str dataUsingEncoding:NSUTF8StringEncoding];
                    NSArray * resultArray = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
                    [objc setValue:resultArray forKey:arr[i]];
                }  else if ([dict[arr[i]] isEqualToString:@"customDict"]) { // 字典處理
                    
                    NSString * str = [self textForColumn:i + 1 stmt:stmt];
                    NSData * data = [str dataUsingEncoding:NSUTF8StringEncoding];
                    NSDictionary * resultDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
                    [objc setValue:resultDict forKey:arr[i]];
                } else if ([dict[arr[i]] isEqualToString:@"blob"]) { // 二進制處理
                    
                    NSString * str = [self textForColumn:i + 1 stmt:stmt];
                    NSData * data = [str dataUsingEncoding:NSUTF8StringEncoding];
                    [objc setValue:data forKey:arr[i]];
                }
            }
            [models addObject:objc];
        }
    }
    return [models copy];
}

在該方法內部,我們根據傳遞進來的類創建了一個對象(使用__block是因為在block內部需要修改對象的屬性),通過之前的方法我們拿到了對應的sql類型,和屬性名,這里就不重復介紹了,通過對應的sql類型執行對應的方法從數據中將數據取出來,并通過KVC的形式給對象賦值,值得一提的是這里我們通過自定義的字段(customArr,customDict)可以知道我們取的是數組或者字典,然后數據庫中的JSON字符串轉換為數組或者字典,然后再利用KVC賦值給對象!

到此基本上所有的功能就都實現了,其他的諸如更新數據,刪除數據,刪除表格等有提供具體的接口,這里就不一一介紹了,源碼中有詳細的注釋,同時也有DEMO,有需要的可以自行下載,

以上均為個人這段時間的總結,如有不對的地方,可以在下面評論
也可以通過QQ:4593679聯系我,如覺得好用記得star一下哦~,謝謝!!!
源碼地址:https://github.com/ChrisCaixx/GKDatabase

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容