原创文章转载请注明出处
今天一个朋友让我完善一下简历,把以前做过的项目内容写详细一些,我说好吧。翻到12年的一个产品,那是第一次写iOS App,当时还在iOS 4.3时代,网上的资料少得可怜。在评估数据库方案的时候,先把在当初还不是很成熟的Core Data给排除了,那就只剩SQLite可选。当时最火的SQLite框架就是FMDB了,但是看着FMDB那又长又臭的CreateTable方法,还有臃肿的包,哥几个决定自己动手改写一个Persistence Manager。
讲真,一直到现在我也没有用过FMDB,自己写的东西已经足够应付开发需求了性能也不差,后来为了跨平台又把数据库改成了Realm。
言归正传,我们的Persistence Manager其实非常简单,只有4个类。
RWLock是为了数据同步实现的读写锁。
PersistenceObject是数据库元数据的基类,提供了简单的save和ignore方法。
#import
@interface PersistenceObject : NSObject
{
NSInteger _id;
}
@property (nonatomic, assign) NSInteger _id;
+ (NSArray *)transients;
- (NSInteger)save;
@end
PersistenceHelper提供了一些库表操作的工具函数,比如建表,数据映射等。
#import
#import
@class PersistenceObject;
@interface PersistenceHelper : NSObject
+ (NSString*) tableName:(Class)class;
+ (NSString*) columnName:(NSString*)propertyName;
+ (NSString*) propertyName:(NSString*)columnName;
+ (NSDictionary*)fields:(Class)class;
+ (NSString*) genDDL:(Class)class;
+ (void)mappingToStatement:(PersistenceObject*)object statement:(sqlite3_stmt*)statement;
+ (void)mappingToObject:(sqlite3_stmt *)statement object:(PersistenceObject *)object;
@end
PersistenceManager封装了一些SQLite的接口
@class PersistenceObject;
@interface PersistenceManager : NSObject
{
sqlite3 *database;
NSMutableArray *tables;
int _transactionLevel;
}
+ (PersistenceManager*)sharedManager;
- (void)databaseNotMatch;
- (BOOL)execSQL:(NSString*)sql;
- (void)startTransaction: (NSString *) tag;
- (void)commitTransaction: (NSString *) tag;
- (void)rollbackTransaction: (NSString *) tag;
- (NSInteger)insert:(PersistenceObject*)object;
- (NSInteger)update:(PersistenceObject*)object;
- (NSInteger)deleteObject:(PersistenceObject *)object;
- (NSArray*)execQuery:(NSString*)sql;
- (NSArray*)execQuery:(Class)class sql:(NSString *)sql;
- (NSArray*)execQuery:(Class)class selection:(NSString*)selection selectionArgs:(NSArray*)selectionArgs groupBy:(NSString*)groupBy orderBy:(NSString*)orderBy limit:(NSInteger)limit;
- (NSArray*)execQuery:(Class)class selection:(NSString*)selection selectionArgs:(NSArray*)selectionArgs groupBy:(NSString*)groupBy orderBy:(NSString*)orderBy limit:(NSInteger)limit skip: (NSInteger) skip;
- (NSInteger) queryCount:(Class) class withWhereClause:(NSString*) whereClause;
- (void) execDropTableSql:(Class) class;
@end
今天要讲的就是PersistenceHelper的mappingToStatement和mappingToObject方法,介绍如何将sqlite3_stmt和PersistenceObject进行映射。
做过Java的都知道反射的强大,iOS的run time提供了一套获取class属性的接口。
因此我们可以通过runtime的方法将PersistenceObject的属性列表取出来,保存为一个NSDictionary,key是propName,Value是数据类型。
+ (NSDictionary *)fields:(Class)class
{
// Recurse up the classes, but stop at NSObject. Each class only reports its own properties, not those inherited from its superclass
NSMutableDictionary *theProps;
if ([class superclass] != [NSObject class])
theProps = (NSMutableDictionary *)[self fields:[class superclass]];
else
theProps = [NSMutableDictionary dictionary];
unsigned int outCount;
objc_property_t *propList = class_copyPropertyList(class, &outCount);
// Loop through properties and add declarations for the create
for (int i=0; i < outCount; i++)
{
objc_property_t oneProp = propList[i];
NSString *propName = [NSString stringWithUTF8String:property_getName(oneProp)];
NSString *attrs = [NSString stringWithUTF8String:property_getAttributes(oneProp)];
// Read only attributes are assumed to be derived or calculated
if ([attrs rangeOfString:@",R,"].location == NSNotFound)
{
NSArray *attrParts = [attrs componentsSeparatedByString:@","];
if (attrParts != nil)
{
if ([attrParts count] > 0)
{
NSString *propType = [[attrParts objectAtIndex:0] substringFromIndex:1];
[theProps setObject:propType forKey:propName];
}
}
}
}
free(propList);
return theProps;
}
因为iOS的数据类型是有限的,我们可以将其和SQLite的数据类型一一对应。接下来就可以根据属性名和属性类型来进行映射了,当然也可以实现自动创建SQLite的Table,这里就不贴出太多的代码。
+ (void)mappingToStatement:(PersistenceObject *)object statement:(sqlite3_stmt *)statement
{
NSDictionary *props = [PersistenceHelper fields:[object class]];
NSArray *transients = [[object class] transients];
int index = 1;
for (NSString *propName in props)
{
if ([transients containsObject:propName]) continue;
NSString *propType = [props objectForKey:propName];
if (![propName isEqualToString:@"_id"]) {
id value = [object valueForKey:propName];
if (!value || [value isKindOfClass:[NSNull class]])
{
sqlite3_bind_null(statement, index++);
}
else if ([propType isEqualToString:@"i"] || // int
[propType isEqualToString:@"I"] || // unsigned int
[propType isEqualToString:@"l"] || // long
[propType isEqualToString:@"L"] || // usigned long
[propType isEqualToString:@"q"] || // long long
[propType isEqualToString:@"Q"] || // unsigned long long
[propType isEqualToString:@"s"] || // short
[propType isEqualToString:@"S"] || // unsigned short
[propType isEqualToString:@"B"]) // bool
{
sqlite3_bind_int64(statement, index++, [value longLongValue]);
}
else if ([propType isEqualToString:@"f"] || // float
[propType isEqualToString:@"d"] ) // double
{
sqlite3_bind_double(statement, index++, [value doubleValue]);
}
else if ([propType isEqualToString:@"c"] || // char
[propType isEqualToString:@"C"] ) // unsigned char
{
sqlite3_bind_int(statement, index++, [value intValue]);
}
else if ([propType hasPrefix:@"@"] ) // Object
{
NSString *className = [propType substringWithRange:NSMakeRange(2, [propType length]-3)];
if([className isEqualToString:@"NSString"])
{
sqlite3_bind_text(statement, index++, [value UTF8String], -1, NULL);
}
else if([className isEqualToString:@"NSNumber"])
{
sqlite3_bind_double(statement, index++, [value doubleValue]);
}
else if([className isEqualToString:@"NSDate"])
{
sqlite3_bind_int64(statement, index++, [value timeIntervalSince1970]);
}
else if([className isEqualToString:@"NSData"])
{
sqlite3_bind_blob(statement, index++, [value bytes], [value length], NULL);
}
else
{
index++;
KLOGV(@"PersistenceHelper", @"Unknow Object Type: %@", className);
}
}
}
}
}
+ (void)mappingToObject:(sqlite3_stmt *)statement object:(PersistenceObject *)object
{
NSDictionary *theProps = [self fields:[object class]];
for (int i=0; i < sqlite3_column_count(statement); i++)
{
NSString *columnName = [NSString stringWithUTF8String:sqlite3_column_name(statement, i)];
NSString *propName = [self propertyName:columnName];
NSString *columnType = [theProps valueForKey:propName];
if (!columnType) {
break;
}
if ([columnType isEqualToString:@"i"] || // int
[columnType isEqualToString:@"l"] || // long
[columnType isEqualToString:@"q"] || // long long
[columnType isEqualToString:@"s"] || // short
[columnType isEqualToString:@"B"] || // bool or _Bool
[columnType isEqualToString:@"I"] || // unsigned int
[columnType isEqualToString:@"L"] || // usigned long
[columnType isEqualToString:@"Q"] || // unsigned long long
[columnType isEqualToString:@"S"]) // unsigned short
{
long long value = sqlite3_column_int64(statement, i);
NSNumber *colValue = [NSNumber numberWithLongLong:value];
[object setValue:colValue forKey:propName];
}
else if ([columnType isEqualToString:@"f"] || // float
[columnType isEqualToString:@"d"] ) // double
{
double value = sqlite3_column_double(statement, i);
NSNumber *colVal = [NSNumber numberWithDouble:value];
[object setValue:colVal forKey:propName];
}
else if ([columnType isEqualToString:@"c"] || // char
[columnType isEqualToString:@"C"] ) // unsigned char
{
NSInteger value = sqlite3_column_int(statement, i);
NSNumber *colValue = [NSNumber numberWithInt:value];
[object setValue:colValue forKey:propName];
}
else if ([columnType hasPrefix:@"@"] ) // Object
{
NSString *className = [columnType substringWithRange:NSMakeRange(2, [columnType length]-3)];
if([className isEqualToString:@"NSString"])
{
const char *colVal = (const char *)sqlite3_column_text(statement, i);
if (colVal != NULL)
{
NSString *colValString = [NSString stringWithUTF8String:colVal];
[object setValue:colValString forKey:propName];
}
}
else if([className isEqualToString:@"NSNumber"])
{
double value = sqlite3_column_double(statement, i);
NSNumber *colVal = [NSNumber numberWithDouble:value];
[object setValue:colVal forKey:propName];
}
else if([className isEqualToString:@"NSDate"])
{
long long value = sqlite3_column_int64(statement, i);
NSDate *colValue = [NSDate dateWithTimeIntervalSince1970:value];
[object setValue:colValue forKey:propName];
}
else if([className isEqualToString:@"NSData"])
{
const void* value = sqlite3_column_blob(statement, i);
if (value != NULL)
{
int length = sqlite3_column_bytes(statement, i);
NSData *colValue = [NSData dataWithBytes:value length:length];
[object setValue:colValue forKey:propName];
}
}
else
{
KLOGV(@"PersistenceHelper", @"Unknow Object Type: %@", className);
}
}
}
}
好多年前的代码了,估计现在要跑起来还要改一些代码。:)
至于Android端,找时间再写吧,注解+反射拿到现在来看都不是什么高深的技术。
我是咕咕鸡,一个还在不停学习的全栈工程师。
热爱生活,喜欢跑步,家庭是我不断向前进步的动力。