前言


  最近在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对数据库的管理

  1. 创建数据库表

    先声明了一个模型类Teacher,如下:

 @interface Teacher : NSObject

 @property (nonatomic,strong) NSString* name;

 @property (nonatomic,assign) NSInteger age;

 @property (nonatomic,strong) NSDate* updateDate;

 @property (nonatomic,strong) NSData* data;

 @end

然后我们调用声明在NSObject+LHDB.h中的类方法createTable,

 [Teacher createTable];
//建表
1 + (void)createTable
{
LHSqlite* sqlite = [LHSqlite shareInstance];
sqlite.sqlPath = [self dbPath];
[sqlite executeUpdateWithSqlstring:createTableString(self) parameter:nil];
}
//数据库路径
1 + (NSString*)dbPath
{
if ([LHDBPath instanceManagerWith:nil].dbPath.length == ) {
return DatabasePath;
}else
return [LHDBPath instanceManagerWith:nil].dbPath;
}

这里注意的一点就是在createTable方法中,对数据库路径的获取,它主要是在LHDBPath中指定了,如果没有指定,则使用默认,这意味着,我们可以在外部修改这个数据库路径名,便可以变更数据库了。

接下来调用了LHSqlite中 executeUpdateWithSqlstring:parameter: 方法真正对数据库进行相关对操作,在上面的调用中,该方法的第一个参数是一个SQL语句串,而第二个参数是一个属性值表,上面调用了一个全局方法createTableString(self),得到一个创建表的SQL语句,该方法声明在了LHModelStateMent中,其定义如下:

LHModelStateMent.m

 NSString* createTableString(Class modelClass)
{
NSMutableString* sqlString = [NSMutableString stringWithString:CREATE_TABLENAME_HEADER];
NSDictionary* stateMentDic = [modelClass getAllPropertyNameAndType]; //key为属性名,value为属性类型字符串,如:NSString,i,Q,d等等
[sqlString appendString:NSStringFromClass(modelClass)]; //类名做为表名
NSMutableString* valueStr = [NSMutableString string];
[stateMentDic enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* obj, BOOL* stop) {
obj = [NSString stringWithFormat:@"%@",obj];
[valueStr appendString:tableNameValueString(obj, key)];
}];
if (valueStr.length>) {
[valueStr deleteCharactersInRange:NSMakeRange(valueStr.length-, )];
}
[sqlString appendFormat:@"(%@)",valueStr];
return sqlString;
}
 #define CREATE_TABLENAME_HEADER @"CREATE TABLE IF NOT EXISTS "
#define INSERT_HEADER @"INSERT INTO "
#define UPDATE_HEADER @"UPDATE "
#define DELETE_HEADER @"DELETE FROM "
#define SELECT_HEADER @"SELECT * FROM "
 static NSString* tableNameValueString(NSString* type,NSString* name)
{
//将oc中的type字符串转换成sqlite能认识的类型type,组合成一个字符串,类似@"age INT,"返回,注意后面的","号 NSString* finalStr = @",";
NSString* typeStr = (NSString*)type;
if ([typeStr isEqualToString:@"i"]) {
return [NSString stringWithFormat:@"%@ %@%@",name,@"INT",finalStr];
}else if ([typeStr isEqualToString:@"f"]) {
return [NSString stringWithFormat:@"%@ %@%@",name,@"FLOAT",finalStr];
}else if ([typeStr isEqualToString:@"B"]) {
return [NSString stringWithFormat:@"%@ %@%@",name,@"BOOL",finalStr];
}else if ([typeStr isEqualToString:@"d"]) {
return [NSString stringWithFormat:@"%@ %@%@",name,@"DOUBLE",finalStr];
}else if ([typeStr isEqualToString:@"q"]) {
return [NSString stringWithFormat:@"%@ %@%@",name,@"LONG",finalStr];
}else if ([typeStr isEqualToString:@"NSData"]||[typeStr isEqualToString:@"UIImage"]) {
return [NSString stringWithFormat:@"%@ %@%@",name,@"BLOB",finalStr];
}else if ([typeStr isEqualToString:@"NSNumber"]){
return [NSString stringWithFormat:@"%@ %@%@",name,@"INT",finalStr];
} else //可见其他类型,将被当做TEXT类型处理,包括NSDictionary,NSArray
return [NSString stringWithFormat:@"%@ %@%@",name,@"TEXT",finalStr];
}

上面标红的方法getAllPropertyNameAndType是一个类方法,被声明在NSObject+LHModel中,返回的是记录类的所有属性名和其相应类型的字典。

NSObject+LHModel.m

 + (NSDictionary*)getAllPropertyNameAndType
{
NSMutableDictionary* dic = [NSMutableDictionary dictionary];
unsigned int count = ;
objc_property_t* property_t = class_copyPropertyList(self, &count);
for (int i=; i<count; i++) {
objc_property_t propert = property_t[i];
NSString* propertyName = [NSString stringWithUTF8String:property_getName(propert)];
NSString* propertyType = [NSString stringWithUTF8String:property_getAttributes(propert)];
[dic setValue:objectType(propertyType) forKey:propertyName];
}
free(property_t);
return dic;
}
 static id objectType(NSString* typeString)
{
//当typeString表示是一个oc对象类型的时候,它看起来类似这样:@"T@\"NSString\",&,N,V_name"
//否则,它看起来类似这样:@"Ti,N,V_age"
if ([typeString containsString:@"@"]) { //type为oc对象时,typeString值类似 @"@\"NSString\"",这时候,分割之后返回的strArray[0]是 @"T@",strArray[1]就是@"NSString"了
NSArray* strArray = [typeString componentsSeparatedByString:@"\""];
if (strArray.count >= 1) {
return strArray[1];
}else
return nil;
}else
return [typeString substringWithRange:NSMakeRange(1, 1)];
}

