前言
最近在 GitHub 上看了一份关于基于 runtime 封装的对象存储型数据库的开源代码,觉得非常值得分享记录一下,在 IOS 中对数据库的操作一般通过 CoreData 和 SQLite,CoreData 虽然能够将 OC 对象转化成数据,保存在 SQLite 数据库文件中,也能够将保存在数据库中的数据还原成 OC 对象,期间不需要编写 SQL 语句,但使用起来并不是那么方便,而 SQLite 则需要用户编写相应的数据库语句,看起来不是很美观,所以大家一般都会将其进行封装,让其使用起来更加方便,而 LHDB 就是建立在 SQLite 之上的封装。现在,我们来看其是如何实现的,不过在此之前,我先假设大家对 OC 的 runtime 机制和 sqlite 有一定的理解。附上源码下载地址:github 链接
实现
所谓的基于对象存储的数据库,顾名思义,就是一切对数据的操作都是对对象模型的操作,通过给对象模型属性赋值,然后将对象交由底层去解析,转化成相应的 SQL 语句,然后执行数据库操作,我们先看看整体的一个 LHDB 目录结构(这里仅写出头文件):
LHDB
LHModel
下面我将从我们正常使用数据库的流程去解析 LHDB 对数据库的管理
先声明了一个模型类 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,
- 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
- 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 > 0) { [valueStr deleteCharactersInRange: NSMakeRange(valueStr.length - 1, 1)];
- } [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 = 0;
- objc_property_t * property_t = class_copyPropertyList(self, &count);
- for (int i = 0; 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 = 0; i) { [self bindObject: parameter[parameter.allKeys[i]] toColumn: i + 1 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, -1, &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], -1, SQLITE_STATIC);
- else sqlite3_bind_text(pStmt, idx, [[self stringFromDate: obj] UTF8String], -1, SQLITE_STATIC);
- } else if ([obj isKindOfClass: [NSNumber class]]) {
- if (strcmp([obj objCType], @encode(char)) == 0) {
- sqlite3_bind_int(pStmt, idx, [obj charValue]);
- } else if (strcmp([obj objCType], @encode(unsigned char)) == 0) {
- sqlite3_bind_int(pStmt, idx, [obj unsignedCharValue]);
- } else if (strcmp([obj objCType], @encode(short)) == 0) {
- sqlite3_bind_int(pStmt, idx, [obj shortValue]);
- } else if (strcmp([obj objCType], @encode(unsigned short)) == 0) {
- sqlite3_bind_int(pStmt, idx, [obj unsignedShortValue]);
- } else if (strcmp([obj objCType], @encode(int)) == 0) {
- sqlite3_bind_int(pStmt, idx, [obj intValue]);
- } else if (strcmp([obj objCType], @encode(unsigned int)) == 0) {
- sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedIntValue]);
- } else if (strcmp([obj objCType], @encode(long)) == 0) {
- sqlite3_bind_int64(pStmt, idx, [obj longValue]);
- } else if (strcmp([obj objCType], @encode(unsigned long)) == 0) {
- sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongValue]);
- } else if (strcmp([obj objCType], @encode(long long)) == 0) {
- sqlite3_bind_int64(pStmt, idx, [obj longLongValue]);
- } else if (strcmp([obj objCType], @encode(unsigned long long)) == 0) {
- sqlite3_bind_int64(pStmt, idx, (long long)[obj unsignedLongLongValue]);
- } else if (strcmp([obj objCType], @encode(float)) == 0) {
- sqlite3_bind_double(pStmt, idx, [obj floatValue]);
- } else if (strcmp([obj objCType], @encode(double)) == 0) {
- NSLog(@"%f", [obj doubleValue]);
- sqlite3_bind_double(pStmt, idx, [obj doubleValue]);
- } else if (strcmp([obj objCType], @encode(BOOL)) == 0) {
- sqlite3_bind_int(pStmt, idx, ([obj boolValue] ? 1 : 0));
- } else {
- sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, 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], -1, 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], -1, SQLITE_STATIC);
- }
- }
至此,我们的数据库创建和表的创建已经完成了,如果上面的过程你都能清楚了,那么后面数据库的内容,你会觉得还是比较轻松的,因为都差不多 ^-^。
2. SQL 插入和查询
对 SQL 插入数据记录,可以看到一个数据记录是怎么从 Model 一步步变成一条 SQL 语句,而数据的查询,则可以看到 SQL 查询的数据怎么映射到一个 Model 中,其它的数据库操作,我觉得类同,不多做阐述。
首先,我们假设将插入一条 Teacher 信息记录,代码如下:
- //直接将model插入数据库
- Teacher * teacher = [[Teacher alloc] init];
- teacher.name = @"tom";
- teacher.age = 18;
- 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 = 0; i) {
- NSDictionary * dic = insertValueString(valueDic.allKeys[i]); [keyStr appendFormat: @"%@,", dic.allKeys[0]]; [valueStr appendFormat: @"%@,", dic[dic.allKeys[0]]];
- } [sqlString appendFormat: @"(%@) VALUES (%@)", [keyStr substringToIndex: keyStr.length - 1], [valueStr substringToIndex: valueStr.length - 1]];
- //这里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 = {
- 0
- };
- 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 > 0) {
- 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 = 0; 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 > 0) {
- //源代码是一个循环,其实这里只是获取第一个属性值,也就是T,即是属性的类型,所以我这里改成了一个判断语句,将t[i] 改成 t[0]
- objc_property_attribute_t p = t[0];
- size_t len = strlen(p.value); //假设属性是NSString,则p.name = "T",p.value = "@\"NString\"";
- if (len > 3) {
- char name[len - 2];
- name[len - 3] = '\0';
- memcpy(name, p.value + 2, len - 3);
- _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 > 1) {
- _type = [_type substringToIndex: 1];
- }
- if (_type.length > 0) {
- _baseTypeEcoding = baseTypeEcoding([_type characterAtIndex: 0]);
- }
- //break;
- }
- }
- free(t);
- if (_name.length > 0) {
- _set = NSSelectorFromString([NSString stringWithFormat: @"set%@%@:", [[_name substringToIndex: 1] uppercaseString], [_name substringFromIndex: 1]]);
- _get = NSSelectorFromString(_name);
- }
- }
- return self;
- }
LHClassInfo 和 LHObjectInfo 分别保存了类的相关信息和属性信息,在 LHObjectInfo 的 initWithProperty 方法中,大家也看到最后属性的 setter 是由 "set + 属性名 (首字母大写)" 得到的,getter 也是和属性名一致的,所以大家在定义模型的时候不要去自己自定义自己的属性 setter 和 getter 方法,这会导致可能不可预知的数据错误,同时,不应该去声明变量,应该总是使用属性,因为我们在转换的时候都是使用了模型的属性列表来做转换。
好了,至此,我们又一次完成了对数据记录插入的分析 ^-^。
数据的查询,其实就是根据条件,在相应的表中查询出满足条件的数据集合。
同样,从外部调用开始:
- //查询数据
- 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 = 0; 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 = {
- 0
- };
- 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 > 0) {
- 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 + 0)];
- }
- 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,必然要应对这样复杂的使用环境,此外,在阅读源码的过程中,我也自己改动了一些方法实现,可能我觉得有些实现还不够精简,不过它作为学习拓展,我觉得还是一个比较不错的开源代码的,就这样了 ^-^。
来源: http://www.cnblogs.com/ginvar/p/7226464.html