iOS持久化
1.文件系統(tǒng)
不管是Mac OS X 還是iOS的文件系統(tǒng)都是建立在UNIX文件系統(tǒng)基礎(chǔ)之上的。
1.1 沙盒模型
在iOS中,一個App的讀寫權(quán)限只局限于自己的沙盒目錄中。
沙盒模型到底有哪些好處呢?
安全:別的App無法修改你的程序或數(shù)據(jù)
保護隱私:別的App無法讀取你的程序和數(shù)據(jù)
方便刪除:因為一個App所有產(chǎn)生的內(nèi)容都在自己的沙盒中,所以刪除App只需要將沙盒刪除就可以徹底刪除程序了
iOS App沙盒中的目錄
- App Bundle ,如xxx.app 其實是一個目錄,里面有app本身的二進制數(shù)據(jù)以及資源文件
- Documents, 存放程序產(chǎn)生的文檔數(shù)據(jù)
- Library , 下面默認包含下面兩個目錄 Caches Preferences
- tmp, 臨時文件目錄
如果我們想在程序中獲取上面某個目錄的路徑,應(yīng)該如何實現(xiàn)呢? 下面就講講路徑的獲取, 通過NSPathUtilities.h中的NSSearchPathForDirectoriesInDomains函數(shù),我們便可以獲取我們想要的路 徑。 此函數(shù)具體聲明如下:
NSArray *NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);
directory 目錄類型 比如Documents目錄 就是NSDocumentDirectory
domainMask 在iOS的程序中這個取NSUserDomainMask
expandTilde YES,表示將~展開成完整路徑
注意函數(shù)返回的類型為數(shù)組,在iOS中一般這個數(shù)組中只包含一個元素,所以直接取lastObject即可。
1.2 NSFileManager
NSFileManager提供一個類方法獲得一個單例。
- /* Returns the default singleton instance.*/ + (NSFileManager *)defaultManager;
下面羅列了NSFileManager的常用方法
- 新建目錄
- - (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary *)attributes error:(NSError **)error;
createIntermediates這個參數(shù)一般為YES,表示如果目錄路徑中間的某個目錄不存在則創(chuàng)建之,如果是NO的話,則要保證所創(chuàng)建目錄的父目錄都必須已經(jīng)存在
- 獲取目錄下的所有文件
- - (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error;
如果目錄為空,則返回空數(shù)組
- 其他的一些方法
- - (BOOL)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error; - (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error; - (BOOL)linkItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error; - (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error;
更多的可以查看文檔 NSFileManager Class Reference。
在實際項目中,我們一般會寫一個工具類來負責(zé)項目中所有的路徑操作。
2. 歸檔(Archives) 和 序列化(Serializations)
我們經(jīng)常聽到“序列化”,“反序列化”這樣的字眼,其實“序列化”的意思就是將對象轉(zhuǎn)換成字節(jié)流以便保存或傳輸,“反序列化”便是一個相反的過程,從字節(jié)流轉(zhuǎn)到對象。
在這節(jié)中涉及到一種文件類型plist,plist就是Property List 的縮寫,即所謂的屬性列表,屬性列表有兩種數(shù)據(jù)格式,一種是XML的,方便閱讀和編輯;另一種是二進制的,節(jié)省存儲空間,以及提高效率。
在Objective-C中這個對象和字節(jié)流的互轉(zhuǎn)分成兩類:
- 歸檔 普通自定義對象和字節(jié)流之間的轉(zhuǎn)換
- 序列化 某些特定類型(NSDictionary, NSArray, NSString, NSDate, NSNumber,NSData)的數(shù)據(jù)和字節(jié)流之間(通常將其保存為plist文件)的轉(zhuǎn)換
不過本質(zhì)上講上述兩種都是對象圖(Object Graph)和字節(jié)流之間的轉(zhuǎn)換. Apple關(guān)于序列化和歸檔的編程指南: Archives and Serializations Programming Guide 。
2.1 歸檔
如果我們需要將自定義的一個對象保存到文件,應(yīng)該如何做呢?
這里引入兩個東西:一個是NSCoding協(xié)議 ;另一個是NSKeyedArchiver,NSKeyedArchiver其實繼承于NSCoder,可以以鍵值對的方式將對象的屬性進行序列化和反序列化。
具體的過程可以這樣描述 通過NSKeyedArchiver 可以將實現(xiàn)了NSCoding協(xié)議的對象 和 字節(jié)流 相互轉(zhuǎn)換 。
像一些框架中的數(shù)據(jù)類型如NSDictionary,NSArray,NSString... 都已經(jīng)實現(xiàn)了NSCoding協(xié)議,所以可以直接對他們進行歸檔操作。
這里來一個比較完整的例子,一個Address類,一個User類,User類下有個Address類型的屬性。
Address類
User類
使用示例
通過查看文件內(nèi)容可以發(fā)現(xiàn),保存的是plist的二進制數(shù)據(jù)格式。 轉(zhuǎn)成XML可以看到如下內(nèi)容:
2.2 序列化
在實際的項目中,我們一般是將NSDictionary或NSArray的對象保存到文件或者從文件讀取成對象。 當(dāng)然這種只是適用于數(shù)據(jù)量不是很大的應(yīng)用場景。 NSDictionary和NSArray 都有一個寫入文件的方法
NSDictionary和NSArray會直接寫成plist文件。
2.2.1 序列化的方式
序列化可以通過兩種途徑來進行
使用數(shù)據(jù)對象自帶的方法
寫文件
寫完的文件內(nèi)容如下:
從文件讀取
使用NSPropertyListSerialization類
通過NSPropertyListSerialization類可以將數(shù)據(jù)對象直接轉(zhuǎn)成NSData或者直接寫到文件或者流中去.
讀取
2.2.2 User Defaults
User Defaults 顧名思義就是一個用戶為系統(tǒng)以及程序設(shè)置的默認值。每個用戶都有自己的一套數(shù)據(jù),用戶和用戶之間沒法共享的。
我們都知道每一個程序都會保存一些設(shè)置數(shù)據(jù),比如記住上次窗口的位置和大小,記住是否彈出某些提示信息等。蘋果提供了一個統(tǒng)一的解決方案,就是每一 個app都有一個plist文件專門用以保存偏好設(shè)置數(shù)據(jù)。 plist文件名默認是程序Bundle identifier,擴展名為plist.
除了程序自己的設(shè)置外,系統(tǒng)還有一些全局的或者其它的一些設(shè)置,也屬于User Defaults的范疇,User Defaults的持久化數(shù)據(jù)都保存在 ~/Library/Preferences 目錄中.
這里有一點簡要的說一下,User Defaults 中存放的key value分放在多個Domain中,取的時候按一定的次序取查找,次序如下:
- The Argument Domain 程序啟動的時候以參數(shù)的方式傳入的
- The Application Domain 通過NSUserDefaults往里面寫數(shù)據(jù)的時候默認就是寫到這個Domain的,通過Bundle identifier來標(biāo)識
- The Global Domain 用戶的全局的設(shè)置(系統(tǒng)的偏好設(shè)置)會放在這個Domain下,比如用戶的語言設(shè)置,滾動條的設(shè)置等,里面的設(shè)置會對所有的程序起作用。
- The Languages Domains
- The Registration Domain 這個domain里面的key value是提供默認值的,一般會在程序啟動的設(shè)置進行設(shè)置,他們都不會被持久化到文件的。當(dāng)某個key對應(yīng)的值在上面的那些domain中都不存在的時候,就到這里找。
Mac系統(tǒng)還為user defaults提供了很好的命令行工具,defaults 你可以通過下面的方式查看具體使用方式
可以通過defaults domains查看當(dāng)前用戶的所有的domain,通過 defaults read NSGlobalDomain 讀取 The Global Domain 中的所有值。
NSUserDefaults 類來讀寫Preferences設(shè)置,而無需考慮文件位置等細節(jié)問題。
NSUserDefaults 用起來和 NSDictionary 很相似,多了一個Domain的概念在里面。NSUserDefaults 一樣提供了一個獲取單例的方法.
NSUserDefaults提供了一系列的接口來根據(jù)key獲取對應(yīng)的value,搜索的次序按照上面提及到的次序在各個Domain中進行查找。還提供了一系列的 Setting Default Values的方法,這些設(shè)置的值都是在 The Application Domain 下的.當(dāng)然也提供了修改其他Domain下的值的方法,只是需要整體的設(shè)置。
3.數(shù)據(jù)庫
Mac上自帶安裝了SQLite3 ,如果你之前接觸過關(guān)系型數(shù)據(jù)庫,你可以通過命令行來對SQLite進行初步的認識
那如果在代碼中使用SQLite呢?
- 添加sqlite的動態(tài)鏈接庫 libsqlite3.0.dylib
- 引入頭文件 #import "sqlite3.h"
這樣之后你便可以通過C的接口來操作數(shù)據(jù)庫了
你會發(fā)現(xiàn)這完全是C語言編程,和Objective-C的代碼混在一起格格不入,也很不方便,所以便有人開發(fā)了開源的sqlite c接口的wrapper
- FMDB https://github.com/ccgus/fmdb
- EGODatabase https://github.com/enormego/egodatabase (部分代碼來自FMDB,thread safe)
具體的使用方法,各自的文檔都寫的比較清楚。 FMDB不支持多線程同時使用同一個數(shù)據(jù)庫連接進行操作,否則會有線程安全問題,有可能導(dǎo)致數(shù)據(jù)庫文件損壞。EGODatabase則引入了多線程的支 持,部分代碼借鑒了FMDB,兩者在使用上非常的相似。另EGODatabase提供了異步數(shù)據(jù)庫操作的支持,將數(shù)據(jù)庫操作封裝成數(shù)據(jù)庫請求(其繼承于 NSOperation),數(shù)據(jù)庫請求創(chuàng)建好了,丟到一個OperationQueue中被異步的進行執(zhí)行,當(dāng)請求數(shù)據(jù)完成之后 ,相應(yīng)的delegate方法會被調(diào)用,然后你可以在主線程更新顯示了.
4.CoreData