下面终于到了最后一步,就是executeUpdateWithSqlstring:parameter:的调用,它基本的一个过程就是,先打开数据库,这跟我们之前在LHDBPath中指定的路径关联,指定哪个就打开哪个数据库,然后会从缓存中根据sqlString,读取相应的sqlite3_stmt结构数据,如果存在,就reset它,然后重新绑定参数,如果不存在,那就进行转换,然后再保存到缓存中,其具体定义如下:

LHSqlite.m

 - (void)executeUpdateWithSqlstring:(NSString *)sqlString parameter:(NSDictionary*)parameter
{
Lock;
if ([self openDB]) {
sqlite3_stmt* stmt = [self stmtWithCacheKey:sqlString];
if (stmt) {
for (int i=; i<parameter.allKeys.count; i++) {
[self bindObject:parameter[parameter.allKeys[i]] toColumn:i+ inStatement:stmt];
}
if (sqlite3_step(stmt) != SQLITE_DONE) {
LHSqliteLog(@"error = %@",errorForDataBase(sqlString, _db));
}
}
}else {
LHSqliteLog(@"打开数据库失败");
}
sqlite3_close(_db);
UnLock;
}
其中stmtWithCacheKey:返回sqlite3_stmt结构类型指针,
 - (sqlite3_stmt*)stmtWithCacheKey:(NSString*)sqlString
{
sqlite3_stmt* stmt = (sqlite3_stmt*)CFDictionaryGetValue(_stmtCache, (__bridge const void *)([[self.sqlPath lastPathComponent] stringByAppendingString:sqlString]));
if (stmt == 0x00) {
if (sqlite3_prepare_v2(_db, sqlString.UTF8String, -, &stmt, nil) == SQLITE_OK) {
//缓存stmt
CFDictionarySetValue(_stmtCache, (__bridge const void *)([[self.sqlPath lastPathComponent] stringByAppendingString:sqlString]), stmt);
return stmt;
}else {
LHSqliteLog(@"error = %@",errorForDataBase(sqlString, _db));
return nil;
}
}else
sqlite3_reset(stmt);
return stmt;
}

这里使用了缓存了,sqlite3_prepare_v2函数,将一个SQL命令字符串转换成一条prepared语句,存储在sqlite3_stmt类型结构体中,sqlite3_prepare_v2函数代价昂贵,所以通常尽可能的重用prepared语句。

之后,执行[self bindObject]语句,根据传入的值类型,调用相应的sqlite3_bind_xxx方法,进行参数绑定,之后调用sqlite3_step执行,下面是bindObject的定义:

 - (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt {

     if ((!obj) || ((NSNull *)obj == [NSNull null])) {
sqlite3_bind_null(pStmt, idx);
} else if ([obj isKindOfClass:[NSData class]]) {
const void *bytes = [obj bytes];
if (!bytes) { bytes = "";
}
sqlite3_bind_blob(pStmt, idx, bytes, (int)[obj length], SQLITE_STATIC);
}
else if ([obj isKindOfClass:[NSDate class]]) {
if (self.dateFormatter)
sqlite3_bind_text(pStmt, idx, [[self.dateFormatter stringFromDate:obj] UTF8String], -, SQLITE_STATIC);
else
sqlite3_bind_text(pStmt, idx, [[self stringFromDate:obj] UTF8String],-,SQLITE_STATIC);
}
else if ([obj isKindOfClass:[NSNumber class]]) { if (strcmp([obj objCType], @encode(char)) == ) {
sqlite3_bind_int(pStmt, idx, [obj charValue]);
}
else if (strcmp([obj objCType], @encode(unsigned char)) == ) {
sqlite3_bind_int(pStmt, idx, [obj unsignedCharValue]);
}
else if (strcmp([obj objCType], @encode(short)) == ) {
sqlite3_bind_int(pStmt, idx, [obj shortValue]);
}
else if (strcmp([obj objCType], @encode(unsigned short)) == ) {
sqlite3_bind_int(pStmt, idx, [obj unsignedShortValue]);
}
else if (strcmp([obj objCType], @encode(int)) == ) {
sqlite3_bind_int(pStmt, idx, [obj intValue]);
}
else if (strcmp([obj objCType], @encode(unsigned int)) == ) {
sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedIntValue]);
}
else if (strcmp([obj objCType], @encode(long)) == ) {
sqlite3_bind_int64(pStmt, idx, [obj longValue]);
}
else if (strcmp([obj objCType], @encode(unsigned long)) == ) {
sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongValue]);
}
else if (strcmp([obj objCType], @encode(long long)) == ) {
sqlite3_bind_int64(pStmt, idx, [obj longLongValue]);
}
else if (strcmp([obj objCType], @encode(unsigned long long)) == ) {
sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongLongValue]);
}
else if (strcmp([obj objCType], @encode(float)) == ) {
sqlite3_bind_double(pStmt, idx, [obj floatValue]);
}
else if (strcmp([obj objCType], @encode(double)) == ) {
NSLog(@"%f",[obj doubleValue]);
sqlite3_bind_double(pStmt, idx, [obj doubleValue]);
}
else if (strcmp([obj objCType], @encode(BOOL)) == ) {
sqlite3_bind_int(pStmt, idx, ([obj boolValue] ? : ));
}
else {
sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -, SQLITE_STATIC);
}
}
else if ([obj isKindOfClass:[NSArray class]]||[obj isKindOfClass:[NSDictionary class]]) {
@try {
NSData* data = [NSJSONSerialization dataWithJSONObject:obj options:NSJSONWritingPrettyPrinted error:nil];
NSString* jsonStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
sqlite3_bind_text(pStmt, idx, [[jsonStr description] UTF8String], -, SQLITE_STATIC);
}
@catch (NSException *exception) { }
@finally { } }else if ([obj isKindOfClass:NSClassFromString(@"UIImage")]) {
NSData* data = UIImagePNGRepresentation(obj);
const void *bytes = [data bytes];
if (!bytes) {
bytes = "";
}
sqlite3_bind_blob(pStmt, idx, bytes, (int)[data length], SQLITE_STATIC);
}
else {
sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -, SQLITE_STATIC);
}
}

