runtime实现对象存储型数据库——LHDB

前言


 

  最近在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,如下:

 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];
//建表
1
+ (void)createTable 2 { 3 LHSqlite* sqlite = [LHSqlite shareInstance]; 4 sqlite.sqlPath = [self dbPath]; 5 [sqlite executeUpdateWithSqlstring:createTableString(self) parameter:nil]; 6 }
//数据库路径
1
+ (NSString*)dbPath 2 { 3 if ([LHDBPath instanceManagerWith:nil].dbPath.length == 0) { 4 return DatabasePath; 5 }else 6 return [LHDBPath instanceManagerWith:nil].dbPath; 7 }

这里注意的一点就是在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,NSArray
22         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         }else
10             return nil;
11     }else
12         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, (__bridge const void *)([[self.sqlPath lastPathComponent] stringByAppendingString:sqlString]), stmt);
 8             return stmt;
 9         }else {
10             LHSqliteLog(@"error = %@",errorForDataBase(sqlString, _db));
11             return nil;
12         }
13     }else
14         sqlite3_reset(stmt);
15     return stmt;
16 }

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

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

 1 - (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt {
 2     
 3     if ((!obj) || ((NSNull *)obj == [NSNull null])) {
 4         sqlite3_bind_null(pStmt, idx);
 5     }
 6     
 7     else if ([obj isKindOfClass:[NSData class]]) {
 8         const void *bytes = [obj bytes];
 9         if (!bytes) {
10 
11             bytes = "";
12         }
13         sqlite3_bind_blob(pStmt, idx, bytes, (int)[obj length], SQLITE_STATIC);
14     }
15     else if ([obj isKindOfClass:[NSDate class]]) {
16         if (self.dateFormatter)
17             sqlite3_bind_text(pStmt, idx, [[self.dateFormatter stringFromDate:obj] UTF8String], -1, SQLITE_STATIC);
18         else
19             sqlite3_bind_text(pStmt, idx, [[self stringFromDate:obj] UTF8String],-1,SQLITE_STATIC);
20     }
21     else if ([obj isKindOfClass:[NSNumber class]]) {
22         
23         if (strcmp([obj objCType], @encode(char)) == 0) {
24             sqlite3_bind_int(pStmt, idx, [obj charValue]);
25         }
26         else if (strcmp([obj objCType], @encode(unsigned char)) == 0) {
27             sqlite3_bind_int(pStmt, idx, [obj unsignedCharValue]);
28         }
29         else if (strcmp([obj objCType], @encode(short)) == 0) {
30             sqlite3_bind_int(pStmt, idx, [obj shortValue]);
31         }
32         else if (strcmp([obj objCType], @encode(unsigned short)) == 0) {
33             sqlite3_bind_int(pStmt, idx, [obj unsignedShortValue]);
34         }
35         else if (strcmp([obj objCType], @encode(int)) == 0) {
36             sqlite3_bind_int(pStmt, idx, [obj intValue]);
37         }
38         else if (strcmp([obj objCType], @encode(unsigned int)) == 0) {
39             sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedIntValue]);
40         }
41         else if (strcmp([obj objCType], @encode(long)) == 0) {
42             sqlite3_bind_int64(pStmt, idx, [obj longValue]);
43         }
44         else if (strcmp([obj objCType], @encode(unsigned long)) == 0) {
45             sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongValue]);
46         }
47         else if (strcmp([obj objCType], @encode(long long)) == 0) {
48             sqlite3_bind_int64(pStmt, idx, [obj longLongValue]);
49         }
50         else if (strcmp([obj objCType], @encode(unsigned long long)) == 0) {
51             sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongLongValue]);
52         }
53         else if (strcmp([obj objCType], @encode(float)) == 0) {
54             sqlite3_bind_double(pStmt, idx, [obj floatValue]);
55         }
56         else if (strcmp([obj objCType], @encode(double)) == 0) {
57             NSLog(@"%f",[obj doubleValue]);
58             sqlite3_bind_double(pStmt, idx, [obj doubleValue]);
59         }
60         else if (strcmp([obj objCType], @encode(BOOL)) == 0) {
61             sqlite3_bind_int(pStmt, idx, ([obj boolValue] ? 1 : 0));
62         }
63         else {
64             sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
65         }
66     }
67     else if ([obj isKindOfClass:[NSArray class]]||[obj isKindOfClass:[NSDictionary class]]) {
68         @try {
69             NSData* data = [NSJSONSerialization dataWithJSONObject:obj options:NSJSONWritingPrettyPrinted error:nil];
70             NSString* jsonStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
71             sqlite3_bind_text(pStmt, idx, [[jsonStr description] UTF8String], -1, SQLITE_STATIC);
72         }
73         @catch (NSException *exception) {
74             
75         }
76         @finally {
77             
78         }
79         
80     }else if ([obj isKindOfClass:NSClassFromString(@"UIImage")]) {
81         NSData* data = UIImagePNGRepresentation(obj);
82         const void *bytes = [data bytes];
83         if (!bytes) {
84             bytes = "";
85         }
86         sqlite3_bind_blob(pStmt, idx, bytes, (int)[data length], SQLITE_STATIC);
87     }
88     else {
89         sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC);
90     }
91 }

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

  2.  SQL插入和查询

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

  • SQL插入数据

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

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

 

