zoukankan      html  css  js  c++  java
  • 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] = '';
    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,必然要应对这样复杂的使用环境,此外,在阅读源码的过程中,我也自己改动了一些方法实现,可能我觉得有些实现还不够精简,不过它作为学习拓展,我觉得还是一个比较不错的开源代码的,就这样了^-^。

  • 相关阅读:
    【转载】 KL距离(相对熵)
    HTML5动画软件工具编辑器 HTML5动画分类 工具推荐
    Flash Actionscript AS3 渐变透明 mask遮罩
    HTML动画分类 HTML5动画 SVG库 SVG工具 Canvas动画工具
    Flash:彻底理解crossdomain.xml、跨swf调用。
    Flash: Event.PASTE Flash获取剪贴板内容 触发paste事件 how to get paste event
    Flash:TextField字体不显示/文字不显示/文字丢失
    Flash 矢量图和位图性能对比 导出为位图/缓存为位图 export as bitmap / cache as bitmap
    验证码去噪 分离背景 分离文字 最大类间方差
    Flash XSS 漏洞详解 根治的好办法
  • 原文地址:https://www.cnblogs.com/ginvar/p/7226464.html
Copyright © 2011-2022 走看看