至此,我们的数据库创建和表的创建已经完成了,如果上面的过程你都能清楚了,那么后面数据库的内容,你会觉得还是比较轻松的,因为都差不多^-^。

  2.  SQL插入和查询

  对SQL插入数据记录,可以看到一个数据记录是怎么从Model一步步变成一条SQL语句,而数据的查询,则可以看到SQL查询的数据怎么映射到一个Model中,其它的数据库操作,我觉得类同,不多做阐述。

  • SQL插入数据

  首先,我们假设将插入一条Teacher信息记录,代码如下:

 //直接将model插入数据库
Teacher* teacher = [[Teacher alloc] init];
teacher.name = @"tom";
teacher.age = ;
teacher.data = [@"my name is tom" dataUsingEncoding:NSUTF8StringEncoding];
teacher.updateDate = [NSDate date];
[teacher save];

NSObject+LHDB.m

 - (void)save
{
LHSqlite* sqlite = [LHSqlite shareInstance];
sqlite.sqlPath = [self dbPath];
[sqlite executeUpdateWithSqlstring:insertString(self) parameter:[self lh_ModelToDictionary]];
}

正如你看到的,这个插入数据的方法跟上面数据库创表时很像,只不过它是类方法,而save是一个实例方法,仅此而已,唯一不同的只是在调用

executeUpdateWithSqlstring:parameter: 传入的两个参数,第一个是插入SQL语句,第二个是SQL语句参数表,看看它们的具体定义:

