在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ 用 SQLite 和 FMDB 替代 Core Data
與四軸無人機的通訊
在沙盒中編寫腳本
結構體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學
NSString 與 Unicode
代碼簽名探析
測試
架構
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應用數據腳本化
一個完整的 Core Data 應用
插件
字符串
為 iOS 建立 Travis CI
先進的自動布局工具箱
動畫
為 iOS 7 重新設計 App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網絡應用實例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應式 Android 應用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調試
項目介紹
Swift 的強大之處
測試并發(fā)程序
Android 通知中心
調試:案例學習
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學習的一代人
視頻
Playground 快速原型制作
Omni 內部
同步數據
設計優(yōu)雅的移動游戲
繪制像素到屏幕上
相機與照片
音頻 API 一覽
交互式動畫
常見的后臺實踐
糟糕的測試
避免濫用單例
數據模型和模型對象
Core Data
字符串本地化
View Controller 轉場
照片框架
響應式視圖
Square Register 中的擴張
DTrace
基礎集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設計的藝術
導航應用
線程安全類的設計
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機項目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數式 API
iOS 7 的多任務
自定義 Collection View 布局
測試 View Controllers
訪談
收據驗證
數據同步
自定義 ViewController 容器轉場
游戲
調試核對清單
View Controller 容器
學無止境
XCTest 測試實戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術:Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴展
理解 Scroll Views
使用 VIPER 構建 iOS 應用
Android 中的 SQLite 數據庫支持
Fetch 請求
導入大數據集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機捕捉
語言標簽
同步案例學習
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉字符串
相機工作原理
Build 過程

用 SQLite 和 FMDB 替代 Core Data

憑良心講,我不能告訴你不去使用 Core Data。它不錯,而且也在變得更好,并且它被很多其他 Cocoa 開發(fā)者所理解,當有新人加入你的團隊或者需要別人接手你的 app 的時候,這點很重要。

更重要的是,不值得花時間和精力去寫自己的系統(tǒng)去代替它。使用 Core Data 吧。真的。

為什么我不使用Core Data

Mike Ash 寫到

就個人而言,我不是個狂熱粉絲。我發(fā)現(xiàn) (Core Data 的) API 是笨拙的,并且框架本身對于超過一定數量級的數據的處理是極其緩慢的。

一個實際的例子:10,000 個條目

想象一個 RSS 閱讀器,一個用戶可以在一個 feed 上點擊右鍵,并且選擇標記所有為已讀。

實際實現(xiàn)上,我們有一個帶有 read 屬性的 Article 實體。把所有條目標記為已讀,app 需要加載這個 feed 的所有文章 (可能通過一對多的關系),然后設置 read 屬性為 YES。

大部分時候這樣是沒問題的。但是設想那個 feed 有 200 篇文章,為了避免阻塞主線程,你可能考慮在后臺線程里做這個工作 (尤其是如果這個 app 是一個 iPhone app)。一旦你開始使用 Core Data 多線程的時候,事情就開始變的不好處理了。

這可能還沒這么糟糕,至少不值得拋棄使用 Core Data。

但是,再添加同步。

我用過兩個不同的 RSS 同步 API,它們返回已讀文章的 uniqueID 數組。其中一個返回近 10,000 個 ID。

你不會打算在主線程中加載 10,000 篇文章,然后設置 read 為 NO。你大概也不會想在后臺線程里加載 10,000 篇文章,即使很小心地管理內存。這里有太多的工作(如果你頻繁的這么做,想一下對電池壽命的影響)。

概念上來說,你真正想要做的是,讓數據庫將 uniqueID 列表里的每一篇文章的 read 設置為 YES。

SQLite 可以做到這個,只用一次調用。如果 uniqueID 上有索引,這會很快。而且你可以在后臺線程執(zhí)行,這和在主線程執(zhí)行一樣容易。

另一個例子:快速啟動

我的另一個 app,我想減少啟動時間 — 不只是 app 的啟動時間,還有數據顯示之前所需要的時間。

這是個類似 Twitter 的 app (雖然它不是):它顯示消息的時間軸。顯示時間軸意味著獲取消息,并加載相關用戶。它很快,但是在啟動的時候,會填充 UI,然后填充數據。

