數據持久化方案(如果總結不到位,或者有誤的地方,敬請斧正)
一、功能:
主要是將數據持久化到本地,減少對網絡請求的次數,既節省了用戶的流量,也增強了App的體驗效果。
二、種類:
plist存儲:使用XML鍵值對持久化,它適用於少量且數據基本不怎么改變的情況。
偏好存儲:使用NSUserDefalut持久化,專門用來保存應用程序的配置信息的,一般不要在偏好設置中保存其他數據。
歸檔序列化存儲:使用二進制序列化持久化,只要遵循了NSCoding協議的對象都可以通過它實現序列化。
沙盒存儲:持久化在Document目錄下,一般持久化一些文件,比如圖片,音頻,視頻等,文件沙盒存儲主要存儲非機密數據。
本地數據庫存儲:適合儲存大規模數據,管理方便,不過操作稍微復雜一些。
三、詳解:
1、plist存儲
定義:
plist文件,即屬性列表文件,全名是Property List,這種文件的擴展名為.plist,因此,通常被叫做plist文件。
作用:
它是一種用來存儲串行化后的對象的文件,在iOS開發中通常用來存儲用戶設置,還可以用於存儲程序中經常用到而不經常改動的數據。
問題:
(1)什么數據適合存儲?
能存儲NSString、NSArray、NSDictionary、NSData、NSDate、NSNumber、Boolean不能存儲自定義對象
(2)存到什么地方?
寫入創建的.plist文件中
(3)使用場景?
plist常用於存儲長時間不容易發生變化的數據,例如省市列表、車輛名稱列表之類的數據等,這些數據可以保存在 plist 文件里,所以plist適用於存儲小型數據,不推薦用plist做緩沖。
(4)如何使用?
存儲
[dict writeToFile:filePath atomically:YES]; // 字典 [array writeToFile:filePath atomically:YES]; // 數組 [string writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; // 字符串
獲取
NSArray *arr = [NSArray arrayWithContentsOfFile:filePath]; // 數組 NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath]; // 字典 NSString *string = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; // 字符串
(5)有什么缺點?
因為所有的數據都放在root dictionary里,每次讀取都要把整個root dictionary取出來再取需要的對象.如果plist文件緩存了幾十M的數據.這樣很費內存和時間。
2、偏好存儲
定義:
User Defaults 顧名思義就是一個用戶為系統以及程序設置的默認值。
每個用戶都有自己的一套數據,用戶和用戶之間沒法共享的。在蘋果的API中,提供了一個類去存儲用戶的偏好設置。
這個方法推薦只存儲用戶的偏好設置,不要存儲一些字典、數組之類的。
作用:
很多iOS應用都支持偏好設置,比如保存用戶名、密碼、字體大小等設置。
iOS提供了一套標准的解決方案來為應用加入偏好設置功能,就是每一個app都有一個plist文件專門用以保存偏好設置數據。
每個應用都有個NSUserDefaults實例,通過它來存取偏好設置。
問題:
(1)什么數據適合存儲?
可以存儲OC定義的所有數據類型,包括對象(系統和自定義的)類型、基本數據類型,如NSInteger等。
(2)存到什么地方?
NSUserDefault 本地保存的位置是Library/Preferences 這個目錄下的 plist 文件。
(3)使用場景?
在App中,有時候我們需要將一些信息進行短期的保存,方便用戶下次更方便使用App,減少多余的操作,增強用戶體驗。
比如,保存用戶名、字體大小、是否自動登錄等。
(4)如何使用?
存儲
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults]; [defaults setObject: forKey:];
[defaults setInteger:forKey:];
[defaults setDouble: forKey:];
[defaults setObject: forKey:];
[defaults synchronize];
獲取
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults objectForKey:];
[defaults objectForKey:];
[defaults integerForKey:];
[defaults doubleForKey:];
(5)注意事項?
- 偏好設置是專門用來保存應用程序的配置信息的, 一般情況不要在偏好設置中保存其他數據。
- 如果利用系統的偏好設置來存儲數據, 默認就是存儲在Preferences文件夾下面的,偏好設置會將所有的數據都保存到同一個文件中。
- 使用偏好設置對數據進行保存之后, 它保存到系統的時間是不確定的,會在將來某一時間點自動將數據保存到Preferences文件夾下面,如果需要即刻將數據存儲,可以使用[defaults synchronize];
- 所有的信息都寫在一個文件中,對比簡單的plist可以保存和讀取基本的數據類型。
3、歸檔序列化存儲
定義:
對象歸檔是iOS中數據持久化的一種方式。 歸檔是指另一種形式的二進制序列化,但它是任何對象都可以實現的更常規的類型。
作用:
使用對模型對象進行歸檔的技術可以輕松將復雜的對象寫入文件,然后再從中讀取它們。
問題:
(1)什么數據適合存儲?
要使用對象歸檔,則歸檔的對象所屬類中實現的每個屬性都是標量,或者都是遵循NSCoding協議和NSCopying協議的某個類的實例對象。
(2)存到什么地方?
對象歸檔后將得到一個后綴為.archive的文件,數據就保存在了這個文件中。
(3)使用場景?
定義某個實例,如果需要持久化該實例從而方便以后使用它的屬性值,同時可以隨意更改該實例的屬性值,推薦在給實例初始化的同時直接使用歸檔進行存儲。
(4)如何使用?
遵循NSCoding協議
- NSCoding中聲明了兩個方法,其中一個用於將對象編碼到歸檔中,另一個方法對歸檔解碼來創建一個新對象。
- 歸檔時要實現的方法為:-(void)encodeWithCoder:(NSCoder *)aCoder;
- 可以使用KVC(Key-Value Coding,鍵值編碼)對對象和原生數據類型進行編碼和解碼。
- 若要對對象進行歸檔,必須使用正確的編碼方法將所有實例變量編碼成encoder。
- 在解碼時,實現一個通過NSCoder解碼的對象初始化方法,就可以恢復之前歸檔的對象。
- 解碼時要實現的方法為:-(id)initWithCoder:(NSCoder *)aDecode;
實現NSCopying協議
- 除了要遵循NSCoding協議外,還要求要使用歸檔的類實現NSCoping協議。 用來復制對象。
- 這個協議中有一個copywithZone方法:- (id)copyWithZone:(NSZone *)zone;
- 其實現與inetWitheCoder非常相似,只需創建一個同一類的新實例,然后將新實例的所有屬性都設置為與該對象屬性相同的值。
存儲
NSMutableData *data = [[NSMutableData alloc] init]; //聲明一個二進制流 data,開辟了一個空間 NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; //聲明一個歸檔類,把歸檔類的內容放入data中
[archiver encodeObject:id forKey:Key]; //用Key進行編碼 [archiver finishEncoding]; //結束編碼 [data writeToFile:filePath atomically:YES];//編碼結束后,歸檔類的內容已經放入data中了,此時data仍然駐留在內存中,需要寫入文件中
獲取
NSData *data = [[NSMutableData alloc] initWithContentsOfFile:filePath];//用歸檔文件中的數據初始化 NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];//聲明一個解歸檔對象,把data中的數據復制給解歸檔對象 id object = [unarchiver decodeObjectForKey:Key]; //用Key進行解碼 [unarchiver finishDecoding]; //結束解碼
(5)有什么缺點?
當待存儲的實例具有成百上千個屬性的時候,單純的一個個去序列化屬性值耗時又費力。(當然可以借助runtime機制解決這個缺點,MJExtension這個框架就是這個原理)
4、Document沙盒存儲
定義:
每個iOS應用都有自己的應用沙盒(應用沙盒就是文件系統目錄),與其他文件系統隔離。
應用必須待在自己的沙盒里,其他應用不能訪問該沙盒。
沙盒的本質就是一個文件夾,名字是隨機分配的。
目錄:
- Application:存放程序源文件,上架前經過數字簽名,上架后不可修改。
- Documents: 保存應⽤運行時生成的需要持久化的數據,iTunes同步設備時會備份該目錄。例如,游戲應用可將游戲存檔保存在該目錄。
- tmp: 保存應⽤運行時所需的臨時數據,使⽤完畢后再將相應的文件從該目錄刪除。應用 沒有運行時,系統也可能會清除該目錄下的文件。iTunes同步設備時不會備份該目錄。
- Library/Caches: 保存應用運行時⽣成的需要持久化的數據,iTunes同步設備時不會備份 該目錄。⼀一般存儲體積大、不需要備份的非重要數據,比如網絡數據緩存存儲到Caches下
- Library/Preference: 保存應用的所有偏好設置,如iOS的Settings(設置) 應⽤會在該目錄中查找應⽤的設置信息。iTunes同步設備時會備份該目錄。
// 獲取程序的Home目錄 NSString *path = NSHomeDirectory(); // 獲取Document目錄 NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) fristObject]; // 獲取Cache目錄 NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) fristObject]; // 獲取Library目錄 NSString *path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) fristObject]; // 獲取Tmp目錄 NSString *path = NSTemporaryDirectory();
作用:
用來存儲和備份稍微較大的不是很重要的數據,比如緩存圖片、音頻、視頻等,最典型的SDWebImage緩存圖片的框架。
當然緩存的時間長短根據開發者選擇持久化的目錄路徑有關。
問題:
(1)什么數據適合存儲?
圖片、音頻、視頻、文本等
(2)存到什么地方?
寫入創建的.txt、.data等任意擴展名的文件中
(3)使用場景?
當App中涉及到電子書閱讀、聽音樂、看視頻、刷圖片列表等時,推薦使用沙盒存儲。
因為這可以極大的節約用戶流量,而且也增強了app的體驗效果。
(4)如何使用?
寫入文件
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) fristObject]; NSArray *array = [[NSArray alloc] initWithObjects:@"內容",@"content",nil]; NSString *filePath = [path stringByAppendingPathComponent:@"testFile.txt"]; [array writeToFile:filePath atomically:YES];
讀取文件
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) fristObject]; NSString *filePath = [path stringByAppendingPathComponent:@"testFile.txt"];
NSArray *array = [[NSArray alloc] initWithContentsOfFile:filePath];
5、本地數據庫存儲
定義:
數據庫是一種常用的通過表進行數據存儲的方式,表與表之間可以有一對一、一對多的聯系,使用外鍵級聯可以達到多表存取數據的目的。
在iOS中,目前有三種熟知數據庫,分別是Sqlite、CoreData、FMDB,用的比較多的是FMDB+Sqlite結合的方式。
特點:
Sqlite:
- 是基於c語言開發的數據庫,代碼繁瑣。
- 用c語言對數據庫執行操作,訪問。
- sqlite是動態的數據庫類型,即存儲的時候是一種類型,使用的時候可以存儲為其他類型。
- 在OC中不是可視化,不易理解。
- 建立連接之后可以不關閉連接。
CoreData;
- 可視化,且具有undo/redo能力。
- 可以實現多種文件格式: * NSSQLiteStoreType 、 * NSBinaryStoreType 、* NSInMemoryStoreType 、* NSXMLStoreTyp。
- 蘋果官方API支持,與iOS結合更緊密。
FMDB;
- FMDB以面向OC的方式封裝了SQLite的C語言API。
- 使用起來更加面向對象,省去了很多麻煩、冗余的C語言代碼。
- 對比蘋果自帶的Core Data框架,更加輕量級和靈活。
- 提供了多線程安全的數據庫操作方法,有效地防止數據混亂。
作用:
用來進行大數據量的存儲工作,不僅僅是容量大,而且通過索引查找速度很快,在App中這個是基本的功能。
問題:
(1)什么數據適合存儲?
開發者定義的類的實例對象,該對象擁有的屬性可以是任何類型,例如數字、 字符、 日期、 圖片等等。
(2)存到什么地方?
寫入創建的.sqlite、.db等任意擴展名的文件中
(3)使用場景?
在App中有大量的數據在沒有網絡的情況下仍然需要顯示時,此時推薦使用本地數據庫緩存這些數據。
(4)如何使用?
Sqlite:
- 新建項目時,先導入系統框架(C語言). (libsqlite3)
- 頭文件#import<sqlite3.h>
- sqlite3_open(fileName.UTF8String, &_db); //打開或者創建一個數據 ,*_db自己定義一個sqlite3的成員變量.進行增刪改查時要用
- sqlite3_exec(_db, sql, NULL, NULL,&error); //不帶結果集的語句,只是對表做操作,不會返回出結果,*該函數可進行insert,delete,update操作.
- sqlite3_prepare_v2(_db, sql, -1, &stmt, NULL); //做查詢前准備,檢測SQL語句是否正確.(查詢操作select. 帶結果集的查詢語句,會返回出結果,從表中查詢到的數據都會放到stmt結構體中)
- sqlite3_step(stmt) //提取查詢到的數據,一次提取一條,通過循環可以取出所有數據
- sqlite3_column_text(stmt, 0) //取出第0列的數據.
- sqlite3_close(sqlite3 *); //關閉數據庫
源碼
NSString *sqlStr = @"INSERT OR REPLACE INTO note (cdate,content) VALUES (?,?)"; sqlite3_stmt *statement; //預處理過程,產生結果集 if (sqlite3_prepare_v2(db, [sqlStr UTF8String], -1, &statement, NULL) == SQLITE_OK) { NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; NSString *nsdate = [dateFormatter stringFromDate:model.date]; //綁定參數開始 sqlite3_bind_text(statement, 1, [nsdate UTF8String], -1, NULL); sqlite3_bind_text(statement, 2, [model.content UTF8String], -1, NULL); //執行插入 if (sqlite3_step(statement) != SQLITE_DONE) { NSAssert(NO, @"插入數據失敗。"); } } } //清理結果集,防止內存泄露 sqlite3_finalize(statement);
CoreData;
- 創建項目時,勾選CoreData選項。
- 此時項目文件中多了一個CoreData___.xcdatamodel文件,選中該文件,進入其創建數據庫表格界面,在界面的左下角點擊Add Entity添加實體對象,並設置該對象的類名;與此同時,在AppDeletegate類中,自動聲明和定義了需要的三個對象Managed Object Model,Persistent Store Coordinator,Managed Object Context ,並且自動封裝了大量的sqlite的函數方法。
- 在attributes選項處添加該實體對象的屬性。
- 選中該實體類,在模擬器選項上點擊Editor下的create Managed Object Context subclass..創建Managed Object Context的子類。
- 這個子類中,編譯器自動生成了所添加的所有屬性。
- 在應用程序代理類中用代碼對數據庫進行操作。
源碼
//獲取實體對象 NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:@“ClassName” inManagedObjectContext:self.managedObjectContext]; //創建請求對象 NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@“ClassName”]; //創建排序對象 NSSortDescriptor *ageSort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES] [request setSortDescriptors:@[ageSort]]; //取出所有的數據 NSArray *fetcheObjects = [self.managedObjectContext executeFetchRequest:request error:&error]
FMDB:
- 創建或打開
self.db = [FMDatabase databaseWithPath:fileName]; //創建數據庫
[self.db open];//打開數據庫
- 查詢語句:
- (FMResultSet *)executeQuery:(NSString*)sql, ... //返回結果集
- (BOOL)next; //遍歷
- { type }ForColumnIndex:(int)columnIdx //取出某一行對應的具體數據
- 創建、插入、修改等等語句:
- (BOOL)executeUpdate:(NSString*)sql, ... //執行更新
- 關閉數據庫
[self.db close]; //關閉數據庫
源碼
//<1>使用:(需要FMDatabase *db成員變量) //創建或打開:FMDataBase類 self.db = [FMDatabase databaseWithPath:fileName]; [self.db open]; [self.db executeUpdate:@“CREATE TABLE IF NOT EXISTS t_student (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name TEXT,age INTEGER)”]; [self.db executeUpdate:@"INSERT INTO t_student(name , age) VALUES(‘admin’,‘10')]; //<2>查詢:FMResultSet類 //1.查詢 FMResultSet *set = [self.db executeQuery:@"SELECT * FROM t_student;"]; //2.取出數據 即 {type}ForColumnIndex: while ([set next]) { //取出姓名 NSString *name = [set stringForColumnIndex:1]; //取出年齡 int age = [set intForColumnIndex:2]; NSString *name = [set stringForColumn:@"name"]; int age = [set intForColumn:@"age"]; NSLog(@"name = %@, age = %d", name, age); } //<3>關閉數據庫 [self.db close];
四、選擇:
既然有這五種存儲方案,那么在項目中應該選擇哪種是最佳的方式呢,這就涉及到了下面這個問題了。
- 什么時候用?通俗的說,也就是針對某種業務,這五個存儲方式的最佳選擇。
針對上面的這個問題,基本是可以有四種參考的維度,分別是:
- 數據量
- 數據類型
- 數據載體的形式
- 數據操作的特點
現在就具體的列表說一下這些區別。
|
存儲方式
|
數據量
|
數據類型
|
數據載體
|
數據操作特點
|
應用示例
|
|---|---|---|---|---|---|
| plist存儲 | 適合存儲小數據量而且屬於一類的列表類的數據 | 只能存儲固定的幾種類型,NSString、NSArray、NSDictionary、NSData、NSDate、NSNumber、Boolean,不能存儲自定義對象 | 非自定義實例對象、基本數據 | 直接在可見文件上操作,增刪改查很方便 |
省市列表、表情列表等 |
| 偏好存儲 | 適合存儲小數據量而是一般是配置信息類的數據,有時根據需要也會存儲一些標志鍵值數據 | 可以存儲OC定義的所有數據類型 |
實例對象、基本數據 |
必須依賴NSUserDefaluts實例對象的實例方法進行存取,過程稍微復雜 |
App應用程序的信息配置,如版本號、app名稱、用戶權限等 標志鍵值,如判斷啟動頁、是否自動登錄等 |
| 歸檔存儲 | 適合存儲數據量稍微較大的數據 | 只能存儲實現了NSCoding協議和NSCopying協議的實例對象類型。 | 實例對象 | 必須依賴NSKeyedUnarchiver、NSKeyedArchiver的類方法或者實例方法進行存取,有時可能還會結合NSUserDefaluts偏好,過程比較復雜 | 用戶的登錄/注冊信息,如賬號、姓名、年齡、學校、省份等 |
| 沙盒存儲 | 適合存儲數據量較大的數據 | 都是存儲二進制的NSdata類型 | 文件File | 需要依賴文件管理者NSFileManager將文件寫入指定的沙盒目錄下、從該目錄中再讀取文件,過程更復雜一些 | 圖片、音頻、視頻、文本等,如SDWebImage圖片緩存框架 |
| 數據庫存儲 | 適合存儲大數據量的數據 | 支持所有的數據類型 | 實例對象 | 增刪改查方便、快捷,可以任意寫sql語句批量處理數據、數據庫升級簡單等 | App中用戶瀏覽過的數據列表內容、電子書讀書進度等,基本上大多數app都有本地數據庫緩存 |