LHModelStateMent.m

 NSString* insertString(id model)
{
NSMutableString* sqlString = [NSMutableString stringWithString:INSERT_HEADER];
[sqlString appendString:NSStringFromClass([model class])];
NSDictionary* valueDic = [model lh_ModelToDictionary];
NSMutableString* keyStr = [NSMutableString string];
NSMutableString* valueStr = [NSMutableString string];
for (int i=; i<valueDic.allKeys.count; i++) {
NSDictionary* dic = insertValueString(valueDic.allKeys[i]);
[keyStr appendFormat:@"%@,",dic.allKeys[]];
[valueStr appendFormat:@"%@,",dic[dic.allKeys[]]];
}
[sqlString appendFormat:@"(%@) VALUES (%@)",[keyStr substringToIndex:keyStr.length-],[valueStr substringToIndex:valueStr.length-]]; //这里sqlString的值类似于"insert into tablename (name,age) values (?,?)",后面的值中的?将会在后面调用sqlite3_bind_xx绑定参数的时候,被相应参数值依次替代
return sqlString;
}
 static NSDictionary* insertValueString(id value,NSString* name,NSString* type)
{
return @{name:@"?"};

下面就到了我们的重点了,-(NSDictionary*)lh_ModelToDictionary ,它的功能就是将对象模型Model转换成表结构的方法,具体来看它的实现:

NSObject+LHModel.m

 - (NSDictionary*)lh_ModelToDictionary
{
if ([self isKindOfClass:[NSArray class]]) {
return nil;
}else if ([self isKindOfClass:[NSDictionary class]]){
return (NSDictionary*)self;
}else if ([self isKindOfClass:[NSString class]]||[self isKindOfClass:[NSData class]]) {
return [NSJSONSerialization JSONObjectWithData:dataFromObject(self) options:NSJSONReadingMutableContainers error:nil];
}else {
NSMutableDictionary* dic = [NSMutableDictionary dictionary];
ModelSetContext context = {};
context.classInfo = (__bridge void *)(dic);
context.model = (__bridge void *)(self); //注意: 这里保存了OC对象本身,后续的属性遍历中,会使用这个model去调用它的属性getter等方法
LHClassInfo* classInfo;
//判断缓存中是否有这个类的信息
if ([LHClassInfo isCacheWithClass:object_getClass(self)]) {
classInfo = [LHClassInfo classInfoWithClass:object_getClass(self)];
}else
//这里记录类信息,并缓存类信息
classInfo = [[LHClassInfo alloc] initWithClass:object_getClass(self)]; //遍历objectInfoDic的key(属性名)和value(结构类型,LHObjectInfo*),得到一个新字典(key:属性名,value:字段值),保存在context.classInfo中,也就是上面的dic;
CFDictionaryApplyFunction((__bridge CFMutableDictionaryRef)classInfo.objectInfoDic, ModelGetValueToDic, &context);
return dic;
}
return nil;
}

注意的地方就是,当OC对象类型是非集合类的时候,首先,它定义了一个临时结构体类型 ModelSetContext context = {0},并初始化了classInfo(可变表结构),和model(模型本身,self)字段值,然后调用LHClassInfo的类方法isCacheWithClass判断对象所属类的信息是否已经登记在全局缓存中,如果有,则返回类信息指针,结构类型为LHClassInfo,否则,调用-initWithClass创建类信息对象,同时保存到缓存中,最后调用CFDictionaryApplyFunction,这个方法苹果文档的描述,就是“Calls a function once for each key-value pair in a dictionary.”,就是遍历字典的每个键值对,它的第一个参数是要操作的字典dictionary,第二个参数是回调方法,第三个参数则是回调方法的第三个参数,将刚刚创建的context地址传入,这个方法,遍历类信息中的所有属性信息,然后利用objc_msgSend调用属性信息中保存的getter方法,得到每个属性的值,并将键值保存到context的classInfo中,从而得到一个记录了对象属性和其对应值的表,相关方法定义如下:

NSObject+LHModel.m

//获取属性对应的字段的值,保存在context->classInfo中
static void ModelGetValueToDic(const void* key,const void* value,void* context)
{
ModelSetContext* modelContext = context;
NSMutableDictionary* dic = (__bridge NSMutableDictionary *)(modelContext->classInfo);
id object = (__bridge id)(modelContext->model);
NSString* dicKey = (__bridge NSString *)(key);
LHObjectInfo* objectInfo = (__bridge LHObjectInfo*)(value);
if (objectInfo) {
if (objectInfo.cls) {
[dic setValue:((id(*)(id,SEL))(void*) objc_msgSend)(object,objectInfo.get) forKey:dicKey];;
}else if (objectInfo.type.length>) {
NSNumber* number = getBaseTypePropertyValue(object, objectInfo.baseTypeEcoding, objectInfo.get);
[dic setValue:number forKey:dicKey];
}
}
}
 static NSNumber* getBaseTypePropertyValue(__unsafe_unretained NSObject* object, NSUInteger type,SEL get)
{
switch (type) {
case LHBaseTypeEcodingINT: return @(((int (*)(id, SEL))(void *) objc_msgSend)(object, get)); case LHBaseTypeEcodingLONG: return @(((long (*)(id, SEL))(void *) objc_msgSend)(object,get)); case LHBaseTypeEcodingULONG: return @(((NSUInteger(*)(id,SEL))(void*) objc_msgSend)(object,get)); case LHBaseTypeEcodingFLOAT: return @(((float(*)(id,SEL))(void*) objc_msgSend)(object,get)); case LHBaseTypeEcodingDOUBLE: return @(((double(*)(id,SEL))(void*) objc_msgSend)(object,get)); case LHBaseTypeEcodingBOOL: return @(((BOOL(*)(id,SEL))(void*) objc_msgSend)(object,get)); case LHBaseTypeEcodingCHAR: return @(((char(*)(id,SEL))(void*) objc_msgSend)(object,get)); default:
return nil;
break;
}
}

LHObjectInfo.h

 //对象类信息
@interface LHClassInfo : NSObject @property (nonatomic)Class cls; @property (nonatomic)Class superClass; @property (nonatomic)Class metaClass; @property (nonatomic,assign) BOOL isMetaClass; @property (nonatomic,strong) NSMutableDictionary* objectInfoDic; - (instancetype)initWithClass:(Class)cls; + (BOOL)isCacheWithClass:(Class)cls; + (LHClassInfo*)classInfoWithClass:(Class)cls; - (LHObjectInfo*)objectInfoWithName:(NSString*)name; @end

其中objectInfoDic记录了对象所有属性的信息,它的原型是LHObjectInfo,

 typedef NS_ENUM(NSUInteger,LHBaseTypeEcoding) {
LHBaseTypeEcodingUnknow,
LHBaseTypeEcodingINT,
LHBaseTypeEcodingLONG,
LHBaseTypeEcodingULONG,
LHBaseTypeEcodingCHAR,
LHBaseTypeEcodingFLOAT,
LHBaseTypeEcodingBOOL,
LHBaseTypeEcodingDOUBLE
}; typedef NS_ENUM(NSUInteger,LHNSTypeEcoding) {
LHNSTypeUNknow,
LHNSTypeNSString,
LHNSTypeNSNumber,
LHNSTypeNSDate,
LHNSTypeNSData,
LHNSTypeNSURL,
LHNSTypeNSArray,
LHNSTypeNSDictionary,
LHNSTypeUIImage
}; //描述对象属性的结构
@interface LHObjectInfo : NSObject @property (nonatomic) Class cls; //当属性是OC对象时,cls记录属性对象所属类,否则为基础类型时,值为nil @property (nonatomic) objc_property_t property_t; //属性 @property (nonatomic,copy) NSString* name; //属性名 @property (nonatomic,assign) LHBaseTypeEcoding baseTypeEcoding;  //自定义基础数据类型编码 @property (nonatomic,assign) LHNSTypeEcoding nsTypeEcoding; //自定义OC对象类型编码 @property (nonatomic) SEL set; //属性的setter方法 @property (nonatomic) SEL get; //属性的getter方法 @property (nonatomic,copy) NSString* type; //对象类型,如:NSString,i,Q,d等等 - (instancetype)initWithProperty:(objc_property_t)property; @end

LHObjectInfo.m

LHClassInfo类实现

 - (instancetype)initWithClass:(Class)cls
{
self = [super init];
if (self) {
_cls = cls;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
objectInfoCacheDic = [NSMutableDictionary dictionary];
});
_objectInfoDic = [NSMutableDictionary dictionary]; //遍历属性,为每个属性生成 LHObjectInfo 对象 并保存在类字典objectInfoDic中,注意是类
unsigned int count;
objc_property_t* t = class_copyPropertyList(cls, &count);
for (int i=; i<count; i++) { LHObjectInfo* info = [[LHObjectInfo alloc] initWithProperty:t[i]];
[_objectInfoDic setValue:info forKey:[NSString stringWithUTF8String:property_getName(t[i])]];
}
free(t); //记录类名对应的类信息,并保存在静态全局缓存字典objectInfoCacheDic中
[objectInfoCacheDic setValue:self forKey:NSStringFromClass(cls)];
}
return self;
} + (BOOL)isCacheWithClass:(Class)cls
{
if ([objectInfoCacheDic objectForKey:NSStringFromClass(cls)]) {
return YES;
}
return NO;
} + (LHClassInfo*)classInfoWithClass:(Class)cls
{
return objectInfoCacheDic[NSStringFromClass(cls)];
}