NSObject+LHDB.m

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

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

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

LHModelStateMent.m

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

 

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

NSObject+LHModel.m

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

注意的地方就是,当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>0) {
            NSNumber* number = getBaseTypePropertyValue(object, objectInfo.baseTypeEcoding, objectInfo.get);
            [dic setValue:number forKey:dicKey];
        }
    }
}
 1 static NSNumber* getBaseTypePropertyValue(__unsafe_unretained NSObject* object, NSUInteger type,SEL get)
 2 {
 3     switch (type) {
 4         case LHBaseTypeEcodingINT:
 5             
 6             return @(((int (*)(id, SEL))(void *) objc_msgSend)(object, get));
 7             
 8         case LHBaseTypeEcodingLONG:
 9             
10             return @(((long (*)(id, SEL))(void *) objc_msgSend)(object,get));
11             
12         case LHBaseTypeEcodingULONG:
13             
14             return @(((NSUInteger(*)(id,SEL))(void*) objc_msgSend)(object,get));
15             
16         case LHBaseTypeEcodingFLOAT:
17             
18             return @(((float(*)(id,SEL))(void*) objc_msgSend)(object,get));
19 
20         case LHBaseTypeEcodingDOUBLE:
21             
22             return @(((double(*)(id,SEL))(void*) objc_msgSend)(object,get));
23 
24         case LHBaseTypeEcodingBOOL:
25             
26             return @(((BOOL(*)(id,SEL))(void*) objc_msgSend)(object,get));
27             
28         case LHBaseTypeEcodingCHAR:
29             
30            return @(((char(*)(id,SEL))(void*) objc_msgSend)(object,get));
31 
32         default:
33             return nil;
34             break;
35     }
36 }

 

LHObjectInfo.h

 1 //对象类信息
 2 @interface LHClassInfo : NSObject
 3 
 4 @property (nonatomic)Class cls;
 5 
 6 @property (nonatomic)Class superClass;
 7 
 8 @property (nonatomic)Class metaClass;
 9 
