sqlite幾乎所有的App都會用到,但是系統自帶的sqlite API是用C語言寫的,非常不友好,用起來非常不便,通常我們使用第三方封裝好的工具,例如:FMDB(https://github.com/ccgus/fmdb)
FMDB的提供了一種更簡單,方便的API,並且還提供了線程安全的隊列FMDatabaseQueue用於數據庫的讀寫,關於FMDB的使用,參見github上的描述
在使用FMDB查詢表的時候的時候我們一般用下面方式
1、定義一個數據模型PersonModel
@interface PersonModel : NSObject @property (nonatomic, assign) NSInteger peopleId; @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *gender; @property (nonatomic, assign) float weight; @property (nonatomic, assign) double height; @property (nonatomic, assign) short age; @property (nonatomic, assign) long score; @property (nonatomic, strong) NSDate *createTime; @property (nonatomic, assign) BOOL married; @property (nonatomic, strong) NSData *desc; @end
2、插入數據
NSString *sql = @"insert into People(name, gender, weight, height, age, score, createTime, married, desc) values(?,?,?,?,?,?,?,?,?)"; NSString *text = @"dataValue"; NSData *data = [text dataUsingEncoding:NSUTF8StringEncoding]; NSArray *param = @[@"bomo", @"male", @70, @175l, @22, @123, [NSDate date], @NO, data]; [_queue inDatabase:^(FMDatabase *db) { [db executeUpdate:sql withArgumentsInArray:param]; }];
3、查詢
NSString *sql = @"select * from People"; __block NSMutableArray *people = [NSMutableArray array]; [_queue inDatabase:^(FMDatabase *db) { FMResultSet *rs = [db executeQuery:sql]; while ([rs next]) { person.name = [rs stringForColumn:@"name"]; person.gender = [rs stringForColumn:@"gender"]; person.height = [rs doubleForColumn:@"height"]; person.score = [rs longForColumn:@"score"]; person.createTime = [rs dateForColumn:@"createTime"]; person.married = [rs boolForColumn:@"married"]; person.desc = [rs dataForColumn:@"desc"]; //下面幾種方式讀取數據會導致數據類型不一致的問題 //person.peopleId = [rs intForColumn:@"peopleId"]; //person.age = [rs intForColumn:@"age"]; //person.weight = [rs doubleForColumn:@"weight"]; [people addObject:person]; } [rs close]; }];
這里的查詢方法需要對每一個屬性進行讀取和賦值,並且可能有數據類型不一致的問題,比如 讀取出來的int 賦值給NSInteger 類型,double類型賦值給float類型,下面我們對查詢方法進行改造,讓其變得更通用,可以自動映射查詢結果到Model,並提供數據庫列名到屬性名之間的映射
1、定義映射協議
#import <Foundation/Foundation.h> //實現數據庫列名到model屬性名的映射 @protocol ColumnPropertyMappingDelegate <NSObject> @required - (NSDictionary *)columnPropertyMapping; @end
2、修改PersonModel模型
#import <Foundation/Foundation.h> #import "ColumnPropertyMappingDelegate.h" @interface PersonModel : NSObject <ColumnPropertyMappingDelegate> @property (nonatomic, assign) NSInteger peopleId; @property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *gender; @property (nonatomic, assign) float weight; @property (nonatomic, assign) double height; @property (nonatomic, assign) short age; @property (nonatomic, assign) long score; @property (nonatomic, strong) NSDate *createTime; @property (nonatomic, assign) BOOL married; @property (nonatomic, strong) NSData *desc; @end @implementation PersonModel - (NSDictionary *)columnPropertyMapping { return @{@"id": @"peopleId", @"str1": @"name", @"str2": @"gender", @"float1": @"weight", @"double1": @"height", @"short1": @"age", @"long1": @"score", @"date1": @"createTime", @"bool1": @"married", @"data1": @"desc"}; } @end
數據庫的列名如果與Model的屬性名不一致,可以通過改映射函數進行配置
3、查詢函數,關鍵方法
/** * 執行查詢操作,自定構造models集合 * * @param sql sql語句 * @param args sql參數 * @param modelClass 結果集model類型 * @param block 對model執行自定義操作 * * @return 查詢結果集 */ - (NSArray *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)args modelClass:(Class)modelClass performBlock:(void (^)(id model, FMResultSet *rs))block { __block NSMutableArray *models = [NSMutableArray array]; [_queue inDatabase:^(FMDatabase *db) { NSDictionary *mapping = nil; FMResultSet *rs = [db executeQuery:sql withArgumentsInArray:args]; while ([rs next]) { id model = [[modelClass alloc] init]; if(!mapping && [model conformsToProtocol:@protocol(ColumnPropertyMappingDelegate)]) { //實現了列-屬性轉換協議 mapping = [model columnPropertyMapping]; } for (int i = 0; i < [rs columnCount]; i++) { //列名 NSString *columnName = [rs columnNameForIndex:i]; //進行數據庫列名到model之間的映射轉換,拿到屬性名 NSString *propertyName; if(mapping) { propertyName = mapping[columnName]; if (propertyName == nil) { //如果映射未定義,則視為相同 propertyName = columnName; } } else { propertyName = columnName; } objc_property_t objProperty = class_getProperty(modelClass, propertyName.UTF8String); //如果屬性不存在,則不操作 if (objProperty) { if(![rs columnIndexIsNull:i]) { [self setProperty:model value:rs columnName:columnName propertyName:propertyName property:objProperty]; } } NSAssert(![propertyName isEqualToString:@"description"], @"description為自帶方法,不能對description進行賦值,請使用其他屬性名或請ColumnPropertyMappingDelegate進行映射"); } //執行自定義操作 if (block) { block(model, rs); } [models addObject:model]; } [rs close]; }]; return models; } /** * 進行屬性賦值 */ - (void)setProperty:(id)model value:(FMResultSet *)rs columnName:(NSString *)columnName propertyName:(NSString *)propertyName property:(objc_property_t)property { // @"f":@"float", // @"i":@"int", // @"d":@"double", // @"l":@"long", // @"c":@"BOOL", // @"s":@"short", // @"q":@"long", // @"I":@"NSInteger", // @"Q":@"NSUInteger", // @"B":@"BOOL", NSString *firstType = [[[[NSString stringWithUTF8String:property_getAttributes(property)] componentsSeparatedByString:@","] firstObject] substringFromIndex:1]; if ([firstType isEqualToString:@"f"]) { NSNumber *number = [rs objectForColumnName:columnName]; [model setValue:@(number.floatValue) forKey:propertyName]; } else if([firstType isEqualToString:@"i"]){ NSNumber *number = [rs objectForColumnName:columnName]; [model setValue:@(number.intValue) forKey:propertyName]; } else if([firstType isEqualToString:@"d"]){ [model setValue:[rs objectForColumnName:columnName] forKey:propertyName]; } else if([firstType isEqualToString:@"l"] || [firstType isEqualToString:@"q"]){ [model setValue:[rs objectForColumnName:columnName] forKey:propertyName]; } else if([firstType isEqualToString:@"c"] || [firstType isEqualToString:@"B"]){ NSNumber *number = [rs objectForColumnName:columnName]; [model setValue:@(number.boolValue) forKey:propertyName]; } else if([firstType isEqualToString:@"s"]){ NSNumber *number = [rs objectForColumnName:columnName]; [model setValue:@(number.shortValue) forKey:propertyName]; } else if([firstType isEqualToString:@"I"]){ NSNumber *number = [rs objectForColumnName:columnName]; [model setValue:@(number.integerValue) forKey:propertyName]; } else if([firstType isEqualToString:@"Q"]){ NSNumber *number = [rs objectForColumnName:columnName]; [model setValue:@(number.unsignedIntegerValue) forKey:propertyName]; } else if([firstType isEqualToString:@"@\"NSData\""]){ NSData *value = [rs dataForColumn:columnName]; [model setValue:value forKey:propertyName]; } else if([firstType isEqualToString:@"@\"NSDate\""]){ NSDate *value = [rs dateForColumn:columnName]; [model setValue:value forKey:propertyName]; } else if([firstType isEqualToString:@"@\"NSString\""]){ NSString *value = [rs stringForColumn:columnName]; [model setValue:value forKey:propertyName]; } else { [model setValue:[rs objectForColumnName:columnName] forKey:propertyName]; } }
我們把數據庫的查詢和更新方法封裝成DbService
#import <Foundation/Foundation.h> @class FMResultSet; @interface DbService : NSObject - (instancetype)initWithPath:(NSString *)path; /** * 查詢第一行第一列的數據 */ - (id)executeScalar:(NSString *)sql param:(NSArray *)param; /** * 查詢行數 */ - (NSInteger)rowCount:(NSString *)tableName; /** * 更新數據 */ - (BOOL)executeUpdate:(NSString *)sql param:(NSArray *)param; #pragma mark - 查詢操作自動構建Model /** * 執行查詢操作,自定構造models集合 * * @param sql sql語句 * @param args sql參數 * @param modelClass 結果集model類型 * * @return 查詢結果集 */ - (NSArray *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)args modelClass:(Class)modelClass; /** * 執行查詢操作,自定構造models集合 * * @param sql sql語句 * @param args sql參數 * @param modelClass 結果集model類型 * @param block 對model執行自定義操作 * * @return 查詢結果集 */ - (NSArray *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)args modelClass:(Class)modelClass performBlock:(void (^)(id model, FMResultSet *rs))block; /** * 查詢結果集取得model集合 * * @param rs 數據庫查詢結果集 * @param modelClass 結果集model類型 * * @return 查詢結果集 */ - (NSArray *)resultForModels:(FMResultSet *)rs modelClass:(Class)modelClass; /** * 查詢結果集取得model集合 * * @param rs 數據庫查詢結果集 * @param modelClass 結果集model類型 * @param block 對model執行自定義操作 * * @return 查詢結果集 */ - (NSArray *)resultForModels:(FMResultSet *)rs modelClass:(Class)modelClass performBlock:(void (^)(id model, FMResultSet *rs))block; @end

#import "DbService.h" #import <objc/runtime.h> #import "FMDB.h" #import "ColumnPropertyMappingDelegate.h" #import "PersonModel.h" @interface DbService () { FMDatabaseQueue *_queue; } @end @implementation DbService - (instancetype)initWithPath:(NSString *)path { if (self = [super init]) { _queue = [FMDatabaseQueue databaseQueueWithPath:path]; } return self; } - (BOOL)executeUpdate:(NSString *)sql param:(NSArray *)param { __block BOOL result = NO; [_queue inDatabase:^(FMDatabase *db) { if (param && param.count > 0) { result = [db executeUpdate:sql withArgumentsInArray:param]; } else { result = [db executeUpdate:sql]; } }]; return result; } - (id)executeScalar:(NSString *)sql param:(NSArray *)param { __block id result; [_queue inDatabase:^(FMDatabase *db) { FMResultSet *rs = [db executeQuery:sql withArgumentsInArray:param]; if ([rs next]) { result = rs[0]; } else { result = 0; } }]; return result; } - (NSInteger)rowCount:(NSString *)tableName { NSNumber *number = (NSNumber *)[self executeScalar:[NSString stringWithFormat:@"SELECT COUNT(*) FROM %@", tableName] param:nil]; return [number longValue]; } #pragma mark - #pragma mark -- 自動創建model查詢方法 - (NSArray *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)args modelClass:(Class)modelClass { return [self executeQuery:sql withArgumentsInArray:args modelClass:modelClass performBlock:nil]; } - (NSArray *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)args modelClass:(Class)modelClass performBlock:(void (^)(id model, FMResultSet *rs))block { __block NSMutableArray *models = [NSMutableArray array]; [_queue inDatabase:^(FMDatabase *db) { NSDictionary *mapping = nil; FMResultSet *rs = [db executeQuery:sql withArgumentsInArray:args]; while ([rs next]) { id model = [[modelClass alloc] init]; if(!mapping && [model conformsToProtocol:@protocol(ColumnPropertyMappingDelegate)]) { //實現了列-屬性轉換協議 mapping = [model columnPropertyMapping]; } for (int i = 0; i < [rs columnCount]; i++) { //列名 NSString *columnName = [rs columnNameForIndex:i]; //進行數據庫列名到model之間的映射轉換,拿到屬性名 NSString *propertyName; if(mapping) { propertyName = mapping[columnName]; if (propertyName == nil) { //如果映射未定義,則視為相同 propertyName = columnName; } } else { propertyName = columnName; } objc_property_t objProperty = class_getProperty(modelClass, propertyName.UTF8String); //如果屬性不存在,則不操作 if (objProperty) { if(![rs columnIndexIsNull:i]) { [self setProperty:model value:rs columnName:columnName propertyName:propertyName property:objProperty]; } } NSAssert(![propertyName isEqualToString:@"description"], @"description為自帶方法,不能對description進行賦值,請使用其他屬性名或請ColumnPropertyMappingDelegate進行映射"); } //執行自定義操作 if (block) { block(model, rs); } [models addObject:model]; } [rs close]; }]; return models; } /** * 解析結果集(models) */ - (NSArray *)resultForModels:(FMResultSet *)rs modelClass:(Class)modelClass { return [self resultForModels:rs modelClass:modelClass performBlock:nil]; } - (NSArray *)resultForModels:(FMResultSet *)rs modelClass:(Class)modelClass performBlock:(void (^)(id model, FMResultSet *rs))block; { NSDictionary *mapping = nil; NSMutableArray *models = [NSMutableArray array]; while ([rs next]) { id model = [[modelClass alloc] init]; if(!mapping && [model conformsToProtocol:@protocol(ColumnPropertyMappingDelegate)]) { //實現了列-屬性轉換協議 mapping = [model columnPropertyMapping]; } for (int i = 0; i < [rs columnCount]; i++) { //列名 NSString *columnName = [rs columnNameForIndex:i]; //進行數據庫列名到model之間的映射轉換,拿到屬性名 NSString *propertyName; if(mapping) { propertyName = mapping[columnName]; if (propertyName == nil) { propertyName = columnName; } } else { propertyName = columnName; } objc_property_t objProperty = class_getProperty(modelClass, propertyName.UTF8String); //如果屬性不存在,則不操作 if (objProperty) { if(![rs columnIndexIsNull:i]) { [self setProperty:model value:rs columnName:columnName propertyName:propertyName property:objProperty]; } } NSAssert(![propertyName isEqualToString:@"description"], @"description為自帶方法,不能對description進行賦值,請使用其他屬性名或請ColumnPropertyMappingDelegate進行映射"); } //執行自定義操作 if (block) { block(model, rs); } [models addObject:model]; } [rs close]; return models; } /** * 進行屬性賦值 */ - (void)setProperty:(id)model value:(FMResultSet *)rs columnName:(NSString *)columnName propertyName:(NSString *)propertyName property:(objc_property_t)property { // @"f":@"float", // @"i":@"int", // @"d":@"double", // @"l":@"long", // @"c":@"BOOL", // @"s":@"short", // @"q":@"long", // @"I":@"NSInteger", // @"Q":@"NSUInteger", // @"B":@"BOOL", NSString *firstType = [[[[NSString stringWithUTF8String:property_getAttributes(property)] componentsSeparatedByString:@","] firstObject] substringFromIndex:1]; if ([firstType isEqualToString:@"f"]) { NSNumber *number = [rs objectForColumnName:columnName]; [model setValue:@(number.floatValue) forKey:propertyName]; } else if([firstType isEqualToString:@"i"]){ NSNumber *number = [rs objectForColumnName:columnName]; [model setValue:@(number.intValue) forKey:propertyName]; } else if([firstType isEqualToString:@"d"]){ [model setValue:[rs objectForColumnName:columnName] forKey:propertyName]; } else if([firstType isEqualToString:@"l"] || [firstType isEqualToString:@"q"]){ [model setValue:[rs objectForColumnName:columnName] forKey:propertyName]; } else if([firstType isEqualToString:@"c"] || [firstType isEqualToString:@"B"]){ NSNumber *number = [rs objectForColumnName:columnName]; [model setValue:@(number.boolValue) forKey:propertyName]; } else if([firstType isEqualToString:@"s"]){ NSNumber *number = [rs objectForColumnName:columnName]; [model setValue:@(number.shortValue) forKey:propertyName]; } else if([firstType isEqualToString:@"I"]){ NSNumber *number = [rs objectForColumnName:columnName]; [model setValue:@(number.integerValue) forKey:propertyName]; } else if([firstType isEqualToString:@"Q"]){ NSNumber *number = [rs objectForColumnName:columnName]; [model setValue:@(number.unsignedIntegerValue) forKey:propertyName]; } else if([firstType isEqualToString:@"@\"NSData\""]){ NSData *value = [rs dataForColumn:columnName]; [model setValue:value forKey:propertyName]; } else if([firstType isEqualToString:@"@\"NSDate\""]){ NSDate *value = [rs dateForColumn:columnName]; [model setValue:value forKey:propertyName]; } else if([firstType isEqualToString:@"@\"NSString\""]){ NSString *value = [rs stringForColumn:columnName]; [model setValue:value forKey:propertyName]; } else { [model setValue:[rs objectForColumnName:columnName] forKey:propertyName]; } } @end
創建一個PeopleService演示一下

#import <Foundation/Foundation.h> @interface PeopleService : NSObject + (instancetype)shareInstance; - (void)createTable; - (void)insertOnePerson; - (NSArray *)query; @end

#import "PersonModel.h" #import "PeopleService.h" #import "DbService.h" @interface PeopleService () { DbService *_dbService; } @end @implementation PeopleService + (instancetype)shareInstance { static id instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; } - (instancetype)init { if (self = [super init]) { NSString *dbName = @"people.db"; NSString *directory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; NSString *dbPath = [directory stringByAppendingPathComponent:dbName]; _dbService = [[DbService alloc] initWithPath:dbPath]; } return self; } - (void)createTable { NSString *sql = @"CREATE TABLE People ( \ id INTEGER PRIMARY KEY AUTOINCREMENT, \ str1 TEXT, \ str2 TEXT, \ float1 REAL, \ double1 INTEGER, \ short1 REAL, \ long1 REAL, \ date1 TEXT, \ bool1 INTEGER, \ data1 BLOB \ )"; [_dbService executeUpdate:sql param:nil]; } - (void)insertOnePerson { NSString *sql = @"insert into People(str1, str2, float1, double1, short1, long1, date1, bool1, data1) values(?,?,?,?,?,?,?,?,?)"; NSString *text = @"dataValue"; NSData *data = [text dataUsingEncoding:NSUTF8StringEncoding]; NSArray *param = @[@"bomo", @"male", @70, @175l, @22, @123, [NSDate date], @NO, data]; [_dbService executeUpdate:sql param:param]; } - (NSArray *)query { return [_dbService executeQuery:@"select * from People" withArgumentsInArray:nil modelClass:[PersonModel class]]; } @end
測試
[[PeopleService shareInstance] createTable]; [[PeopleService shareInstance] insertOnePerson]; NSArray *people = [[PeopleService shareInstance] query]; for (PersonModel *person in people) { NSLog(@"name = %@, age = %d", person.name, person.age); }
我們看一下數據庫的表結構
查詢操作只需要一行代碼,其他的都交給DbService,減少代碼量,減少人為錯誤,由於個人水平有限,如有疏漏或問題,歡迎回復,如果大家有更好的方式,可以一起討論
Demo:http://files.cnblogs.com/files/bomo/FmdbDemo.zip