一行代碼搞定iOS端數據庫存儲

正常情況下,當我們需要進行數據庫存儲操作時,需要進行以下操作:
1、書寫數據庫語句創建表

CREATE TABLE IF NOT EXISTS TableName

2、確定數據庫中需要插入字段,然后書寫數據庫語句進行插入操作

INSERT INTO TableName (name1, name2,...) Values (?, ?,...)

真正的項目中我們需要做的操作可能會更多,比如需要判斷表是否存在、要插入的字段是否存在等一系列判斷,本來好好寫代碼邏輯的人怎么就被拉去寫數據庫語句出不來了(心疼自己幾分鐘)!

當然,一個完整的項目里面不可能只有這些操作,應該包括數據庫的增(Insert)、刪(delete)、改(update)、查(select)四大操作,數據庫查找還包括條件篩選語句,如果你一行一行的寫完這些語句,會發現整個人都不好了,難道就沒有一個更好地解決辦法嗎?

我們應該都對數據模型化不陌生,各種數據轉模型的第三方庫如MJExtension、YYModel應有盡有,我們只需要一行代碼即可完成數據模型化的操作,比如MJExtension中的數組轉模型:

NSArray *userArray = [MJUser mj_objectArrayWithKeyValuesArray:dictArray];

得到的就是一個模型數組,想象一下數據庫的所有操作也都用一行代碼來完成是什么感覺?

//Insert
for (LYTeacher *teacher in self.teachers) {
        [LYFMDBTool ly_insertDBWithModel:teacher category:FMDB_TEACHER];
}

//Delete
[LYFMDBTool ly_deleteDBWithModel:teacher
                                           category:FMDB_TEACHER
                                       propertyName:@"teacherId"];

//Update
LYTeacher *teacher = self.teachers[arc4random_uniform(20)];
teacher.name = [teacher.name stringByAppendingString:@"updated"];
BOOL success = [LYFMDBTool ly_updateDBWithModel:teacher
                                           category:FMDB_TEACHER
                                       propertyName:@"teacherId"];

//Select
NSArray *result = [LYFMDBTool ly_selectFromDBWithTableName:NSStringFromClass([LYTeacher class]) category:FMDB_TEACHER];

//Filter Select
//按照降序查找數據庫中10條數據
NSString *filter = @"ORDER BY teacherId DESC LIMIT 10";
    NSArray *result = [LYFMDBTool ly_selectFromDBWithTableName:NSStringFromClass([LYTeacher class]) category:FMDB_TEACHER filter:filter];

基于模型對象的數據庫增、刪、改、查操作已經完成,而且我們并沒有書寫一句SQL語句,并且每當你為模型對象增加屬性字段后執行插入或者更新操作,數據庫中都會增加對應的字段。

這其中的魔法操作便是runtime,我們通過運行時動態獲取模型對象的所有屬性字段將其存到數組中,然后再對每個屬性進行字符串拼接動態生成數據庫語句,最后執行對應的數據庫語句即可,下面是對應的實現代碼

/**
 插入模型到指定表中
 @param model 模型名稱(必須是模型名稱)
 @param tableName 表名
 @param category  表后綴名(用來區別以同一個模型創建的不同表名)
 */