關于 iPhone app(或者所有應用),我的理論是,啟動時間比其他大部分開發(fā)者想的都要重要。啟動時間很慢的 app 是不太可能被啟動的,因為人們潛意識里會記住,并且在啟動那個應用這件事情上形成一種抵抗心理。減少啟動時間可以減少這種阻力,用戶也會更愿意使用你的應用,并且把它推薦給其他人。這是你讓你的 app 成功的一部分。

因為我不使用 Core Data,我手頭有一個簡單的,保守的解決方案。我把時間軸(消息和人物對象)通過 NSCoding 保存到一個 plist 文件中。啟動的時候它讀取這個文件,創(chuàng)建消息和人物對象,UI 一出現(xiàn)就顯示時間軸。

這明顯的減少了延遲。

把消息和人物對象作為 NSManagedObject 的實例對象,這是不可能的。(假設我已經編碼并且存儲對象的 IDs,但是那意味著讀取 plist 文件,之后再涉及數據庫。這種方式我完全避免了數據庫)。

(在更新更快的機器出來后, 我去掉了那些代碼。回顧過去,我希望我可以把它留下來。)

我怎么考慮這個問題

當考慮是否使用 Core Data,我考慮下面這些事情:

會有難以置信數量的數據嗎?

對于一個 RSS 閱讀器或者 Twitter app,答案顯而易見:是的。有些人關注上百個人。一個人可能訂閱了上千個 feed。

即使你的應用不從網絡獲取數據,用戶仍然有可能自動添加數據。如果你用一個支持 AppleScript 的 Mac,有人會寫腳本去加載非常多的數據。如果通過 web API 去添加數據也是一樣的。

會有一個 Web API 包含類似于數據庫的結果嗎(對比于類似對象的結果)?

一個 RSS 同步 API 能夠返回一個已讀文章的 uniqueID 列表。一個筆記的應用的一個同步 API 可能返回已存檔的和已刪除的筆記的 uniqueID 列表。

用戶可能通過操作處理大量對象嗎?

在底層,需要考慮和之前一樣的問題。當有人刪除所有已經下載的 5,000 個面食食譜,你的食譜 app 性能如何?(在 iPhone 上?)

如果我決定使用 Core Data(我已經發(fā)布過使用 Core Data 的應用),我會特別注意我如何使用它。結果為了得到好的性能,我發(fā)現(xiàn)我把它當做了一個奇怪接口的 SQL 數據庫在使用,然后我就知道了,我應該舍棄 Core Data,而去直接使用 SQLite。

我如何使用 SQLite

我通過 FMDB Wrapper 來使用 SQLite,F(xiàn)MDB 來自 Flying Meat Software,由 Gus Mueller 開發(fā)。

基本操作

在使用 iPhone 和 Core Data 之前,我就使用過 SQLite。這里有關于它如何工作的要點:

  • 所有數據庫訪問 - 讀和寫 - 發(fā)生在一個后臺線程的連續(xù)的隊列里。在主線程中觸及數據庫是從來不被允許的。使用一個連續(xù)隊列來保證每一件事是按順序發(fā)生的。
  • 我大量使用 blocks 使得異步編程容易些。
  • 模型對象只存在在主線程(但有兩個重要的例外),改變會觸發(fā)一個后臺保存。
  • 模型對象列出來它們在數據庫中存儲的屬性。這可能在代碼里或者在 plist 文件里。
  • 有些模型對象是唯一的,有些不是。取決于 app 的需要(大部分情況是唯一的)。
  • 對關系型數據,我盡可能避免創(chuàng)建查詢表。
  • 一些對象類型在啟動的時候就完全讀入內存,另一些對象類型我可能創(chuàng)建和維護的只有它們 uniqueID 的一個 NSMutableSet,所以我可以在不去碰數據庫的情況下就知道什么存在、什么不存在。
  • Web API 的調用發(fā)生在后臺線程,它們使用“分離“的模型對象。

我會使用我目前的 app 的代碼來描述。

數據庫更新

在我最近的 app 中,有一個單一的數據庫控制器 - VSDatabaseController,它通過 FMDB 來與 SQLite 對話。

FMDB 區(qū)分更新和查詢。更新數據庫,app 調用:

-[VSDatabaseController runDatabaseBlockInTransaction:(VSDatabaseUpdateBlock)databaseBlock]

