標籤:use 管理 案例 uem mask 網路 完整 版本 操作
概念:
FMDB 是用於資料存放區的架構,它是 iOS 平台下對 SQLite 資料庫的封裝。FMDB 是物件導向的,它以 OC 的方式封裝了 SQLite 的 C 語言 API,使用起來更加方便。
Core Data是 ORM(對象關係映射) 的一種體現,使用Core Data需要用到模型資料的轉化,雖然操作簡單,不需要直接操作資料庫,但是效能沒有直接使用SQLite高。但是SQLite使用的時候需要使用c語言中的函數,操作比較麻煩,因此需要對它進行封裝。但是如果只是簡單地封裝,很可能會忽略很多重要的細節,比如如何處理並發以及安全性更問題。
使用第三方架構FMDB,它是對libsqlite3架構的封裝,用起來的步驟與SQLite使用類似,並且它對於多線程的同時操作一個表格時進行了處理,也就意味著它是安全執行緒的。FMDB是輕量級的架構,使用靈活,它是很多企業開發的首選。
FMDB GitHub:
重要的類:
FMResultSet : 表示FMDatabase執行查詢之後的結果集。
FMDatabase : 表示一個單獨的SQLite資料庫操作執行個體,用來執行SQL語句, 通過它可以對資料庫進行增刪改查等等操作。
FMDatabaseAdditions : 擴充FMDatabase類,新增對查詢結果只返回單個值的方法進行簡化,對錶、列是否存在,版本號碼,校正SQL等等功能。
FMDatabaseQueue : 使用串列隊列 ,對多線程的操作進行了支援,用於在多線程中執行多個查詢或更新,它是安全執行緒的。
FMDatabasePool : 使用任務池的形式,對多線程的操作提供支援。(不過官方對這種方式並不推薦使用,優先選擇FMDatabaseQueue的方式:ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD)
FMDatabaseQueue 要使用單例建立,這樣多線程調用時,資料庫操作使用一個隊列,保證安全執行緒。
是把資料庫的操作放到一個串列隊列中,從而保證不會在同一時間對資料庫做改動。
多線程下使用FMDatabaseQueue的操作原理就可以建立一個管理類對模型資料的存取查刪進行統一管理,可以使用工具類操作,也可以建立整合NSObject的子類進行管理,需要存取的模型類繼承此子類即可。
FMDatabaseQueue如何?多線程?
/** * FMDatabaseQueue如何?多線程的案例 */- (void)FMDatabaseQueueMutilThreadTest { //1、擷取資料庫檔案路徑 NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; NSString *fileName = [doc stringByAppendingPathComponent:@"students.sqlite"]; //使用queue1 FMDatabaseQueue *queue1 = [FMDatabaseQueue databaseQueueWithPath:fileName]; [queue1 inDatabase:^(FMDatabase *db) { for (int i=0; i<10; i++) { NSLog(@"queue1---%zi--%@",i,[NSThread currentThread]); } }]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [queue1 inDatabase:^(FMDatabase *db) { for (int i=11; i<20; i++) { NSLog(@"queue1---%zi--%@",i,[NSThread currentThread]); } }]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [queue1 inDatabase:^(FMDatabase *db) { for (int i=20; i<30; i++) { NSLog(@"queue1---%zi--%@",i,[NSThread currentThread]); } }]; }); //雖然開啟了多個線程,可依然還是串列處理。原因如下: /**FMDatabaseQueue雖然看似一個隊列,實際上它本身並不是,它通過內部建立一個Serial的dispatch_queue_t來處理通過inDatabase和inTransaction傳入的Blocks,所以當我們在主線程(或者後台)調用inDatabase或者inTransaction時,代碼實際上是同步的。FMDatabaseQueue這麼設計的目的是讓我們避免發生並發訪問資料庫的問題,因為對資料庫的訪問可能是隨機的(在任何時候)、不同線程間(不同的網路回調等)的請求。內建一個Serial隊列後,FMDatabaseQueue就變成安全執行緒了,所有的資料庫訪問都是同步執行,而且這比使用@synchronized或NSLock要高效得多。 */}
//雖然開啟了多個線程,可依然還是串列處理。原因如下:
FMDatabaseQueue雖然看似一個隊列,實際上它本身並不是,
它通過內部建立一個 Serial 的 dispatch_queue_t 來處理通過 inDatabase 和 inTransaction 傳入的 Blocks.
所以當我們在主線程(或者後台)調用 inDatabase 或者 inTransaction 時,代碼實際上是同步的。
FMDatabaseQueue這麼設計的目的是讓我們避免發生並發訪問資料庫的問題,因為對資料庫的訪問可能是隨機的(在任何時候)、不同線程間(不同的網路回調等)的請求。
內建一個 Serial 隊列後,FMDatabaseQueue 就變成安全執行緒了,所有的資料庫訪問都是同步執行,而且這比使用 @synchronized 或 NSLock要高效得多。
雖然每個queue內部是串列執行的,當時不同的queue之間可以並發執行。
/** * FMDatabaseQueue如何?多線程的案例2 */- (void)FMDatabaseQueueMutilThreadTest2{ //1、擷取資料庫檔案路徑 NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; NSString *fileName = [doc stringByAppendingPathComponent:@"students.sqlite"]; //使用queue1 FMDatabaseQueue *queue1 = [FMDatabaseQueue databaseQueueWithPath:fileName]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [queue1 inDatabase:^(FMDatabase *db) { for (int i=0; i<5; i++) { NSLog(@"queue1---%zi--%@",i,[NSThread currentThread]); } }]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [queue1 inDatabase:^(FMDatabase *db) { for (int i=5; i<10; i++) { NSLog(@"queue1---%zi--%@",i,[NSThread currentThread]); } }]; }); //使用queue2 FMDatabaseQueue *queue2 = [FMDatabaseQueue databaseQueueWithPath:fileName]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [queue2 inDatabase:^(FMDatabase *db) { for (int i=0; i<5; i++) { NSLog(@"queue2---%zi--%@",i,[NSThread currentThread]); } }]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [queue2 inDatabase:^(FMDatabase *db) { for (int i=5; i<10; i++) { NSLog(@"queue2---%zi--%@",i,[NSThread currentThread]); } }]; }); //建立多個隊列操作同一個 就不發保證安全執行緒了。不過一般 不會這麼用。}
如果後台在執行大量的更新,而主線程也需要訪問資料庫,雖然要訪問的資料量很少,但是在後台執行完之前,還是會阻塞主線程。 怎麼辦?
解決方案:
- 如果你是在後台使用的
inDatabase
來執行更新,可以考慮換成 inTransaction
,後者比前者更新起來快很多,特別是在更新量比較大的時候(比如更新1000條或10000條)。
- 拆解你的更新資料量,如果有300條,可以分10次、每次更新30條。當然有時不能這麼做,因為你可能通過網路請求回來的資料,你希望一次性、完整地寫入到資料庫中,雖然有局限性,不過這確實能很好地減少每個Block佔用資料庫的時間。
- 上面兩點可以改善問題,但是問題依然是存在的,在大多數時候,你應該把從主線程調用
inDatabase
和 inTransaction
放在非同步裡:dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self.databaseQueue inDatabase:^(FMDatabase *db) { //do something... }];});
iOS 開發之 FMDB 源碼分析