LHObjectInfo类实现

 - (instancetype)initWithProperty:(objc_property_t)property
{
if (property == nil) return nil;
self = [super init];
if (self) {
_property_t = property;
_name = [NSString stringWithUTF8String:property_getName(property)]; //记录属性名 unsigned int count;
objc_property_attribute_t* t = property_copyAttributeList(property, &count);
//for (unsigned int i=0; i<count; i++) {
if(count > ){
//源代码是一个循环,其实这里只是获取第一个属性值,也就是T,即是属性的类型,所以我这里改成了一个判断语句,将t[i] 改成 t[0]
objc_property_attribute_t p = t[];
size_t len = strlen(p.value); //假设属性是NSString,则p.name = "T",p.value = "@\"NString\"";
if (len > ) {
char name[len - ];
name[len - ] = '\0';
memcpy(name, p.value + , len - );
_cls = objc_getClass(name); //记录类
_type = [NSString stringWithUTF8String:name]; //记录对象类型
_nsTypeEcoding = nsTypeEcoding(_type); //记录oc对象编码,目前只支持NSString,NSNumber,NSDate,NSData,NSURL,NSArray,NSDictionary,UIImage
//break;
}else {
//基础数据类型
_type = [NSString stringWithUTF8String:p.value];
if (_type.length>) {
_type = [_type substringToIndex:];
}
if (_type.length>) {
_baseTypeEcoding = baseTypeEcoding([_type characterAtIndex:]);
}
//break;
}
}
free(t); if (_name.length>) {
_set = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",[[_name substringToIndex:] uppercaseString],[_name substringFromIndex:]]);
_get = NSSelectorFromString(_name);
}
}
return self;
}

LHClassInfo和LHObjectInfo分别保存了类的相关信息和属性信息,在LHObjectInfo的initWithProperty方法中,大家也看到最后属性的setter是由"set+属性名(首字母大写)"得到的,getter也是和属性名一致的,所以大家在定义模型的时候不要去自己自定义自己的属性setter和getter方法,这会导致可能不可预知的数据错误,同时,不应该去声明变量,应该总是使用属性,因为我们在转换的时候都是使用了模型的属性列表来做转换。

好了,至此,我们又一次完成了对数据记录插入的分析^-^。

  • SQL查询数据

  数据的查询,其实就是根据条件,在相应的表中查询出满足条件的数据集合。  

  同样,从外部调用开始:

 //查询数据
LHPredicate* predicate = [LHPredicate predicateWithFormat:@"name = '%@'",@"tom"]; NSArray* result = [Teacher selectWithPredicate:predicate];
NSLog(@"result1 = %@",result);

NSObject+LHDB.m

 //查询记录不需要对象方法
+ (NSArray*)selectWithPredicate:(LHPredicate*)predicate
{
LHSqlite* sqlite = [LHSqlite shareInstance];
sqlite.sqlPath = [self dbPath];
NSArray* array = [sqlite executeQueryWithSqlstring:selectString(self, predicate)];
NSMutableArray* resultArray = [NSMutableArray array];
for (NSDictionary* dic in array) {
[resultArray addObject:[self lh_ModelWithDictionary:dic]];
}
return resultArray;
}