10 @property (nonatomic,assign) BOOL isMetaClass;
11 
12 @property (nonatomic,strong) NSMutableDictionary* objectInfoDic;
13 
14 - (instancetype)initWithClass:(Class)cls;
15 
16 + (BOOL)isCacheWithClass:(Class)cls;
17 
18 + (LHClassInfo*)classInfoWithClass:(Class)cls;
19 
20 - (LHObjectInfo*)objectInfoWithName:(NSString*)name;
21 
22 @end

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

 1 typedef NS_ENUM(NSUInteger,LHBaseTypeEcoding) {
 2     LHBaseTypeEcodingUnknow,
 3     LHBaseTypeEcodingINT,
 4     LHBaseTypeEcodingLONG,
 5     LHBaseTypeEcodingULONG,
 6     LHBaseTypeEcodingCHAR,
 7     LHBaseTypeEcodingFLOAT,
 8     LHBaseTypeEcodingBOOL,
 9     LHBaseTypeEcodingDOUBLE
10 };
11 
12 typedef NS_ENUM(NSUInteger,LHNSTypeEcoding) {
13     LHNSTypeUNknow,
14     LHNSTypeNSString,
15     LHNSTypeNSNumber,
16     LHNSTypeNSDate,
17     LHNSTypeNSData,
18     LHNSTypeNSURL,
19     LHNSTypeNSArray,
20     LHNSTypeNSDictionary,
21     LHNSTypeUIImage
22 };
23 
24 
25 //描述对象属性的结构
26 @interface LHObjectInfo : NSObject
27 
28 @property (nonatomic) Class cls;    //当属性是OC对象时,cls记录属性对象所属类,否则为基础类型时,值为nil
29 
30 @property (nonatomic) objc_property_t property_t;   //属性
31 
32 @property (nonatomic,copy) NSString* name;  //属性名
33 
34 @property (nonatomic,assign) LHBaseTypeEcoding baseTypeEcoding;  //自定义基础数据类型编码
35 
36 @property (nonatomic,assign) LHNSTypeEcoding nsTypeEcoding; //自定义OC对象类型编码
37 
38 @property (nonatomic) SEL set;  //属性的setter方法
39 
40 @property (nonatomic) SEL get;  //属性的getter方法
41 
42 @property (nonatomic,copy) NSString* type; //对象类型,如:NSString,i,Q,d等等
43 
44 - (instancetype)initWithProperty:(objc_property_t)property;
45 
46 @end

LHObjectInfo.m

LHClassInfo类实现

 1 - (instancetype)initWithClass:(Class)cls
 2 {
 3     self = [super init];
 4     if (self) {
 5         _cls = cls;
 6         static dispatch_once_t onceToken;
 7         dispatch_once(&onceToken, ^{
 8             objectInfoCacheDic = [NSMutableDictionary dictionary];
 9         });
10         _objectInfoDic = [NSMutableDictionary dictionary];
11         
12         //遍历属性,为每个属性生成 LHObjectInfo 对象 并保存在类字典objectInfoDic中,注意是类
13         unsigned int count;
14         objc_property_t* t = class_copyPropertyList(cls, &count);
15         for (int i=0; i<count; i++) {
16             
17             LHObjectInfo* info = [[LHObjectInfo alloc] initWithProperty:t[i]];
18             [_objectInfoDic setValue:info forKey:[NSString stringWithUTF8String:property_getName(t[i])]];
19         }
20         free(t);
21         
22         //记录类名对应的类信息,并保存在静态全局缓存字典objectInfoCacheDic中
23         [objectInfoCacheDic setValue:self forKey:NSStringFromClass(cls)];
24     }
25     return self;
26 }
27 
28 + (BOOL)isCacheWithClass:(Class)cls
29 {
30     if ([objectInfoCacheDic objectForKey:NSStringFromClass(cls)]) {
31         return YES;
32     }
33     return NO;
34 }
35 
36 + (LHClassInfo*)classInfoWithClass:(Class)cls
37 {
38     return objectInfoCacheDic[NSStringFromClass(cls)];
39 }