VSDatabaseUpdateBlock很簡單:

typedef void (^VSDatabaseUpdateBlock)(FMDatabase *database);

runDatabaseBlockInTransaction也很簡單:

- (void)runDatabaseBlockInTransaction:(VSDatabaseUpdateBlock)databaseBlock {
    dispatch_async(self.serialDispatchQueue, ^{
        @autoreleasepool {
            [self beginTransaction];
            databaseBlock(self.database);
            [self endTransaction];
        }
    });
}

(注意我用的自己的連續(xù) dispatch 隊列。Gus 建議看一下 FMDatabaseQueue,這也是一個連續(xù)調度隊列。因為它比 FMDB 剩下的其他東西都要新,所以我自己還沒有去看過。)

beginTransactionendTransaction 的調用是可嵌套的(在我的數據庫控制器里)。在合適的時候他們會調用 -[FMDatabase beginTransaction]-[FMDatabase commit]。(使用 transactions 是讓 SQLite 變快的一大關鍵。)提示:我在 -[NSThread threadDictionary] 中存儲當前的 transaction 的計數。這對于針對每個線程的數據來說是很方便的,我也幾乎從不用它做其他的事情。

這兒有個調用更新數據庫的簡單例子:

- (void)emptyTagsLookupTableForNote:(VSNote *)note {
    NSString *uniqueID = note.uniqueID;
    [self runDatabaseBlockInTransaction:^(FMDatabase *database) {
        [database executeUpdate:
            @"delete from tagsNotesLookup where noteUniqueID = ?;", uniqueID];
    }];
}

這說明了不少事情。首先, SQL 并不可怕。即使你從沒見過它,你也知道這行代碼做了什么。

VSDatabaseController 的所有其他公共接口一樣,emptyTagsLookupTableForNote 也應該在主線程中被調用。模型對象只能在主線程中被引用,所以在 block 中使用 uniqueID ,而不是 VSNote 對象。

注意在這種情況下,我更新了一個查詢表。Notes 和 tags 有一個多對多關系,一種表現(xiàn)方式是用一個數據庫表映射 note uniqueIDs 和 tag uniqueIDs。這些表不會很難維護,但是如果可能,我盡量避免使用它們。

注意在更新字符串中的 ?。-[FMDatabase executeUpdate:] 是一個可變參數函數。SQLite 支持使用占位符 - ? 字符 - 所以你不需要把實際的值放入字符串中去。這是一個安全上的考量:它可以守護程序避免 SQL 注入。它也可以幫助你減少必須 escape 值這樣的不必要的麻煩。

最后,注意在 tagsNotesLookup 表中,有一個 noteUniquelID 的索引(索引是 SQLite 性能的又一個關鍵)。這行代碼在每次啟動時都調用:

[self.database executeUpdate:
    @"CREATE INDEX if not exists noteUniqueIDIndex on tagsNotesLookup (noteUniqueID);"];

數據庫獲取

要獲取對象,app 調用:

-[VSDatabaseController runFetchForClass:(Class)databaseObjectClass 
                             fetchBlock:(VSDatabaseFetchBlock)fetchBlock 
                      fetchResultsBlock:(VSDatabaseFetchResultsBlock)fetchResultsBlock];

這兩行代碼做了大部分工作:

FMResultSet *resultSet = fetchBlock(self.database);
NSArray *fetchedObjects = [self databaseObjectsWithResultSet:resultSet 
                                                       class:databaseObjectClass];

用 FMDB 查找數據庫返回一個 FMResultSet. 通過 resultSet 你可以逐句循環(huán),創(chuàng)建模型對象。

我建議寫通用的代碼去將數據庫中的行轉換為對象。一種我已經使用的方法是在 app 中用一個 plist 文件,將列的名字映射到模型對象的屬性上去。它也包含類型,所以你知道是調用 -[FMResultSet dateForColumn:]還是 -[FMResultSet stringForColumn:]或是其他方法。

在我的最新 app 里我做的事情更簡單。數據庫行剛好對應模型對象屬性的名字。除了那些名字以 “Date” 結尾的屬性以外,所有屬性都是字符串。簡單,但是你可以看到所需要明顯清晰的對應關系。

唯一對象

創(chuàng)建模型對象的操作和從數據庫獲取數據操作在同樣的后臺線程進行。一但獲取到,app 會把它們轉到主線程。

