前言
最近在GitHub上看了一份关于基于runtime封装的对象存储型数据库的开源代码,觉得非常值得分享记录一下,在IOS中对数据库的操作一般通过CoreData和SQLite,CoreData 虽然能够将OC对象转化成数据,保存在SQLite数据库文件中,也能够将保存在数据库中的数据还原成OC对象,期间不需要编写SQL语句,但使用起来并不是那么方便,而SQLite则需要用户编写相应的数据库语句,看起来不是很美观,所以大家一般都会将其进行封装,让其使用起来更加方便,而LHDB就是建立在SQLite之上的封装。现在,我们来看其是如何实现的,不过在此之前,我先假设大家对OC的runtime机制和sqlite有一定的理解。附上源码下载地址:github链接
实现
所谓的基于对象存储的数据库,顾名思义,就是一切对数据的操作都是对对象模型的操作,通过给对象模型属性赋值,然后将对象交由底层去解析,转化成相应的SQL语句,然后执行数据库操作,我们先看看整体的一个LHDB目录结构(这里仅写出头文件):
LHDB
LHDBPath.h //记录数据库路径
LHModelStateMent.h //提供一系列将对象模型转化成相应的SQL语句的接口
LHPredicate.h //条件语句处理类
LHSqlite.h //真正执行数据库操作的类
NSObject+LHDB.h //对外提供一系列数据库操作接口
LHModel
LHObjectInfo.h //声明了两个类,LHClassInfo 记录类信息,LHObjectInfo 记录类对象属性的信息(包括属性Type,Getter和Setter)
NSObject+LHModel.h //提供一系列对象模型信息和数据转换相关的接口
下面我将从我们正常使用数据库的流程去解析LHDB对数据库的管理
创建数据库表
先声明了一个模型类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
然后我们调用声明在NSObject+LHDB.h中的类方法createTable,
1 [Teacher createTable];
+ ( LHSqlite* sqlite = sqlite.sqlPath = }
+ (NSString* ([LHDBPath instanceManagerWith:nil].dbPath.length == } }
这里注意的一点就是在createTable方法中,对数据库路径的获取,它主要是在LHDBPath中指定了,如果没有指定,则使用默认,这意味着,我们可以在外部修改这个数据库路径名,便可以变更数据库了。
接下来调用了LHSqlite中 executeUpdateWithSqlstring:parameter: 方法真正对数据库进行相关对操作,在上面的调用中,该方法的第一个参数是一个SQL语句串,而第二个参数是一个属性值表,上面调用了一个全局方法createTableString(self),得到一个创建表的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字符串转换成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 //可见其他类型,将被当做TEXT类型处理,包括NSDictionary,NSArray22 return [NSString stringWithFormat:@"%@ %@%@",name,@"TEXT",finalStr];23 }
上面标红的方法getAllPropertyNameAndType是一个类方法,被声明在NSObject+LHModel中,返回的是记录类的所有属性名和其相应类型的字典。
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 //当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:的调用,它基本的一个过程就是,先打开数据库,这跟我们之前在LHDBPath中指定的路径关联,指定哪个就打开哪个数据库,然后会从缓存中根据sqlString,读取相应的sqlite3_stmt结构数据,如果存在,就reset它,然后重新绑定参数,如果不存在,那就进行转换,然后再保存到缓存中,其具体定义如下:
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(@"打开数据库失败");16 }17 sqlite3_close(_db);18 UnLock;19 }
其中stmtWithCacheKey:返回sqlite3_stmt结构类型指针,
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