ios 資料持久化儲存
說到資料儲存,我們不得不先了解下蘋果的沙盒 、如何獲取沙盒路徑和沙盒目錄下對應的檔案:
一、沙盒(sandbox)
-
每一個App都有一個儲存空間。iOS系統為每個應用程式建立自己的目錄,每個應用程式只能訪問自己的目錄,不能相互通訊。
沙盒主要包括下面幾個檔案:用模擬器執行 NSLog(@"%@",NSHomeDirectory()); 列印路徑;
進入該路徑下回看到四個檔案 Documents , Libraby , SystemData , tmp四個檔案
-
各個檔案的用途 :
-
1、Documents
獲取路徑NSString *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
儲存持久化資料,會備份。一般用來儲存需要持久化的資料。
一般我們在專案中,我們會吧一些使用者的登入資訊進行儲存,以及搜尋歷史記錄等等一些關鍵資料。2、Library 下面有兩個檔案 Caches 和 Preferences
Caches: iTunes不會同步此資料夾,適合儲存體積大,不需要備份的非重要資料。
獲取路徑NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
Preferences: iTunes同步該應用時會同步此資料夾中的內容,通常儲存應用的設定資訊。NSUserDefaults存放
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
3、tmp
iTunes不會同步此資料夾,系統可能在應用沒執行時就刪除該目錄下的檔案,所以此目錄適合儲存應用中的一些臨時檔案,用完就刪除。
獲取路徑NSString *path = NSTemporaryDirectory();
4、SystemData 這個是新加入的資料夾
二、 下面看下資料儲存 包含: Preference 、檔案儲存、歸檔、資料庫 、 CoreData
1、Preference(偏好設定): NSUserDefaults
可以儲存 字典 陣列 字元等系統自帶的資料型別,自定義的物件無法儲存,預設存放字 Library/Preferences 下 具體程式碼實現
- (void)setDefault {
//可以儲存 字典 陣列 字元等系統自帶的資料型別,自定義的物件無法儲存
NSUserDefaults * def = [NSUserDefaults standardUserDefaults];
[def setObject:@"aaaaaa" forKey:@"DEFAULT"];
[def synchronize];
NSLog(@"default ---- %@",[def objectForKey:@"DEFAULT"]);
}
- (void)delDefault {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:@"DEFAULT"];
NSLog(@"清空了default ---- %@",[[NSUserDefaults standardUserDefaults] objectForKey:@"DEFAULT"]);
}
2 、檔案儲存
儲存系統自帶的資料型別,一般實際開發中儲存字典、陣列,自定義的模型無法進行儲存
程式碼實習
#define LvPath [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"lvData.plist"]
#define LPath [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"lData.plist"]
#define LhPath [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"lhData.plist"]
- (void)createFile{
//檔案
NSDictionary *dic = @{@"A":@"123"};
NSArray * arr = @[@"Q",@"123"];
NSString * string = @"aaaaaaaa";
[dic writeToFile:LvPath atomically:YES];
[arr writeToFile:LPath atomically:YES];
[string writeToFile:LhPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
UIButton * fileBtn = [UIButton buttonWithType:UIButtonTypeCustom];
fileBtn.frame = CGRectMake(10, 180, 80, 80);
[fileBtn setTitle:@"file" forState:UIControlStateNormal];
fileBtn.backgroundColor = [UIColor redColor];
[fileBtn addTarget:self action:@selector(setFile) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:fileBtn];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)setFile {
NSArray * arr = [NSArray arrayWithContentsOfFile:LPath];
NSDictionary * dic = [NSDictionary dictionaryWithContentsOfFile:LvPath];
NSString * string = [NSString stringWithContentsOfFile:LhPath encoding:NSUTF8StringEncoding error:nil];
NSLog(@"file :arr--%@ dict---%@ string--%@",arr,dic,string);
}
3、歸檔(又名序列化)
歸檔是把物件轉為位元組碼,以檔案的形式儲存到磁碟上,程式執行過程中或者再次重新開啟程式的時候,可以通過解歸檔(返序列化)還原這些物件。
歸檔的物件是Foundation框架中的物件
歸檔和解歸檔其中任意物件都需要歸檔和解歸檔整個檔案
歸檔後的檔案是加密的,所以歸檔檔案的副檔名可以隨意取
在帶鍵的歸檔中,每個歸檔都有一個key值,解歸檔時key值要與歸檔時key值匹配
程式碼實習 下面這個方法是解除安裝app ,再次裝值也是還是之前的
LHKeyChain.m 檔案
#import "LHKeyChain.h"
@implementation LHKeyChain
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(id)kSecClassGenericPassword,(id)kSecClass,
service, (id)kSecAttrService,
service, (id)kSecAttrAccount,
(id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,
nil];
}
+ (void)save:(NSString *)service data:(id)data {
//Get search dictionary
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Delete old item before add new item
SecItemDelete((CFDictionaryRef)keychainQuery);
//Add new object to search dictionary(Attention:the data format)
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
//Add item to keychain with the search dictionary
SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
}
+ (id)load:(NSString *)service {
id ret = nil;
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Configure the search setting
//Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
[keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
@try {
ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
} @catch (NSException *e) {
NSLog(@"Unarchive of %@ failed: %@", service, e);
} @finally {
}
}
if (keyData)
CFRelease(keyData);
return ret;
}
+ (void)deleteKeyData:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
SecItemDelete((CFDictionaryRef)keychainQuery);
}
LHKeyChain.h
#import <Foundation/Foundation.h>
@interface LHKeyChain : NSObject
+ (void)save:(NSString *)service data:(id)data;
+ (id)load:(NSString *)service;
+ (void)deleteKeyData:(NSString *)service;
@end
呼叫 //歸檔
- (void)setArchiver {
NSString *IDFV = [LHKeyChain load:@"IDFV"];
if ([IDFV isEqualToString:@""] || !IDFV) {
IDFV = [UIDevice currentDevice].identifierForVendor.UUIDString;
[LHKeyChain save:@"IDFV" data:IDFV];
}
NSLog(@"archiver---%@",[LHKeyChain load:@"IDFV"]);
[LHKeyChain deleteKeyData:@"IDFV"];
NSLog(@"archiver---%@",[LHKeyChain load:@"IDFV"]);
}
4 、資料庫
SQLite資料庫的幾個特點:
1、基於C語言開發的輕型資料庫
2、在iOS中需要使用C語言語法進行資料庫操作、訪問(無法使用ObjC直接訪問,因為libqlite3框架基於C語言編寫)
3、SQLite中採用的是動態資料型別,即使建立時定義了一種型別,在實際操作時也可以儲存其他型別,但是推薦建庫時使用合適的型別
4、建立連線後通常不需要關閉連線
SQLite 使用:
1、在iOS中使用SQLite3,首先要新增庫檔案libsqlite3.dylib和匯入主標頭檔案。如圖
2、匯入標頭檔案,可以使用庫中的函式
#import <sqlite3.h>
3、正好現在趁著資料持久化,講下資料庫增、刪、改、查;有一個很好的第三方庫 FMDB,這裡不具體講,詳細使用可以點選瞭解詳情 demo
1)建立並開啟資料庫
使用 sqlite3_open(<#const char *filename#>, <#sqlite3 **ppDb#>)函式的一些說明:把一個檔名稱傳遞給他,它會自動檢測這個檔案是否存在,如果不存在的話,會自動建立相應的檔案;
- 引數說明它的第一個引數為檔案的名稱(需轉換為C語言的),第二個引數是資料庫的例項,sqlite3 *db;
- 說明:sqlite3是一種型別,db是資料庫的控制代碼,就是資料庫的象徵,如果要進行增刪改查,就得操作db這個例項。
- 返回值:它的返回值為int型的,根據函式的返回值可以知道,開啟資料庫檔案是成功還是失敗,如果返回值是SQLITE_OK則說明成功,否則為失敗。
程式碼實現:
//資料庫
- (void)createSqlite {
//db是資料庫的縮寫
sqlite3 * db;
//這裡面定義一個數據庫存放路徑,並獲取到
NSString * path = [NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, YES) lastObject];
NSString * dbPath = [path stringByAppendingString:@"demo.sqlite"];
//因為sqlite是c語言 所以下面需要將OC字串轉換為c語言的字串
const char * cdbPath = dbPath.UTF8String;
//下面開啟資料庫 (如果資料庫存在的話,會直接開啟,反之資料庫不存在的話,會自動建立資料庫文檔案)
int result = sqlite3_open(cdbPath, &db);
if (result == SQLITE_OK) {
NSLog(@"成功開啟資料庫");
} else {
NSLog(@"資料庫開啟失敗");
}
//資料庫 現在有了,接下來建立表
}
執行 檢視結果 :
沙盒中已經存在 如圖
資料庫有了 ,下面就該建立表了
2)語句 sqlite3_exec(<#sqlite3 *#>, <#const char *sql#>, <#int (*callback)(void *, int, char **, char **)#>, <#void *#>, <#char **errmsg#>)
- 引數:第一個引數為資料庫的控制代碼(db),第二個引數為sql語句,第三個引數為回撥引數,是一個指向函式的指標,如果把callback前面的*改成^則就是一個block程式碼段,第四個引數可以寫NULL,第五個引數為錯誤資訊,用以程式碼除錯。
程式碼實現
//db是資料庫的縮寫
sqlite3 * db;
//這裡面定義一個數據庫存放路徑,並獲取到
NSString *document = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString * dbPath = [document stringByAppendingPathComponent:@"demo.sqlite"];
//因為sqlite是c語言 所以下面需要將OC字串轉換為c語言的字串
const char * cdbPath = dbPath.UTF8String;
//下面開啟資料庫 (如果資料庫存在的話,會直接開啟,反之資料庫不存在的話,會自動建立資料庫文檔案)
int result = sqlite3_open(cdbPath, &db);
if (result == SQLITE_OK) {
NSLog(@"成功開啟資料庫");
//資料庫 現在有了,接下來建立表
const char * sql = "create table if not exists t_demo (id integer PRIMARY KEY AUTOINCREMENT,title text not null, content text not null)";
char * errorMsg = NULL;
result = sqlite3_exec(db, sql, NULL, NULL, &errorMsg);
if (result == SQLITE_OK) {
NSLog(@"表建立成功");
} else {
NSLog(@"表建立失敗 %s",errorMsg);
}
} else {
NSLog(@"資料庫開啟失敗");
}
執行結果
開啟資料庫 檢視下成果:
現在表也有了 ,該對錶進行操作了
3)插入資料
程式碼實現
//1.拼接SQL語句
NSString * title = [NSString stringWithFormat:@"title"];
NSString * contet = [NSString stringWithFormat:@"content"];
NSString *sql=[NSString stringWithFormat:@"INSERT INTO t_demo (title,content) VALUES ('%@','%@');",title,contet];
//2.執行SQL語句
char *errmsg=NULL;
sqlite3_exec(db, sql.UTF8String, NULL, NULL, &errmsg);
if (errmsg) {//如果有錯誤資訊
NSLog(@"插入資料失敗--%s",errmsg);
}else
{
NSLog(@"插入資料成功----%@",title);
}
執行結果
檢視資料庫 如圖
4) 下面看下修改資料
程式碼實現
//1.拼接SQL語句
NSString * title = [NSString stringWithFormat:@"title--12"];
NSString * contet = [NSString stringWithFormat:@"content--13"];
NSString *sql=[NSString stringWithFormat:@"UPDATE t_demo set title = '%@',content = '%@' where id = 2;",title,contet];
//2.執行SQL語句
char *errmsg=NULL;
sqlite3_exec(db, sql.UTF8String, NULL, NULL, &errmsg);
if (errmsg) {//如果有錯誤資訊
NSLog(@"更新資料失敗--%s",errmsg);
}else
{
NSLog(@"更新資料成功----%@",title);
}
執行結果
5) 查詢資料
語句:sqlite3_prepare_v2(<#sqlite3 *db#>, <#const char *zSql#>, <#int nByte#>, <#sqlite3_stmt **ppStmt#>, <#const char **pzTail#>)
- 引數:第一個引數為資料庫的控制代碼,第二個引數為sql語句,第三個引數為sql的長度(如果設定為-1,則代表系統會自動計算sql語句的長度),第四個引數用來取資料,第五個引數為尾部一般用不上可直接寫NULL。
程式碼實現
const char *sql="SELECT id,title,content FROM t_demo;";
sqlite3_stmt *stmt=NULL;
//進行查詢前的準備工作
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL)==SQLITE_OK) {//SQL語句沒有問題
NSLog(@"查詢語句沒有問題");
//每呼叫一次sqlite3_step函式,stmt就會指向下一條記錄
while (sqlite3_step(stmt)==SQLITE_ROW) {//找到一條記錄
//取出資料
//(1)取出第0列欄位的值(int型別的值)
int ID = sqlite3_column_int(stmt, 0);
//(2)取出第1列欄位的值(text型別的值)
const unsigned char * title = sqlite3_column_text(stmt, 1);
//(3)取出第2列欄位的值(int型別的值)
const unsigned char * content = sqlite3_column_text(stmt, 2);
// NSLog(@"%d %s %d",ID,name,age);
printf("%d %s %s\n",ID,title,content);
}
}else
{
NSLog(@"查詢語句有問題");
}
執行結果
6) 刪除語句
程式碼實現
//1.拼接SQL語句
NSString *sql=[NSString stringWithFormat:@"DELETE from t_demo where id = 2;"];
//2.執行SQL語句
char *errmsg=NULL;
sqlite3_exec(db, sql.UTF8String, NULL, NULL, &errmsg);
if (errmsg) {//如果有錯誤資訊
NSLog(@"刪除資料失敗--%s",errmsg);
}else
{
NSLog(@"刪除資料成功");
}
執行結果 ,檢視刪除成功了
5、CoreData
Core Data是iOS5之後才出現的一個框架,它提供了物件-關係對映(ORM)的功能,即能夠將OC物件轉化成資料,儲存在SQLite資料庫檔案中,也可以使用其他方式,比如:資料庫檔案,XML,二進位制檔案,記憶體等。CoreData 提供了 物件-關係對映(ORM) 功能,能夠將儲存在資料庫中的資料還原成OC物件。在此資料操作期間,我們不需要編寫任何SQL語句;
本文 只講簡單的使用 下篇文字將學習CoreData具體的知識
程式碼實現 :
LHModel 類:
LHModel.h
#import <CoreData/CoreData.h>
@interface LHModel : NSManagedObject
@property (nonatomic, copy) NSString * title;
@property (nonatomic, copy) NSString * content;
@end
LJModel.m
#import "LHModel.h"
@implementation LHModel
@synthesize title;
@synthesize content;
@end
CoreDataViewController類 這個是我自己定義的類
CoreDataViewController.m
#import "CoreDataViewController.h"
#import <CoreData/CoreData.h>
#import "LHModel.h"
@interface CoreDataViewController ()
/**
* 上下文 容器
* 存放的是 所有從資料庫中取出的轉換成OC物件
*/
@property (strong, nonatomic) NSManagedObjectContext * managedObjectContext;
/* 讀取解析 .momd檔案中的內容 */
@property (strong, nonatomic) NSManagedObjectModel * managedObjectModel;
/* 連線的類,處理資料庫資料和OC資料底層的相互轉換 */
@property (strong, nonatomic) NSPersistentStoreCoordinator * persistentStoreCoordinator;
@end
@implementation CoreDataViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
NSLog(@"%@",self.managedObjectContext);
//插入一條資料 (往LHModel表中插入一條資料)
//NSEntityDescription 實體類
//EntityForName 實體名稱(表名)
LHModel * model = [NSEntityDescription insertNewObjectForEntityForName:@"LHModel1" inManagedObjectContext:self.managedObjectContext];
//賦值
model.title = @"快取1";
model.content = @"快取1內容";
//同步操作 把context中的資料同步到資料庫中
[self saveContext];
// 查詢資料
// 建立查詢請求
NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"LHModel1"];
// Context 執行請求(執行查詢操作) 陣列中存放的是oc類的物件(People類的物件)
NSArray * array = [self.managedObjectContext executeFetchRequest:request error:nil];
for (LHModel *lhModel in array)
{
NSLog(@"%@",lhModel.title);
}
//查詢特定條件資料
NSFetchRequest * request1 = [NSFetchRequest fetchRequestWithEntityName:@"LHModel1"];
//使用謂詞指定查詢的判定條件
NSString * title = @"快取1";
// NSString *predStr = [NSString stringWithFormat:@"%@ AND (%@ CONTAINS \"%@\")", kPredicateStr_MovieItem_MoviesInCatalog, titleForSearch, title];
NSPredicate * predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:@"SELF.title == '%@'",title]];
//關聯判定條件
[request1 setPredicate:predicate];
//執行查詢操作
NSArray * array2 = [self.managedObjectContext executeFetchRequest:request1 error:nil];
for (LHModel * lhModel in array2)
{
NSLog(@"%@",lhModel.title);
}
//更改資料
//獲取出要修改的資料
LHModel * lhModel = [array lastObject];
//修改屬性
lhModel.title = @"快取2";
lhModel.content = @"快取2內容";
//同步資料
[self saveContext];
//刪除資料
LHModel * lhModel1 = [array lastObject];
[self.managedObjectContext deleteObject:lhModel1];
//同步資料
[self saveContext];
}
//managedObjectModel 屬性的getter方法
- (NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel != nil) return _managedObjectModel;
//.xcdatamodeld檔案 編譯之後變成.momd檔案 (.mom檔案)
NSURL * modelURL = [[NSBundle mainBundle] URLForResource:@"MemoryData" withExtension:@"momd"];
//把檔案的內容讀取到managedObjectModel中
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
//Coordinator 排程者負責資料庫的操作 建立資料庫 開啟資料 增刪改查資料
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) return _persistentStoreCoordinator;
// 設定資料庫存放的路徑
NSURL * storeURL = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:@"MemoryData.sqlite"];
//根據model建立了persistentStoreCoordinator
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSError * error = nil;
//如果沒有得到資料庫
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
{
NSLog(@"錯誤資訊: %@, %@", error, [error userInfo]);
}
return _persistentStoreCoordinator;
}
//容器類 存放OC的物件
-(NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) return _managedObjectContext;
NSPersistentStoreCoordinator * coordinator = [self persistentStoreCoordinator];
if (!coordinator)
{
return nil;
}
/* 建立context物件 NSManagedObjectContext 例項提供一個執行緒。我們需要將這個 init 方法替換成 -initWithConcurrency: 方法。這個方法配置了 NSManagedObjectContext 例項化所在的執行緒。
這就意味著我們要確定在哪個執行緒上例項化我們的 NSManagedObjectContext ,主執行緒,還是另外建立一個後臺執行緒。我們可以選擇的引數有:
NSPrivateQueueConcurrencyType
NSMainQueueConcurrencyType
在這裡,我把它配置成在主執行緒上進行例項化(一般選擇主執行緒就可以)*/
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
//讓context和coordinator關聯 context可以對資料進行增刪改查功能
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;
}
#pragma mark - Core Data Saving support
-(void)saveContext
{
NSManagedObjectContext * managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil)
{
NSError * error = nil;
// hasChanges 判斷資料是否更改
// sava 同步資料庫中的資料
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
NSLog(@"錯誤資訊: %@, %@", error, [error userInfo]);
}
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
執行結果 :
希望大家一起學習: 附有demo
csdn demo下載地址
https://download.csdn.net/download/u013983033/10745909