移動互聯網下iOS客戶端的開發,一般都會與服務端進行通訊,也會使用到Sqlite數據庫來保存一些數據,按常規的搞法,一般都需要手動建表結構,寫實體類對象,然後寫插入、更新、查詢等語句來實現功能,因此想到是否有一種通用的辦法來進行一些代碼方面的減負工作。利用反射的機制,可以很方便的實現。
首先,我們進行了以下的約定:
sqlite的數據庫表名直接使用實體類的類名;
sqlite的數據字段使用實體類的屬性名稱;
sqlite的數據類型統一設為text(引起sqlite這種文本數據庫是動態類型的,存儲的本質都是文本)
實體類的類型統一都設為NSString
做以上的約定,只是減少使用過程中,由於類型的不同造成不必要的麻煩,如果要支持各種類型,需要編寫各種判斷代碼,進行格式的處理,有興趣的同學可以進一步研究。
先擴展了NSObject,名稱叫NSObject+Property,然後添加下面代碼。
利用反射取得NSObject的屬性,並存入到數組中
- (NSArray *)getPropertyList: (Class)clazz { u_int count; objc_property_t *properties = class_copyPropertyList(clazz, &count); NSMutableArray *propertyArray = [NSMutableArray arrayWithCapacity:count]; for (int i = 0; i < count ; i++) { const char* propertyName = property_getName(properties[i]); [propertyArray addObject: [NSString stringWithUTF8String: propertyName]]; } free(properties); return propertyArray; }
根據屬性生成創建Sqlite表的語句
- (NSString *)tableSql:(NSString *)tablename { NSMutableString *sql = [[NSMutableString alloc] init]; NSArray *array = [self getPropertyList]; [sql appendFormat:@"create table %@ (",tablename] ; NSInteger i = 0; for (NSString *key in array) { if (i>0) { [sql appendString:@","]; } [sql appendFormat:@"%@ text",key]; i++; } [sql appendString:@")"]; return sql; }
把一個實體對象,封裝成字典Dictionary
- (NSDictionary *)convertDictionary { NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; NSArray *propertyList = [self getPropertyList]; for (NSString *key in propertyList) { SEL selector = NSSelectorFromString(key); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" id value = [self performSelector:selector]; #pragma clang diagnostic pop if (value == nil) { value = [NSNull null]; } [dict setObject:value forKey:key]; } return dict; } 從一個字典中還原成一個實體對象 - (void)dictionaryForObject:(NSDictionary*) dict { for (NSString *key in [dict allKeys]) { id value = [dict objectForKey:key]; if (value==[NSNull null]) { continue; } if ([value isKindOfClass:[NSDictionary class]]) { id subObj = [self valueForKey:key]; if (subObj) [subObj dictionaryForObject:value]; } else { [self setValue:value forKeyPath:key]; } } }
返回一個對象的類型名稱
- (NSString *)className { return [NSString stringWithUTF8String:object_getClassName(self)]; }
以上是對NSObject的一個擴展,使用了Obj-C的Category特性
以下是與數據存儲相關,定義為DbHelper,對sqlite進行操作
把id類型的數據對象插入到數據
-(void)insertObject:(id)object { NSString *tablename = [object className]; NSMutableString *sql = [[NSMutableString alloc] init]; NSArray *array = [object getPropertyList]; [sql appendFormat:@"insert into %@ (",tablename] ; NSInteger i = 0; for (NSString *key in array) { if (i>0) { [sql appendString:@","]; } [sql appendFormat:@"%@",key]; i++; } [sql appendString:@") values ("]; NSMutableArray *arrayValue = [NSMutableArray array]; i=0; for (NSString *key in array) { SEL selector = NSSelectorFromString(key); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" id value = [object performSelector:selector]; #pragma clang diagnostic pop if (value==nil) { value = @""; } [arrayValue addObject:value]; if (i>0) { [sql appendString:@","]; } [sql appendString:@"?"]; i++; } [sql appendString:@")"]; [_db executeUpdate:sql withArgumentsInArray:arrayValue]; }
把字典NSDictionary對象插入到數據庫中
在與服務器進行交互時候,我們一般采用Json進行數據通訊,從服務端獲取Json字符,通過JSONKit框架,反序列化成NSDictionary對象,然後插入到數據庫
生成插入的sql語句
-(NSString *)createInsertSqlByDictionary:(NSDictionary *)dict tablename:(NSString *)table { NSMutableString *sql = [[NSMutableString alloc] init]; [sql appendFormat:@"insert into %@ (",table] ; NSInteger i = 0; for (NSString *key in dict.allKeys) { if (i>0) { [sql appendString:@","]; } [sql appendFormat:@"%@",key]; i++; } [sql appendString:@") values ("]; i = 0; for (NSString *key in dict.allKeys) { if (i>0) { [sql appendString:@","]; } [sql appendFormat:@":%@",key]; i++; } [sql appendString:@")"]; return sql; }
把字典插入到數據庫中
-(void)insertBySql:(NSString *)sql dict:(NSDictionary *)dict { if (sql && sql.length>0) { [_dbQueue inDatabase:^(FMDatabase *db) { [db executeUpdate:sql withParameterDictionary:dict]; }]; } }
取數據
從數據庫取數據,封裝成成字典,然後放入到數組中
-(NSArray *)queryDbToDictionaryArray:(NSString *)tablename sql:(NSString *)sql { FMResultSet *resultSet=[_db executeQuery:sql]; NSArray *columnArray = [self fMSetColumnArray:resultSet]; NSMutableArray *syncArray = [[NSMutableArray alloc] init]; NSString *columnName = nil; while ([resultSet next]) { NSMutableDictionary *syncData = [[NSMutableDictionary alloc] init]; for(int i =0;i<columnArray.count;i++) { columnName = [columnArray objectAtIndex:i]; NSString *columnValue = [resultSet stringForColumn: columnName]; if (columnValue==nil) { columnValue=@""; } [syncData setObject:columnValue forKey:columnName]; } [syncArray addObject:syncData]; } if ([syncArray count]==0) { return nil; } return syncArray; }
從數據庫中取數據,封裝成對象,然後放入數組中
-(NSArray *)queryDbToObjectArray:(Class )clazz sql:(NSString *)sql { FMResultSet *resultSet=[_db executeQuery:sql]; NSArray *columnArray = [self fMSetColumnArray:resultSet]; NSMutableArray *syncArray = [[NSMutableArray alloc] init]; NSString *columnName = nil; while ([resultSet next]) { NSObject *obj = [[clazz alloc] init]; if (obj==nil) { continue; } for(int i =0;i<columnArray.count;i++) { columnName = [columnArray objectAtIndex:i]; NSString *columnValue = [resultSet stringForColumn: columnName]; SEL selector = NSSelectorFromString(columnName); if ([obj respondsToSelector:selector]) { [obj setValue:columnValue forKeyPath:columnName ]; } } [syncArray addObject:obj]; } if ([syncArray count]==0) { return nil; } return syncArray; }
在數據量很大時候,考慮到性能問題,此方法需要酌情使用。
有了這幾個東西,進行開發就省去了很多時間和代碼量,直接動態生成表,從服務端接口取到數據,直接插入到數據庫中保存,顯示數據時,從數據庫中取出數據放入到對象數組中。