前言
最近在GitHub上看了一份關(guān)于基于runtime封裝的對象存儲型數(shù)據(jù)庫的開源代碼,覺得非常值得分享記錄一下,在IOS中對數(shù)據(jù)庫的操作一般通過CoreData和SQLite,CoreData 雖然能夠?qū)C對象轉(zhuǎn)化成數(shù)據(jù),保存在SQLite數(shù)據(jù)庫文件中,也能夠?qū)⒈4嬖跀?shù)據(jù)庫中的數(shù)據(jù)還原成OC對象,期間不需要編寫SQL語句,但使用起來并不是那么方便,而SQLite則需要用戶編寫相應(yīng)的數(shù)據(jù)庫語句,看起來不是很美觀,所以大家一般都會將其進行封裝,讓其使用起來更加方便,而LHDB就是建立在SQLite之上的封裝?,F(xiàn)在,我們來看其是如何實現(xiàn)的,不過在此之前,我先假設(shè)大家對OC的runtime機制和sqlite有一定的理解。附上源碼下載地址:github鏈接
實現(xiàn)
所謂的基于對象存儲的數(shù)據(jù)庫,顧名思義,就是一切對數(shù)據(jù)的操作都是對對象模型的操作,通過給對象模型屬性賦值,然后將對象交由底層去解析,轉(zhuǎn)化成相應(yīng)的SQL語句,然后執(zhí)行數(shù)據(jù)庫操作,我們先看看整體的一個LHDB目錄結(jié)構(gòu)(這里僅寫出頭文件):
LHDB
LHDBPath.h //記錄數(shù)據(jù)庫路徑
LHModelStateMent.h //提供一系列將對象模型轉(zhuǎn)化成相應(yīng)的SQL語句的接口
LHPredicate.h //條件語句處理類
LHSqlite.h //真正執(zhí)行數(shù)據(jù)庫操作的類
NSObject+LHDB.h //對外提供一系列數(shù)據(jù)庫操作接口
LHModel
LHObjectInfo.h //聲明了兩個類,LHClassInfo 記錄類信息,LHObjectInfo 記錄類對象屬性的信息(包括屬性Type,Getter和Setter)
NSObject+LHModel.h //提供一系列對象模型信息和數(shù)據(jù)轉(zhuǎn)換相關(guān)的接口
下面我將從我們正常使用數(shù)據(jù)庫的流程去解析LHDB對數(shù)據(jù)庫的管理
創(chuàng)建數(shù)據(jù)庫表
先聲明了一個模型類Teacher,如下:
1 @interface Teacher : NSObject 2 3 @property (nonatomic,strong) NSString* name; 4 5 @property (nonatomic,assign) NSInteger age; 6 7 @property (nonatomic,strong) NSDate* updateDate; 8 9 @property (nonatomic,strong) NSData* data;10 11 @end
然后我們調(diào)用聲明在NSObject+LHDB.h中的類方法createTable,
1 [Teacher createTable];
+ ( LHSqlite* sqlite = sqlite.sqlPath = }
+ (NSString* ([LHDBPath instanceManagerWith:nil].dbPath.length == } }
這里注意的一點就是在createTable方法中,對數(shù)據(jù)庫路徑的獲取,它主要是在LHDBPath中指定了,如果沒有指定,則使用默認,這意味著,我們可以在外部修改這個數(shù)據(jù)庫路徑名,便可以變更數(shù)據(jù)庫了。
接下來調(diào)用了LHSqlite中 executeUpdateWithSqlstring:parameter: 方法真正對數(shù)據(jù)庫進行相關(guān)對操作,在上面的調(diào)用中,該方法的第一個參數(shù)是一個SQL語句串,而第二個參數(shù)是一個屬性值表,上面調(diào)用了一個全局方法createTableString(self),得到一個創(chuàng)建表的SQL語句,該方法聲明在了LHModelStateMent中,其定義如下:
LHModelStateMent.m
1 NSString* createTableString(Class modelClass) 2 { 3 NSMutableString* sqlString = [NSMutableString stringWithString:CREATE_TABLENAME_HEADER]; 4 NSDictionary* stateMentDic = [modelClass getAllPropertyNameAndType]; //key為屬性名,value為屬性類型字符串,如:NSString,i,Q,d等等 5 [sqlString appendString:NSStringFromClass(modelClass)]; //類名做為表名 6 NSMutableString* valueStr = [NSMutableString string]; 7 [stateMentDic enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* obj, BOOL* stop) { 8 obj = [NSString stringWithFormat:@"%@",obj]; 9 [valueStr appendString:tableNameValueString(obj, key)];10 }];11 if (valueStr.length>0) {12 [valueStr deleteCharactersInRange:NSMakeRange(valueStr.length-1, 1)];13 }14 [sqlString appendFormat:@"(%@)",valueStr];15 return sqlString;16 }
1 #define CREATE_TABLENAME_HEADER @"CREATE TABLE IF NOT EXISTS "2 #define INSERT_HEADER @"INSERT INTO "3 #define UPDATE_HEADER @"UPDATE "4 #define DELETE_HEADER @"DELETE FROM "5 #define SELECT_HEADER @"SELECT * FROM "
1 static NSString* tableNameValueString(NSString* type,NSString* name) 2 { 3 //將oc中的type字符串轉(zhuǎn)換成sqlite能認識的類型type,組合成一個字符串,類似@"age INT,"返回,注意后面的","號 4 5 NSString* finalStr = @","; 6 NSString* typeStr = (NSString*)type; 7 if ([typeStr isEqualToString:@"i"]) { 8 return [NSString stringWithFormat:@"%@ %@%@",name,@"INT",finalStr]; 9 }else if ([typeStr isEqualToString:@"f"]) {10 return [NSString stringWithFormat:@"%@ %@%@",name,@"FLOAT",finalStr];11 }else if ([typeStr isEqualToString:@"B"]) {12 return [NSString stringWithFormat:@"%@ %@%@",name,@"BOOL",finalStr];13 }else if ([typeStr isEqualToString:@"d"]) {14 return [NSString stringWithFormat:@"%@ %@%@",name,@"DOUBLE",finalStr];15 }else if ([typeStr isEqualToString:@"q"]) {16 return [NSString stringWithFormat:@"%@ %@%@",name,@"LONG",finalStr];17 }else if ([typeStr isEqualToString:@"NSData"]||[typeStr isEqualToString:@"UIImage"]) {18 return [NSString stringWithFormat:@"%@ %@%@",name,@"BLOB",finalStr];19 }else if ([typeStr isEqualToString:@"NSNumber"]){20 return [NSString stringWithFormat:@"%@ %@%@",name,@"INT",finalStr];21 } else //可見其他類型,將被當(dāng)做TEXT類型處理,包括NSDictionary,NSArray22 return [NSString stringWithFormat:@"%@ %@%@",name,@"TEXT",finalStr];23 }
上面標紅的方法getAllPropertyNameAndType是一個類方法,被聲明在NSObject+LHModel中,返回的是記錄類的所有屬性名和其相應(yīng)類型的字典。
NSObject+LHModel.m
1 + (NSDictionary*)getAllPropertyNameAndType 2 { 3 NSMutableDictionary* dic = [NSMutableDictionary dictionary]; 4 unsigned int count = 0; 5 objc_property_t* property_t = class_copyPropertyList(self, &count); 6 for (int i=0; i<count; i++) { 7 objc_property_t propert = property_t[i]; 8 NSString* propertyName = [NSString stringWithUTF8String:property_getName(propert)]; 9 NSString* propertyType = [NSString stringWithUTF8String:property_getAttributes(propert)];10 [dic setValue:objectType(propertyType) forKey:propertyName];11 }12 free(property_t);13 return dic;14 }
1 static id objectType(NSString* typeString) 2 { 3 //當(dāng)typeString表示是一個oc對象類型的時候,它看起來類似這樣:@"T@\"NSString\",&,N,V_name" 4 //否則,它看起來類似這樣:@"Ti,N,V_age" 5 if ([typeString containsString:@"@"]) { //type為oc對象時,typeString值類似 @"@\"NSString\"",這時候,分割之后返回的strArray[0]是 @"T@",strArray[1]就是@"NSString"了 6 NSArray* strArray = [typeString componentsSeparatedByString:@"\""]; 7 if (strArray.count >= 1) { 8 return strArray[1]; 9 }else10 return nil;11 }else12 return [typeString substringWithRange:NSMakeRange(1, 1)];13 }
下面終于到了最后一步,就是executeUpdateWithSqlstring:parameter:的調(diào)用,它基本的一個過程就是,先打開數(shù)據(jù)庫,這跟我們之前在LHDBPath中指定的路徑關(guān)聯(lián),指定哪個就打開哪個數(shù)據(jù)庫,然后會從緩存中根據(jù)sqlString,讀取相應(yīng)的sqlite3_stmt結(jié)構(gòu)數(shù)據(jù),如果存在,就reset它,然后重新綁定參數(shù),如果不存在,那就進行轉(zhuǎn)換,然后再保存到緩存中,其具體定義如下:
LHSqlite.m
1 - (void)executeUpdateWithSqlstring:(NSString *)sqlString parameter:(NSDictionary*)parameter 2 { 3 Lock; 4 if ([self openDB]) { 5 sqlite3_stmt* stmt = [self stmtWithCacheKey:sqlString]; 6 if (stmt) { 7 for (int i=0; i<parameter.allKeys.count; i++) { 8 [self bindObject:parameter[parameter.allKeys[i]] toColumn:i+1 inStatement:stmt]; 9 }10 if (sqlite3_step(stmt) != SQLITE_DONE) {11 LHSqliteLog(@"error = %@",errorForDataBase(sqlString, _db));12 }13 }14 }else {15 LHSqliteLog(@"打開數(shù)據(jù)庫失敗");16 }17 sqlite3_close(_db);18 UnLock;19 }
其中stmtWithCacheKey:返回sqlite3_stmt結(jié)構(gòu)類型指針,
1 - (sqlite3_stmt*)stmtWithCacheKey:(NSString*)sqlString 2 { 3 sqlite3_stmt* stmt = (sqlite3_stmt*)CFDictionaryGetValue(_stmtCache, (__bridge const void *)([[self.sqlPath lastPathComponent] stringByAppendingString:sqlString])); 4 if (stmt == 0x00) { 5 if (sqlite3_prepare_v2(_db, sqlString.UTF8String, -1, &stmt, nil) == SQLITE_OK) { 6 //緩存stmt 7 CFDictionarySetValue(_stmtCache, (__b
http://www.cnblogs.com/ginvar/p/7226464.html