原創文章轉載請注明出處
今天一個朋友讓我完善一下簡歷,把以前做過的項目內容寫詳細一些,我說好吧。翻到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 <Foundation/Foundation.h>
@interface PersistenceObject : NSObject
{
NSInteger _id;
}
@property (nonatomic, assign) NSInteger _id;
+ (NSArray *)transients;
- (NSInteger)save;
@end
PersistenceHelper提供了一些庫表操作的工具函數,比如建表,數據映射等。
#import <Foundation/Foundation.h>
#import <sqlite3.h>
@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端,找時間再寫吧,注解+反射拿到現在來看都不是什么高深的技術。
我是咕咕雞,一個還在不停學習的全棧工程師。
熱愛生活,喜歡跑步,家庭是我不斷向前進步的動力。