LHModelStateMent.m

 NSString* selectString(Class modelClass,LHPredicate* predicate)
{
NSMutableString* selectStr = [NSMutableString stringWithString:SELECT_HEADER];
[selectStr appendString:NSStringFromClass(modelClass)];
if (predicate.predicateFormat) {
[selectStr appendFormat:@" WHERE %@",predicate.predicateFormat];
}
if (predicate.sortString) {
[selectStr appendFormat:@" ORDER BY %@",predicate.sortString];
}
return selectStr;
}

LHSqlite.m

 - (NSArray*)executeQueryWithSqlstring:(NSString*)sqlString
{
Lock;
NSArray* resultArray = 0x00;
if ([self openDB]) {
sqlite3_stmt* stmt = [self stmtWithCacheKey:sqlString];
if (stmt) {
NSMutableArray* dataSource = [NSMutableArray array];
int count = sqlite3_column_count(stmt);
while (sqlite3_step(stmt) == SQLITE_ROW) {
NSMutableDictionary* dataDic = [NSMutableDictionary dictionary];
for (int i=; i<count; i++) {
int type = sqlite3_column_type(stmt, i);
NSString* propertyName = [NSString stringWithUTF8String:sqlite3_column_name(stmt, i)];
NSObject* value = dataWithDataType(type, stmt, i);
[dataDic setValue:value forKey:propertyName];
}
[dataSource addObject:dataDic];
}
resultArray = dataSource;
}
}
sqlite3_close(_db);
UnLock;
return resultArray;
}

这里,前面类似创建表、插入、更新、删除,都是使用了LHSqlite的executeUpdateWithSqlstring:parameter:方法,而查询调用的是executeQueryWithSqlstring:方法,这里将每条数据查询出来之后,通过dataWithDataType获得每个属性对应的值,得到每个记录的数据存放在字典中,最后将每个表放到数组对象中,返回。dataWithDataType定义如下:

LHSqlite.m

 static NSObject* dataWithDataType(int type,sqlite3_stmt * statement,int index)
{
if (type == SQLITE_INTEGER) {
int value = sqlite3_column_int(statement, index);
return [NSNumber numberWithInt:value];
}else if (type == SQLITE_FLOAT) {
float value = sqlite3_column_double(statement, index);
return [NSNumber numberWithFloat:value];
}else if (type == SQLITE_BLOB) {
const void *value = sqlite3_column_blob(statement, index);
int bytes = sqlite3_column_bytes(statement, index);
return [NSData dataWithBytes:value length:bytes];
}else if (type == SQLITE_NULL) {
return nil;
}else if (type == SQLITE_TEXT) {
return [NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, index)];
}else {
return nil;
}
}

到此,我们看回selectWithPredicate:方法,方法中取出从executeQueryWithSqlstring:返回的结果后,遍历每个记录,并调用了lh_ModelWithDictionary这个类方法,得到每个对象Model,

 //从数据字典中还原model
+ (id)lh_ModelWithDictionary:(NSDictionary*)dic
{
if (!dic ||![dic isKindOfClass:[NSDictionary class]]) return nil;
NSObject* object = [[self alloc] init]; //创建一个类的对象
ModelSetContext context = {};
LHClassInfo* info;
//判断缓存中是否有这个类的信息
if ([LHClassInfo isCacheWithClass:self]) {
info = [LHClassInfo classInfoWithClass:self];
}else
info = [[LHClassInfo alloc] initWithClass:self];
context.classInfo = (__bridge void *)(info);
context.model = (__bridge void *)(object); //将类对象赋给model,下面的遍历方法中会使用该对象去调用属性的setter方法 CFDictionaryApplyFunction((__bridge CFDictionaryRef)dic, ModelSetValueToProperty, &context);
return object;
}

这里先创建了一个OC对象,然后获取类信息,因为我们在外部调用的时候,[Teacher selectWithPredicate:predicate]; 所以这里创建的类信息自然是Teacher类的信息结构,同样这里也声明了一个ModelSetContext结构对象,但是这里classInfo保存的是一个描述Model类信息的LHClassInfo指针,它被传入ModelSetValueToProperty方法中,由于LHClassInfo保存了类所有属性的信息,通过属性名和数据字典的key值对比,找到相应的属性,最后调用对象属性的setter方法,相关方法定义如下:

