本隨筆系列主要介紹從一個Windows平臺從事C#開發到Mac平臺蘋果開發的一系列感想和體驗歷程,本系列文章是在起步階段逐步積累的,但願帶給你們更好,更真實的轉換歷程體驗。本篇主要開始介紹基於XCode進行IOS程序的開發,介紹使用FMDB對Sqlite數據庫進行操做,以及對數據庫操做類進行抽象設計,以期達到重用、簡化、高效開發的目的。html
在.NET領域開發了不少年,通常常見的項目都須要操做數據庫,包括有Oracle、SqlServer、Mysql、Sqlite、Access等數據庫,這些數據庫是很常見的,咱們在.NET環境裏面開發的各類系統,可能都或多或少須要和其中一種以上的數據庫打交道,這也是我致力於提煉個人.NET領域的Winform開發框架、Web開發框架、混合式開發框架的目的,儘量達到簡化、重用、高效開發的目的。sql
雖然如今在IOS領域作一些研究開發,即便IOS設備更多強調的是一個多媒體的設備,可是數據庫的操做仍是必不可少,所以我先從我熟悉的數據庫這塊入手,瞭解其中數據庫是如何操做的,有哪些現成的組件進行參考學習等等。數據庫
在IOS裏面開發,提起和數據庫打交道,可能不少人都熟悉FMDB這個數據庫的組件,它對IOS裏面操做Sqlite數據庫進行了很大程度的簡化,簡化後,咱們大多數狀況下,只須要和FMDatabase和FMResultSet打交道便可,使用起來很是方便。設計模式
爲了較好介紹總體性的內容,咱們先從FMDB的各類操做進行介紹。框架
1)數據庫打開操做函數
FMDatabase *db= [FMDatabase databaseWithPath:dbPath] ; if (![db open]) { NSLog(@"沒法打開數據庫"); return ; }
2)數據庫操做executeUpdatepost
使用FMDB,對於沒有返回記錄的操做,均可以用executeUpdate進行操做,以下是刪除記錄的函數學習
- (BOOL) deleteByCondition:(NSString *) condition { NSString *query = [NSString stringWithFormat:@"Delete FROM %@ Where %@ ", self.tableName, condition]; BOOL result = [self.database executeUpdate:query]; return result; }
固然,對於SQLite不少數據庫操做,咱們可使用參數化語句進行操做,以下例子所示測試
[db executeUpdate:@"INSERT INTO User (Name,Age) VALUES (?,?)",@"張三",[NSNumber numberWithInt:30]]
參數化也可使用 : 字符做爲參數標識,以下所示,是我封裝的一個數據庫操做函數atom
- (BOOL) deleteById:(id) key { NSDictionary *argsDict = [NSDictionary dictionaryWithObjectsAndKeys:key, @"id", nil]; NSString *query = [NSString stringWithFormat:@"Delete FROM %@ Where %@ =:id", self.tableName, self.primaryKey]; BOOL result = [self.database executeUpdate:query withParameterDictionary:argsDict]; return result; }
3)返回集合的操做
操做有返回集合的語句,咱們就須要用到FMResultSet對象了,這個對象相似於之前見過的遊標,不過不同的東西而已。
FMResultSet *rs=[db executeQuery:@"SELECT * FROM User"]; rs=[db executeQuery:@"SELECT * FROM User WHERE Age = ?",@"20"]; while ([rs next]){ NSLog(@"%@ %@",[rs stringForColumn:@"Name"],[rs stringForColumn:@"Age"]); }
上面的代碼操做,返回一個Resultset集合進行遍歷使用,咱們能夠根據Resultset的一些方法獲取到不一樣的數據內容。
相對於FMResult的操做方式,原生態的Sqlite數據庫操做代碼以下所示。看完是否是感受使用FMDB類庫操做數據庫方便不少呢。
NSString *sqlQuery = @"SELECT * FROM User"; sqlite3_stmt * statement; if (sqlite3_prepare_v2(db, [sqlQuery UTF8String], -1, &statement, nil) == SQLITE_OK) { while (sqlite3_step(statement) == SQLITE_ROW) { char *name = (char*)sqlite3_column_text(statement, 1); NSString *nsNameStr = [[NSString alloc]initWithUTF8String:name]; int age = sqlite3_column_int(statement, 2); char *address = (char*)sqlite3_column_text(statement, 3); NSString *nsAddressStr = [[NSString alloc]initWithUTF8String:address]; NSLog(@"name:%@ age:%d address:%@",nsNameStr,age, nsAddressStr); } } sqlite3_close(db);
FMResultSet方法有下面幾種,分別用於獲取不一樣的數據:
intForColumn:
longForColumn:
longLongIntForColumn:
boolForColumn:
doubleForColumn:
stringForColumn:
dateForColumn:
dataForColumn:
dataNoCopyForColumn:
UTF8StringForColumnIndex:
objectForColumn:
上面小節介紹了使用FMDB類庫對Sqlite數據庫的操做,使咱們大體瞭解了在IOS裏面對數據庫操做的過程。可是單純若是介紹這些,我以爲太泛泛了,並且對咱們使用起來也仍是很不方便,不少時候,我老是想經過設計的方式,來簡化個人各類操做處理。如咱們知道,IOS裏面的Objective C也提供了不少高級語言都有的屬性,如對象繼承,接口、多態等等。
我很早以前寫過的隨筆《Winform開發框架之數據訪問層的設計》 ,介紹了我在.NET領域裏面的數據庫訪問層的設計,因爲這種設計,能很大程度上減小代碼量,並提升開發效率,所以,我也想再IOS裏面造成這樣的數據訪問設計,雖然IOS裏面可能主要是使用單機版的數據庫,如SQLite數據庫,因此我想簡化一些.NET裏面的設計模型。
在.NET裏面,個人框架分層主要以下所示,這種框架的設計模式,已經很好應用在了個人Winform開發框架、WCF及混合式開發框架、Web框架裏面。它們常見的分層模式,能夠分爲UI層、BLL層、DAL層、IDAL層、Entity層、公用類庫層等等
而其中的DAL層的設計,示意圖以下所示,DAL層主要是經過繼承自BaseDAL基類(如BaseDALSQL)進行, BaseDALSQL進行更高一層的抽象,已達到更好的應用目的。
而咱們在IOS裏面,則能夠主要考慮SQLite數據庫便可,所以,我把設計經過簡化,構造下面的設計模型
在項目裏面,數據訪問層的文件以下所示(爲了演示測試方便,使用User表進行操做)。
爲了實現數據的承載,咱們須要把表的數據轉換爲實體類進行顯示和操做,所以實體類的設計也須要考慮好。因爲數據訪問層的基類,封裝了大多數的數據操做,也包括返回的數據對象和集合,所以數據訪問層的基類,也涉及到了數據類型返回的問題。
因爲IOS裏面的Objective C裏面沒有泛型這樣的東西,所以有兩種方式能夠來實現基類實體類的處理:一是使用動態類型id類型做爲實體類類型,一種是使用一種半類型化的類型(實體類的基類)做爲對象,我傾向於使用後者,由於畢竟比較接近真實的類型了。
// // BaseEntity.h // MyDatabaseProject // // Created by 伍華聰 on 14-4-2. // Copyright (c) 2014年 伍華聰. All rights reserved. // #import <Foundation/Foundation.h> @interface BaseEntity : NSObject @end
// // UserInfo.h // MyDatabaseProject // // Created by 伍華聰 on 14-3-30. // Copyright (c) 2014年 伍華聰. All rights reserved. // #import <Foundation/Foundation.h> #import "BaseEntity.h" @interface UserInfo : BaseEntity @property(nonatomic, copy) NSString *ID; @property(nonatomic, copy) NSString *name; @property(nonatomic, copy) NSString *fullName; @end
這種繼承模式和.NET的繼承關係差很少了。
對於數據訪問層的設計代碼,它就是在數據訪問基類的定義以下,基類使用了FMDB的數據訪問類進行操做的,裏面不少操做的接口就是模仿我在.NET領域裏面的數據訪問層的設計。
// // BaseDAL.h // MyDatabaseProject // // Created by 伍華聰 on 14-3-30. // Copyright (c) 2014年 伍華聰. All rights reserved. // #import <Foundation/Foundation.h> #import "FMDatabase.h" #import "BaseEntity.h" @interface BaseDAL : NSObject { NSString *pathToDatabase; } #pragma 數據庫相關屬性 @property (nonatomic, strong) NSString *pathToDatabase; @property (nonatomic, readonly) FMDatabase *database; // 數據庫操做對象 @property (nonatomic, strong) NSString *tableName;//表名稱 @property (nonatomic, strong) NSString *primaryKey;//主鍵 @property (nonatomic, strong) NSString *sortField;//排序,默認爲主鍵 @property (nonatomic, assign, getter=isDescending) BOOL descending;//是否降序查詢 //將DataReader的屬性值轉化爲實體類的屬性值,返回實體類(子類必須重寫) -(id) rsToEntity:(FMResultSet *)rs; //將實體對象的屬性值轉化爲字典列表對應的鍵值(子類必須重寫) -(NSDictionary *) dictByEntity:(BaseEntity *) info; #pragma 基礎操做接口 //根據指定對象的ID,從數據庫中刪除指定對象 - (BOOL) deleteById:(id) key; //根據指定條件,從數據庫中刪除指定對象 - (BOOL) deleteByCondition:(NSString *) condition; //更新對象屬性到數據庫中 -(BOOL) update:(BaseEntity *) info byKey:(id) key; //插入指定對象到數據庫中 -(BOOL) insert:(BaseEntity *) info; //插入指定對象到數據庫中,並返回最後插入的ID -(NSInteger) insert2:(BaseEntity *) info; //查詢數據庫,檢查是否存在指定ID的對象 - (BaseEntity *) findById:(id) key; //根據條件查詢數據庫,若是存在返回第一個對象 -(BaseEntity *) findSingle:(NSString *) condition; //根據條件查詢數據庫,若是存在返回第一個對象 -(BaseEntity *) findSingle:(NSString *) condition orderBy:(NSString *) orderBy; //根據條件查詢數據庫,並返回對象集合 - (NSArray *) find:(NSString *) condition; //根據條件查詢數據庫,並返回對象集合 - (NSArray *) find:(NSString *) condition orderBy:(NSString *) orderBy; //獲取表的所有數據 - (NSArray *) getAll; //獲取表的所有數據 - (NSArray *) getAll:(NSString *) orderBy; //獲取某字段數據字典列表 -(NSArray *) getFieldList:(NSString *) fieldName; //獲取表的全部記錄數量 -(int) getRecordCount; //根據條件,獲取表查詢的記錄數量 -(int) getRecordCount:(NSString *) condition; //根據條件,判斷是否存在記錄 -(BOOL) isExistRecord:(NSString *)condition; //查詢數據庫,檢查是否存在指定鍵值的對象 -(BOOL) isExist:(NSString *)fieldName value:(id) value; //根據主鍵和字段名稱,獲取對應字段的內容 -(NSString *) getFieldValue:(NSString *)key fieldName:(NSString *)fieldName; //執行SQL查詢語句,返回查詢結果的全部記錄的第一個字段,用逗號分隔。 -(NSString *) sqlValueList:(NSString *)query; #pragma 數據庫初始化函數及關閉操做 //根據SQLite數據庫地址初始化數據庫 -(id) initWithPath:(NSString *)filePath; //根據SQLite數據庫名稱初始化數據庫 -(id) initWithFileName:(NSString *)fileName; // 關閉鏈接 -(void) close; @end
和個人.NET框架裏面的數據訪問層設計同樣,數據訪問基類已經封裝了大多數的數據訪問操做,所以各個表的數據訪問對象,它的代碼就能夠很簡潔了。從上面的基類接口設計能夠看到,裏面一些實體類返回函數或者列表返回函數,都使用了BaseEntity做爲對象,咱們具體在起子類使用的時候,把它返回的對象再一次轉換便可。對於數據庫訪問基類,咱們以一個返回集合的接口實現來分析。
- (NSArray *) find:(NSString *) condition orderBy:(NSString *) orderBy { NSString *query = [NSString stringWithFormat:@"SELECT * FROM %@ ", self.tableName]; if (condition != nil) { query = [query stringByAppendingFormat:@" where %@ ", condition]; } if (orderBy != nil) { query = [query stringByAppendingString:orderBy]; } else { query = [query stringByAppendingFormat:@" ORDER BY %@ %@ ", self.sortField, self.isDescending ? @"DESC" : @"ASC"]; } FMResultSet *rs = [self.database executeQuery:query]; NSMutableArray *array = [NSMutableArray arrayWithCapacity:[rs columnCount]]; BaseEntity *info = nil; //默認初始化爲空 while ([rs next]) { info = [self rsToEntity:rs]; [array addObject:info]; } [rs close]; return array; }
上面代碼使用了參數化的SQL語句進行查詢,而且,對返回的數據庫的ResultSet進行轉換爲實體類。
info = [self rsToEntity:rs];
因爲基類封裝了大多數的數據庫操做函數,所以數據訪問層的具體表的實現類,能夠很簡潔,可是已經具有了常見的CRUD操做,以及一些分頁查詢等複雜的數據操做功能。
// // UserDAL.h // MyDatabaseProject // // Created by 伍華聰 on 14-3-30. // Copyright (c) 2014年 伍華聰. All rights reserved. // #import <Foundation/Foundation.h> #import "FMDatabase.h" #import "BaseDAL.h" #import "UserInfo.h" @interface UserDAL : BaseDAL { } //單例模式 +(UserDAL *) defaultDAL; @end
基於篇幅的緣由,我將在下一篇介紹如何在界面層中使用這樣的數據訪問設計類,先放上一些測試程序的界面截圖。