通常我會使用唯一對象。數據庫里的同一行,始終對應著同樣的一個對象。

為了做到唯一,我使用 NSMapTable 創(chuàng)建了一個對象緩存,在 init 函數里:_objectCache = [NSMapTable weakToWeakObjectsMapTable]。我來解釋一下:

例如,當你進行一個數據庫獲取操作并且把對象轉交給一個視圖控制器時,你希望在這個視圖控制器使用完這些對象后,或者在一個不一樣的視圖控制器被顯示后,這些對象可以消失。

如果你的對象緩存是一個 NSMutableDictionary,那你將需要做一些額外的工作來清空緩存中的對象。保證它只引用了那些其他地方有引用的對象是一件非常讓人蛋疼的事情。而使用配合弱引用的NSMapTable,這個問題就被自動處理掉了。

所以:我們在主線程中讓對象唯一。如果一個對象已經在對象緩存中存在,我們就用那個存在的對象。(因為主線程中對象可能有改變,因此在沖突時我們使用主線程的對象。)如果對象緩存中沒有,它會被加上。

保持對象在內存中

有很多次,把整個對象類型保留在內存中是有道理的。我最新的 app 有一個 VSTag 對象。雖然可能有成百上千篇筆記,但 tags 的數量很小,基本少于十個。一個 tag 只有 6 個屬性:三個 BOOL,兩個很小的 NSstring,還有一個 NSDate。

啟動的時候,app 獲取所有 tags 并且把它們保存在兩個字典里,其中一個的鍵是 tag 的 uniqueID,另一個的鍵是 tag 名字的小寫。

這簡化了很多事,比如 tag 自動補全系統(tǒng),就可以完全在內存中操作,而不需要從數據庫獲取了。

但是很多次,把所有數據保留在內存中是不實際的。比如我們不會在內存中保留所有筆記。

但是也有很多次,把所有對象保存在內存中是不可行的。當不能在內存中保留一個對象類型時,你可能會希望在內存中保留所有 uniqueID,你可以進行這樣一個獲取操作:

FMResultSet *resultSet = [self.database executeQuery:@"select uniqueID from some_table"];

resultSet 只包含了 uniqueIDs, 你可以存儲到一個 NSMutableSet 里。

我發(fā)現(xiàn)有時這個對 web APIs 很有用。想象一個 API 返回從某個確定的時間以后所創(chuàng)建筆記的 uniqueIDs 列表。如果我本地已經有了一個包含所有筆記 uniqueIDs 的 NSMutableSet,我可以 (通過 -[NSMutableSet minusSet]) 快速檢查是否有漏掉的筆記,然后去調用另一個 API 下載那些漏掉的筆記。這些完全不需要觸及數據庫。

但是,像這樣的事情應該小心處理。app 可以提供足夠的內存嗎?它真的簡化編程并且提高性能了嗎?

使用 SQLite 和 FMDB 來代替 Core Data,會給你帶來大量的靈活性和使用更聰明的辦法來解決問題的空間。記住有的時候聰明是好的,也有的時候聰明是一個大錯誤。

Web APIs

我的 API 調用都跑在后臺進程里(通常是用一個 NSOperationQueue,這樣我可以取消操作)。模型對象只在主線程,然后將模型對象傳遞給我的 API 調用。

具體這么做:一個數據庫對象有一個 detachedCopy 方法,可以復制數據庫對象。這個復制的對象不會被我用來做唯一化的對象緩存所引用。唯一引用這個對象的地方是 API 調用,當 API 調用結束時,這個復制的對象也就消失了。

這是一個好的系統(tǒng),因為它意味著我可以在 API 調用里使用模型對象。方法看起來像這樣:

- (void)uploadNote:(VSNote *)note {
    VSNoteAPICall *apiCall = [[VSNoteAPICall alloc] initWithNote:[note detachedCopy]];
    [self enqueueAPICall:apiCall];
}

VSNoteAPICall 從分離出來的 VSNote 中獲取值,并且創(chuàng)建 HTTP 請求,而不是將 note 包裝成一個字典或其他表現(xiàn)形式。

處理 Web API 的返回值