NSObject+LHModel.m

 static void ModelSetValueToProperty(const void *key, const void *value, void *context)
{
ModelSetContext* modelContext = context;
NSString* dicKey = (__bridge NSString *)(key);
id dicValue = (__bridge id)(value);
LHObjectInfo* objectInfo = [((__bridge LHClassInfo*)modelContext->classInfo) objectInfoWithName:dicKey]; //根据属性名获取属性信息结构
NSObject* object = (__bridge NSObject*)modelContext->model;
if (objectInfo) {
if (objectInfo.cls) {
setNSTypePropertyValue(object, dicValue, objectInfo.nsTypeEcoding, objectInfo.set); }else if (objectInfo.type.length>) {
NSNumber* number = numberWithValue(dicValue);
setBaseTypePropertyValue(object, number, objectInfo.baseTypeEcoding,objectInfo.set);
}
}
}
 static NSNumber* numberWithValue(__unsafe_unretained id value)
{
if (!value) {
return nil;
}
if ([value isKindOfClass:[NSNumber class]]) return value;
if ([value isKindOfClass:[NSString class]]) {
if ([value containsString:@"."]) { const char *cstring = ((NSString *)value).UTF8String;
if (!cstring) return nil;
double num = atof(cstring);
if (isnan(num) || isinf(num)) return nil; //判断浮点数是否是非数字或者无限大
return @(num);
}else {
const char *cstring = ((NSString*)value).UTF8String;
if (!cstring) return nil;
NSNumber* number = @(atoll(cstring));
if (!atoll(cstring)) {
number = [NSNumber numberWithChar:*(cstring+)];
}
return number;
}
}
return nil; }
 static void setBaseTypePropertyValue(__unsafe_unretained NSObject* object,__unsafe_unretained NSNumber* value, NSUInteger type,SEL set)
{
switch (type) {
case LHBaseTypeEcodingINT:
((void (*)(id, SEL, int))(void *) objc_msgSend)(object, set, value.intValue);
break; case LHBaseTypeEcodingLONG:
((void(*)(id,SEL,long))(void*) objc_msgSend)(object,set,value.integerValue);
break;
case LHBaseTypeEcodingULONG:
((void(*)(id,SEL,long))(void*) objc_msgSend)(object,set,value.unsignedIntegerValue);
break; case LHBaseTypeEcodingFLOAT:
((void(*)(id,SEL,float))(void*) objc_msgSend)(object,set,value.floatValue);
break;
case LHBaseTypeEcodingDOUBLE:
((void(*)(id,SEL,double))(void*) objc_msgSend)(object,set,value.doubleValue);
break;
case LHBaseTypeEcodingBOOL:
((void(*)(id,SEL,BOOL))(void*) objc_msgSend)(object,set,value.boolValue);
break; case LHBaseTypeEcodingCHAR:
((void(*)(id,SEL,char))(void*) objc_msgSend)(object,set,value.charValue);
break;
default:
((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,nil);
break;
}
} static void setNSTypePropertyValue(__unsafe_unretained id object,__unsafe_unretained id value,LHNSTypeEcoding typeEcoding,SEL set)
{
switch (typeEcoding) {
case LHNSTypeUNknow:
((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);
break; case LHNSTypeNSString:
//将其它类型转成nsstring类型
if ([value isKindOfClass:[NSString class]]) {
((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);
}else if ([value isKindOfClass:[NSNumber class]]) {
((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,[value stringValue]);
}else if ([value isKindOfClass:[NSData class]]) {
((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,[[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding]);
}else if ([value isKindOfClass:[NSDate class]]) {
((void(*)(id,SEL,NSString*))(void*) objc_msgSend)(object,set,stringFormDate(value));
}else
((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);
break; case LHNSTypeNSNumber:
((void(*)(id,SEL,NSNumber*))(void*) objc_msgSend)(object,set,numberWithValue(value));
break; case LHNSTypeNSDate:
if ([value isKindOfClass:[NSDate class]]) {
((void(*)(id,SEL,NSDate*))(void*) objc_msgSend)(object,set,value);
}else if ([value isKindOfClass:[NSString class]]) {
((void(*)(id,SEL,NSDate*))(void*) objc_msgSend)(object,set,dateFromString(value));
}else if ([value isKindOfClass:[NSData class]]) {
NSString* dateStr = [[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding];
((void(*)(id,SEL,NSDate*))(void*) objc_msgSend)(object,set,dateFromString(dateStr));
}else
((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);
break; case LHNSTypeNSData:
((void(*)(id,SEL,NSData*))(void*) objc_msgSend)(object,set,dataFromObject(value));
break; case LHNSTypeNSURL:
((void(*)(id,SEL,NSURL*))(void*) objc_msgSend)(object,set,urlFromObject(value));
break; case LHNSTypeNSArray:
((void(*)(id,SEL,NSArray*))(void*) objc_msgSend)(object,set,arrayFromObject(value));
break; case LHNSTypeNSDictionary:
((void(*)(id,SEL,NSDictionary*))(void*) objc_msgSend)(object,set,dicFromObject(value));
break; case LHNSTypeUIImage:
((void(*)(id,SEL,UIImage*))(void*) objc_msgSend)(object,set,imageFromObject(value));
break; default:
break;
}
}

至此,我们已经完成了对数据库数据插入和查询的分析,至于删除和更新操作,基本都是一样的流程,这里不多阐述,大家可以去看源代码实现:https://github.com/ginvar/LHDB

总结


  LHDB虽然是对象存储型数据库,思路还是非常不错的,至少在使用上比较方便,但是,它还有一些不足我不得不去指出,在应对比较复杂的数据库操作的时候,或者数据库更新上面,我发现其实它并不支持,就比如说当我们app发布后,假设我们业务需求需要新增数据库表字段,它就不行,它需要你将数据库表都删除之后,然后再重新创建,这样意味着用户会丢弃了他的本地数据,这显然不合理,而我们目前一个强大的app,必然要应对这样复杂的使用环境,此外,在阅读源码的过程中,我也自己改动了一些方法实现,可能我觉得有些实现还不够精简,不过它作为学习拓展,我觉得还是一个比较不错的开源代码的,就这样了^-^。



runtime实现对象存储型数据库——LHDB的更多相关文章

  1. RRDTool 存储原理简介——基于时间序列的环型数据库

    转自:http://www.jianshu.com/p/b925b1584ab2 RRDTool是一套监测工具,可用于存储和展示被监测对象随时间的变化情况.比如,我们在 Windows 电脑上常见的内 ...

  2. FMDB将对象放进数据库[二](使用runtime)

    @import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...

  3. java开发之阿里云对象存储OSS和云数据库Memcache的使用

    web开发中标配:aliyun ECS(阿里云服务器),aliyun RDS(阿里云数据库),aliyun OSS(阿里云对象存储),aliyun Memcache(阿里云缓存数据库). 今天就介绍下 ...

  4. 在ThinkPHP框架(5.0.24)下引入Ueditor并实现向七牛云对象存储上传图片同时将图片信息保存到MySQL数据库,同时实现lazyload懒加载

    这是我花了很多天的时间才得以真正实现的一组需求. 文章后面有完整Demo的GitHub链接. 一. 需求描述 1. 应用是基于ThinkPHP5开发的: 2. 服务器环境是LNMP,PHP版本是7.2 ...

  5. 【巨杉数据库Sequoiadb】巨杉⼯具系列之一 | ⼤对象存储⼯具sdblobtool

    近期,巨杉数据库正式推出了完整的SequoiaDB 工具包,作为辅助工具,更好地帮助大家使用和运维管理分布式数据库.为此,巨杉技术社区还将持续推出工具系列文章,帮助大家了解巨杉数据库丰富的工具矩阵. ...

  6. DevOps之存储和数据库

    唠叨话 关于德语噢屁事的知识点,仅提供专业性的精华汇总,具体知识点细节,参考教程网址,如需帮助,请留言. <数据(Data)> 了解有关数据部分.涉及存储及数据库的概念:知识与技能的层次( ...

  7. HDFS对象存储--Ozone架构设计

    前言 如今做云存储的公司非常多,举2个比較典型的AWS的S3和阿里云.他们都提供了一个叫做对象存储的服务,就是目标数据是从Object中进行读写的,然后能够通过key来获取相应的Object,就是所谓 ...

  8. 关于数据库管理系统DBMS--关系型数据库(MySQL/MariaDB)

    数据库的结构(3种):层次,网状,关系型(用的最多): DBMS的三层模型: 视图层:面向最终用户: 逻辑层:面向程序员或DBA: 物理层:面向系统管理员: 关系型数据库管理系统——RDBMS: 主要 ...

  9. 非关系统型数据库-mangodb

    第三十六课 非关系统型数据库-mangodb 目录 二十四 mongodb介绍 二十五 mongodb安装 二十六 连接mongodb 二十七 mongodb用户管理 二十八 mongodb创建集合. ...

随机推荐

  1. HTMLCollection 对象详解,以及为什么循环获取的dom合集操作可能会出现下标不正确的情况?

    有时候循环dom合集,然后操作其中的某些dom之后,发现下标不正确了 比如我们要删除一个dom合集的时候: var selectDom = document.getElementsByClassNam ...

  2. oracle 体系结构简介

    1.1.SGA(system global area) SGA是oracle Instance的基本组成部分,在示例启动是分配.是一组包含一个oracle实例的数据和控制信息的共享内存结构.主要用于存 ...

  3. Tomcat7配置管理员帐号密码及权限

    在使用tomcat时,若要使用管理监控功能,需要用用户名密码登录使用,而tomcat7默认是将用户是注释的,所以需要配置后使用, 配置文件为根目录下的/conf/tomcat-users.xml文件. ...

  4. cms基本概念(dedecms,phpcms)

    1.什么是cms? cms是"Content Management System"的缩写,意为"内容管理系统". 内容管理系统是企业信息化建设和电子政务的新宠, ...

  5. Vue按需加载提升用户体验

    Vue官方文档异步组件: 在大型应用中,我们可能需要将应用拆分为多个小模块,按需从服务器下载.为了让事情更简单, Vue.js 允许将组件定义为一个工厂函数,动态地解析组件的定义.Vue.js 只在组 ...

  6. oracle表空间自增长

    方式一:通过修改oracle database control 修改 第一步,点击开始--所有程序--Oracle - OraDb11g_home1--Database Control 第二步,通过g ...

  7. 自动清理SQLServerErrorLog错误日志避免太大

    问题描述:开启SQLServer自动备份后,备份文件越来越多,有没有及时清理,导致服务器空间不足,备份出错,以至于出现几个G的ErrorLog文件,影响系统的登录管理. 解决办法:定期清理SQLSer ...

  8. 跨域访问之JSONP

    跨域 在平常的工作中常常会遇到A站点的需要访问B站点的资源. 这时就产生了跨域访问. 跨域是指从一个域名的网页去请求另一个域名的资源.浏览器遵循同源策略,不允许A站点的Javascript 读取B站点 ...

  9. 针对Oracle数据库表中的数据的常见操作

    1.查询表中所有数据 select * from 表名; 例:select * from stu; 2.查询的同时修改表中数据 select * from 表名  for update; 例:sele ...

  10. php Base64编码文件二进制流主要使用

    <?php header( "Content-type: image/jpeg"); //$filename='1.jpg'; //$content=file_get_con ...