LHObjectInfo类实现

 1 - (instancetype)initWithProperty:(objc_property_t)property
 2 {
 3     if (property == nil) return nil;
 4     self = [super init];
 5     if (self) {
 6         _property_t = property;
 7         _name = [NSString stringWithUTF8String:property_getName(property)]; //记录属性名
 8         
 9         unsigned int count;
10         objc_property_attribute_t* t = property_copyAttributeList(property, &count);
11         //for (unsigned int i=0; i<count; i++) {
12         if(count > 0){
13             //源代码是一个循环,其实这里只是获取第一个属性值,也就是T,即是属性的类型,所以我这里改成了一个判断语句,将t[i] 改成 t[0]
14             objc_property_attribute_t p = t[0];
15             size_t len = strlen(p.value);  //假设属性是NSString,则p.name = "T",p.value = "@\"NString\"";
16             if (len > 3) {
17                 char name[len - 2];
18                 name[len - 3] = '\0';
19                 memcpy(name, p.value + 2, len - 3);
20                 _cls = objc_getClass(name); //记录类
21                 _type = [NSString stringWithUTF8String:name];   //记录对象类型
22                 _nsTypeEcoding = nsTypeEcoding(_type);  //记录oc对象编码,目前只支持NSString,NSNumber,NSDate,NSData,NSURL,NSArray,NSDictionary,UIImage
23                 //break;
24             }else {
25                 //基础数据类型
26                 _type = [NSString stringWithUTF8String:p.value];
27                 if (_type.length>1) {
28                     _type = [_type substringToIndex:1];
29                 }
30                 if (_type.length>0) {
31                     _baseTypeEcoding = baseTypeEcoding([_type characterAtIndex:0]);
32                 }
33                 //break;
34             }
35         }
36         free(t);
37         
38         if (_name.length>0) {
39             _set = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",[[_name substringToIndex:1] uppercaseString],[_name substringFromIndex:1]]);
40             _get = NSSelectorFromString(_name);
41         }
42     }
43     return self;
44 }

 

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

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

 

  • SQL查询数据

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

  同样,从外部调用开始:

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

 

NSObject+LHDB.m

 1 //查询记录不需要对象方法
 2 + (NSArray*)selectWithPredicate:(LHPredicate*)predicate
 3 {
 4     LHSqlite* sqlite = [LHSqlite shareInstance];
 5     sqlite.sqlPath = [self dbPath];
 6     NSArray* array = [sqlite executeQueryWithSqlstring:selectString(self, predicate)];
 7     NSMutableArray* resultArray = [NSMutableArray array];
 8     for (NSDictionary* dic in array) {
 9         [resultArray addObject:[self lh_ModelWithDictionary:dic]];
10     }
11     return resultArray;
12 }

 

LHModelStateMent.m

 1 NSString* selectString(Class modelClass,LHPredicate* predicate)
 2 {
 3     NSMutableString* selectStr = [NSMutableString stringWithString:SELECT_HEADER];
 4     [selectStr appendString:NSStringFromClass(modelClass)];
 5     if (predicate.predicateFormat) {
 6         [selectStr appendFormat:@" WHERE %@",predicate.predicateFormat];
 7     }
 8     if (predicate.sortString) {
 9         [selectStr appendFormat:@" ORDER BY %@",predicate.sortString];
10     }
11     return selectStr;
12 }

 

LHSqlite.m

 1 - (NSArray*)executeQueryWithSqlstring:(NSString*)sqlString
 2 {
 3     Lock;
 4     NSArray* resultArray = 0x00;
 5     if ([self openDB]) {
 6         sqlite3_stmt* stmt = [self stmtWithCacheKey:sqlString];
 7         if (stmt) {
 8             NSMutableArray* dataSource = [NSMutableArray array];
 9             int count = sqlite3_column_count(stmt);
10             while (sqlite3_step(stmt) == SQLITE_ROW) {
11                 NSMutableDictionary* dataDic = [NSMutableDictionary dictionary];
12                 for (int i=0; i<count; i++) {
13                     int type = sqlite3_column_type(stmt, i);
14                     NSString* propertyName = [NSString stringWithUTF8String:sqlite3_column_name(stmt, i)];
15                     NSObject* value = dataWithDataType(type, stmt, i);
16                     [dataDic setValue:value forKey:propertyName];
17                 }
18                 [dataSource addObject:dataDic];
19             }
20             resultArray = dataSource;
21         }
22     }
23     sqlite3_close(_db);
24     UnLock;
25     return resultArray;
26 }

 

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