+ (BOOL)ly_insertDBWithModel:(NSObject *)model tableName:(NSString *)tableName category:(NSString *)category {
    if (!model) { return NO; }
    //動態拼接數據庫表名,這里由模型名、表名和表名后綴組成一個真正的數據庫表名
    NSString *className = NSStringFromClass(object_getClass(model));
    className = (tableName == nil) ? className : tableName;
    __block BOOL successed = YES;
    //判斷對應表名的數據庫是否創建成功
    if (![self ly_createTableName:className category:category]) {
        successed = NO;
        return successed;
    }
    tableName = [className stringByAppendingString:category];
    //獲取模型的所有屬性
    NSArray *propertyArr = [self ly_getPropertyList:className];
    //獲取允許插入數據庫中的屬性
    NSArray *allowedProperties = [model.class ly_FMDBAllowedPropertyNames];
    //拼接數據庫語句
    NSString *sql1 = [NSString stringWithFormat:@"INSERT INTO %@%@ ",className,category];
    __block NSMutableString *keyStr = [NSMutableString string];
    __block NSMutableString *valueStr = [NSMutableString string];
    __block NSMutableArray *argumentsArr = [NSMutableArray array];
    for (int i = 0; i < propertyArr.count; i++) {
        __block MJProperty *property = propertyArr[i];
        __block MJPropertyType *type = property.type;
        if (![allowedProperties containsObject:property.name]) {
            continue;
        }
        [_queue inDatabase:^(FMDatabase *db) {
            //如果對應的屬性字段在表中不存在,則在表中增加對應字段
            if (![db columnExists:property.name inTableWithName:tableName]) {
                NSString *alterStr = [NSString stringWithFormat:@"ALTER TABLE %@ ADD %@ TEXT",tableName,property.name];
                BOOL added = [db executeUpdate:alterStr];
                if (!added) {
                    successed = NO;
                }
            }
            NSUInteger length = keyStr.length;
            (!length)?[keyStr appendFormat:@"%@",property.name]
            :[keyStr appendFormat:@",%@",property.name];
            (!length)?[valueStr appendString:@"?"]
            :[valueStr appendString:@",?"];
            id value = [model valueForKey:property.name];
            if (value) {
                //由于數據庫不支持數組、字典、模型對象的直接存儲,因此我們將數組、字典、模型對象轉json字符串存儲
                if ([self _isKeyTypeValidateJSON:type]) {
                    NSString *jsonStr = [self _switchValue:value toJSON:type];
                    [argumentsArr addObject:jsonStr ?:@""];
                } else {
                    [argumentsArr addObject:value];
                }
            } else {
                [argumentsArr addObject:@""];
            }
        }];
        if (!successed) {
            return successed;
        }
    }
    NSString *sql = [NSString stringWithFormat:@"%@(%@) VALUES (%@)",sql1,keyStr,valueStr];
    
    [_queue inDatabase:^(FMDatabase *db) {
        if (![db executeUpdate:sql withArgumentsInArray:argumentsArr]) {
            successed = NO;
        }
    }];
    return successed;
}

數據庫的更新和刪除操作也是這個原理,這里我們采取了MJExtension的處理辦法,默認為NSObject對象增加了一個LYFMDBKeyValue協議,我們繼承自NSObject的類可以聲明需要插入到數據庫的屬性字段,默認會取所有的屬性進行插入操作

@protocol LYFMDBKeyValue <NSObject>

@optional

/**
 允許存儲到數據庫的屬性
 */
+ (nonnull NSArray *)ly_FMDBAllowedPropertyNames;

@end

@interface NSObject (LYFMDBKeyValue) <LYFMDBKeyValue>

@end

@implementation NSObject (LYFMDBKeyValue)

static NSMutableArray *_allowedProperties;
+ (NSArray *)ly_FMDBAllowedPropertyNames {
    if (_allowedProperties.count) {
        return _allowedProperties;
    }
    NSMutableArray *propertyList =  [LYFMDBTool ly_getPropertyList:NSStringFromClass([self class])];
    if (!_allowedProperties) {
        _allowedProperties = [NSMutableArray array];
    }
    for (MJProperty *property in propertyList) {
        [_allowedProperties addObject:property.name];
    }
    return _allowedProperties;
}

@end

以上就是所有的實現原理,附上github鏈接地址:https://github.com/LustySwimmer/LYFMDBManager

說明:
1、該工具基于FMDB進行二次封裝操作;
2、數據模型化采用的是MJExtension進行轉換處理;
3、支持cocoapod導入
4、數據模型要有一個唯一值得屬性字段,可以使用時間作區分,也可以是其他不同的數字或者字符串
注意點:
不支持NSUInteger類型存儲,最好使用NSString進行存儲操作;
在使用過程中暫時沒有發現其他問題,如果有發現問題,歡迎提issue,如果有更好的建議和方法,歡迎指教!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容