我對 web 的返回值做了一些類似的處理。我會對返回的 JSON 或者 XML 創(chuàng)建一個模型對象,這個模型對象也是分離的。它沒有存儲在唯一化模型緩存里。

這里有些事情是不確定的。有時我們需要用那個模型對象在在內存緩存以及數據庫兩個地方做本地修改。

數據庫通常是容易的部分。比如:我的 app 已經有一個方法來保存筆記對象。它使用 SQL 的 insert or replace 命令。我只需用從 web API 返回值所生成的筆記對象來進行調用,數據庫就會更新。

但是可能同樣的對象在內存中還有一個版本,幸運的是我們很容易找到它:

VSNote *cachedNote = [self.mapTable objectForKey:downloadedNote.uniqueID];

如果 cachedNote 存在,我會讓它從 downloadedNote中 獲取值(這部分可以共享 detachedCopy 方法的代碼。),而不是直接替換它(這樣可能違反唯一性)。

一旦 cachedNote 更新了,觀察者會通過 KVO 察覺到變化,或者我會發(fā)送一個 NSNotification,或者兩者都做。

Web API 調用也會返回一些其他值。我提到過 RSS 閱讀器可能獲得一個已讀條目的大列表。這種情況下,我選擇通過那個列表創(chuàng)建一個 NSSet,在內存的緩存中更新每一個緩存文章的 read 屬性,然后調用 -[FMDatabase executeUpdate:]。

完成這個工作的關鍵是 NSMapTable 的查找是快速的。如果你找的對象在一個 NSArray 里,我們就得重新考慮考慮了。

數據庫遷移

當正常工作的時候,Core Data 的數據庫遷移功能還是蠻酷的。

但是不可避免的,它在代碼和數據庫中加入了一層。如果你更直接一點,去使用 SQLite,那么更新數據庫也就變得越直接。

你可以安全容易的做到這點。

比如加一個表:

[self.database executeUpdate:@"CREATE TABLE if not exists tags "
    "(uniqueID TEXT UNIQUE, name TEXT, deleted INTEGER, deletedModificationDate DATE);"];

或添加一個索引

[self.database executeUpdate:@"CREATE INDEX if not exists "
    "archivedSortDateIndex on notes (archived, sortDate);"];

或添加一列:

[self.database executeUpdate:@"ALTER TABLE tags ADD deletedDate DATE"];

app 應該用類似上面這樣的代碼來首先對數據庫進行設置。以后的改變就是添加對 executeUpdate 的調用 — 我讓他們按順序執(zhí)行。因為我的數據庫是我設計的,所以這不會有什么問題(我從沒碰到性能問題,它很快)。

當然大的改變需要更多代碼。如果你的數據通過 web 獲取,有時你可以從一個新數據庫模型開始,重新下載你需要的數據。

性能技巧

SQLite 可以非常非???,但是也可以非常慢。完全取決于你怎么使用它。

事務

把更新包裝在事務里。在更新前調用 -[FMDatabase beginTransaction],更新后調用 -[FMDatabase commit]。

如果你不得不反規(guī)范化( Denormalize)

反規(guī)范化讓人很不爽。這個方法是,為了加速檢索而添加冗余數據,但是它意味著你需要維護冗余數據。

我總是盡力避免它,直到這樣能有嚴重的性能差異。然后我會盡可能少得這么做。

使用索引

我的 app 中 tags 表的創(chuàng)建語句像這樣:

CREATE TABLE if not exists tags 
  (uniqueID TEXT UNIQUE, name TEXT, deleted INTEGER, deletedModificationDate DATE);

uniqueID 列是自動索引的,因為它定義為 unique。但是如果我想用 name 來查詢表,我可能會在name上創(chuàng)建一個索引,像這樣:

CREATE INDEX if not exists tagNameIndex on tags (name);

你可以一次性在多列上創(chuàng)建索引,像這樣:

CREATE INDEX if not exists archivedSortDateIndex on notes (archived, sortDate);

但是注意太多索引會降低你的插入速度。你只需要足夠數量并且是正確的那些。

使用命令行應用

當我的 app 在模擬器里運行時,我會用 NSLog 輸出數據庫的路徑。我可以通過 sqlite3 的命令行來打開數據庫。(通過 man sqlite3 命令來了解這個應用的更多信息)。

打開數據庫的命令:sqlite3 path/to/database。

