正常情況下,當我們需要進行數據庫存儲操作時,需要進行以下操作:
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,如果有更好的建議和方法,歡迎指教!