1. 程式人生 > >ios 資料持久化儲存

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

github demo 下載地址
https://github.com/lvhome/DataMemory