LHSqlite.m

 1 static NSObject* dataWithDataType(int type,sqlite3_stmt * statement,int index)
 2 {
 3     if (type == SQLITE_INTEGER) {
 4         int value = sqlite3_column_int(statement, index);
 5         return [NSNumber numberWithInt:value];
 6     }else if (type == SQLITE_FLOAT) {
 7         float value = sqlite3_column_double(statement, index);
 8         return [NSNumber numberWithFloat:value];
 9     }else if (type == SQLITE_BLOB) {
10         const void *value = sqlite3_column_blob(statement, index);
11         int bytes = sqlite3_column_bytes(statement, index);
12         return [NSData dataWithBytes:value length:bytes];
13     }else if (type == SQLITE_NULL) {
14         return nil;
15     }else if (type == SQLITE_TEXT) {
16         return [NSString stringWithUTF8String:(char*)sqlite3_column_text(statement, index)];
17     }else {
18         return nil;
19     }
20 }

 

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

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

 

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

NSObject+LHModel.m

 1 static void ModelSetValueToProperty(const void *key, const void *value, void *context)
 2 {
 3     ModelSetContext* modelContext = context;
 4     NSString* dicKey = (__bridge NSString *)(key);
 5     id dicValue = (__bridge id)(value);
 6     LHObjectInfo* objectInfo = [((__bridge LHClassInfo*)modelContext->classInfo) objectInfoWithName:dicKey]; //根据属性名获取属性信息结构
 7     NSObject* object = (__bridge NSObject*)modelContext->model;
 8     if (objectInfo) {
 9         if (objectInfo.cls) {
10             setNSTypePropertyValue(object, dicValue, objectInfo.nsTypeEcoding, objectInfo.set);
11             
12         }else if (objectInfo.type.length>0) {
13             NSNumber* number = numberWithValue(dicValue);
14             setBaseTypePropertyValue(object, number, objectInfo.baseTypeEcoding,objectInfo.set);
15         }
16     }
17 }
 1 static NSNumber* numberWithValue(__unsafe_unretained id value)
 2 {
 3     if (!value) {
 4         return nil;
 5     }
 6     if ([value isKindOfClass:[NSNumber class]]) return value;
 7     if ([value isKindOfClass:[NSString class]]) {
 8         if ([value containsString:@"."]) {
 9             
10             const char *cstring = ((NSString *)value).UTF8String;
11             if (!cstring) return nil;
12             double num = atof(cstring);
13             if (isnan(num) || isinf(num)) return nil; //判断浮点数是否是非数字或者无限大
14             return @(num);
15         }else {
16             const char *cstring = ((NSString*)value).UTF8String;
17             if (!cstring) return nil;
18             NSNumber* number = @(atoll(cstring));
19             if (!atoll(cstring)) {
20                 number = [NSNumber numberWithChar:*(cstring+0)];
21             }
22             return number;
23         }
24     }
25     return nil;
26     
27 }
 1 static void setBaseTypePropertyValue(__unsafe_unretained NSObject* object,__unsafe_unretained NSNumber* value, NSUInteger type,SEL set)
 2 {
 3     switch (type) {
 4         case LHBaseTypeEcodingINT:
 5             ((void (*)(id, SEL, int))(void *) objc_msgSend)(object, set, value.intValue);
 6             break;
 7         
 8         case LHBaseTypeEcodingLONG:
 9             ((void(*)(id,SEL,long))(void*) objc_msgSend)(object,set,value.integerValue);
10             break;
11         case LHBaseTypeEcodingULONG:
12             ((void(*)(id,SEL,long))(void*) objc_msgSend)(object,set,value.unsignedIntegerValue);
13             break;
14             
15         case LHBaseTypeEcodingFLOAT:
16             ((void(*)(id,SEL,float))(void*) objc_msgSend)(object,set,value.floatValue);
17             break;
18         case LHBaseTypeEcodingDOUBLE:
19             ((void(*)(id,SEL,double))(void*) objc_msgSend)(object,set,value.doubleValue);
20             break;
21         case LHBaseTypeEcodingBOOL:
22             ((void(*)(id,SEL,BOOL))(void*) objc_msgSend)(object,set,value.boolValue);
23             break;
24             
25         case LHBaseTypeEcodingCHAR:
26             ((void(*)(id,SEL,char))(void*) objc_msgSend)(object,set,value.charValue);
27             break;
28         default:
29             ((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,nil);
30             break;
31     }
32 }
33 
34 static void setNSTypePropertyValue(__unsafe_unretained id object,__unsafe_unretained id value,LHNSTypeEcoding typeEcoding,SEL set)
35 {
36     switch (typeEcoding) {
37         case LHNSTypeUNknow:
38             ((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);
39             break;
40         
41         case LHNSTypeNSString:
42             //将其它类型转成nsstring类型
43             if ([value isKindOfClass:[NSString class]]) {
44                 ((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);
45             }else if ([value isKindOfClass:[NSNumber class]]) {
46                 ((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,[value stringValue]);
47             }else if ([value isKindOfClass:[NSData class]]) {
48                 ((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,[[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding]);
49             }else if ([value isKindOfClass:[NSDate class]]) {
50                 ((void(*)(id,SEL,NSString*))(void*) objc_msgSend)(object,set,stringFormDate(value));
51             }else
52                 ((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);
53             break;
54             
55         case LHNSTypeNSNumber:
56             ((void(*)(id,SEL,NSNumber*))(void*) objc_msgSend)(object,set,numberWithValue(value));
57             break;
58             
59         case LHNSTypeNSDate:
60             if ([value isKindOfClass:[NSDate class]]) {
61                 ((void(*)(id,SEL,NSDate*))(void*) objc_msgSend)(object,set,value);
62             }else if ([value isKindOfClass:[NSString class]]) {
63                 ((void(*)(id,SEL,NSDate*))(void*) objc_msgSend)(object,set,dateFromString(value));
64             }else if ([value isKindOfClass:[NSData class]]) {
65                 NSString* dateStr = [[NSString alloc] initWithData:value encoding:NSUTF8StringEncoding];
66                 ((void(*)(id,SEL,NSDate*))(void*) objc_msgSend)(object,set,dateFromString(dateStr));
67             }else
68                 ((void(*)(id,SEL,id))(void*) objc_msgSend)(object,set,value);
69             break;
70             
71         case LHNSTypeNSData:
72             ((void(*)(id,SEL,NSData*))(void*) objc_msgSend)(object,set,dataFromObject(value));
73             break;
74             
75         case LHNSTypeNSURL:
76             ((void(*)(id,SEL,NSURL*))(void*) objc_msgSend)(object,set,urlFromObject(value));
77             break;
78             
79         case LHNSTypeNSArray:
80             ((void(*)(id,SEL,NSArray*))(void*) objc_msgSend)(object,set,arrayFromObject(value));
81             break;
82             
83         case LHNSTypeNSDictionary:
84             ((void(*)(id,SEL,NSDictionary*))(void*) objc_msgSend)(object,set,dicFromObject(value));
85             break;
86             
87         case LHNSTypeUIImage:
88             ((void(*)(id,SEL,UIImage*))(void*) objc_msgSend)(object,set,imageFromObject(value));
89             break;
90         
91         default:
92             break;
93     }
94 }

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

 

总结


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

 

 

 

 

 

 

posted @ 2017-07-25 14:03  Ginvar  阅读(1287)  评论(0编辑  收藏  举报