打開以后,你可以輸入 .schema 來查看 schema。

你可以更新和查詢,這是在你的 app 使用 SQL 之前就將它們正確地準備妥當的很好的方式。

這里面最酷的一部分是,SQLite Explain Query Plan 命令,你會希望確保你的語句執(zhí)行的盡可能快。

真實的例子

我的 app 顯示所有沒有歸檔筆記的標簽列表。每當筆記或者標簽有變化,這個查詢就會重新執(zhí)行一次,所以它需要很快。

我可以用 SQL join 來查詢,但是這會很慢(join 都很慢)。

所以我放棄 sqlite3 并開始嘗試別的方法。我又檢查了一次我的 schema,意識到我可以反規(guī)范化。一個筆記的歸檔狀態(tài)可以存儲在 notes 表里,它也可以存儲在 tagsNotesLookup 表。

然后我可以執(zhí)行一個查詢:

select distinct tagUniqueID from tagsNotesLookup where archived=0;

我已經有了一個在 tagUniqueID 上的索引。所以我用 explain query plan 來告訴我當我執(zhí)行這個查詢的時候會發(fā)生什么。

sqlite> explain query plan select distinct tagUniqueID from tagsNotesLookup where archived=0;
0|0|0|SCAN TABLE tagsNotesLookup USING INDEX tagUniqueIDIndex (~100000 rows)

它用了一個索引,這很不錯,但是 SCAN TABLE 聽起來不太好,最好是一個 SEARCH TABLE 加上覆蓋索引的方式。

我在 tagUniqueID 和 archive 上建了索引:

CREATE INDEX archivedTagUniqueID on tagsNotesLookup(archived, tagUniqueID);

再次執(zhí)行 explain query plan:

sqlite> explain query plan select distinct tagUniqueID from tagsNotesLookup where archived=0;
0|0|0|SEARCH TABLE tagsNotesLookup USING COVERING INDEX archivedTagUniqueID (archived=?) (~10 rows)

現(xiàn)在好多了。

更多性能提示

FMDB 的某處加了緩存 statements 的能力,所以當創(chuàng)建或打開一個數據庫的時候,我總是調用 [self.database setShouldCacheStatements:YES]。這意味著對每個調用你不需要再次編譯每個 statement。

我從來沒有找到關于使用 vacuum 的好的指引。如果數據庫沒有定期壓縮,它會變得越來越慢。我的 app 會每周跑一次 vacuum。(在 NSUserDefaults 里存儲上次 vacuum 的時間,然后在開始的時候檢查是否過了一周)。

使用 auto_vacuum 可能會更好,可以參看 pragma statements supported by SQLite 列表。

其他酷的東西

Gus Mueller 讓我講講自定義 SQLite 方法的內容。我并沒有真的使用過這些東西,不過既然他指出了,我可以放心的說我能找到它的用處。因為它很酷。

在 Gus 的這個 gist 里,有一個查詢是這樣的:

select displayName, key from items where UTTypeConformsTo(uti, ?) order by 2;

SQLite 完全不知道 UTTypes 的事情。但是你可以通過代碼塊來添加核心方法,感興趣的話,可以看看 -[FMDatabase makeFunctionNamed:maximumArguments:withBlock:] 方法。

你可以執(zhí)行一個大的查詢來替代,然后評估每個對象 - 但是那需要更多工作。最好在 SQL 級就過濾,而不是在將表格行轉為對象以后再做這件事情。

最后

你真的應該使用 Core Data,我不是在開玩笑。

我用 SQLite 和 FMDB 一段時間了,我對多得到的好處感到很興奮,也得到非同一般的性能。

但是記住設備在不斷變快。也請記住,其他看你代碼的人期望看到 Core Data,這是他們已經了解的 - 他們不打算看你的數據庫代碼如何工作。

所以請把這整篇文章看做一個瘋子的叫喊,關于他為自己建立了充滿細節(jié)又瘋狂的世界 - 并把自己鎖在了里面。

有點難過的搖頭,并且請享受這個話題下那些超贊的 Core Data 的文章吧。

而對我來說,接下來在研究完 Gus 指出的自定義 SQLite 方法特性后,我會研究 SQLite 的 全文搜索擴展。 總有更多的內容需要不斷去學習。

上一篇:測試下一篇:字符串解析