數據持久化存儲

沙盒

iOS程序默認情況下只能訪問自己的目錄,這個目錄被稱作沙盒

沙盒結構

沙盒結構主要為

Document
Library/Caches
Library/Preferences
tmp

沙盒結構圖
目錄特性
應用程序包:

存放程序的資源文件和可執行文件

NSString *path = [[NSBundle mainBundle] bundlePath];
Document:

最常用的目錄,iTunes備份會備份這個文件夾,適合保存重要的數據

NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectories,NSUserDomainMask,YES).firstObject;
Library/Caches:

iTunes不會備份這個文件夾,適合存放不需要備份的非重要的數據

NSString *path = NSSearchPathForDirectoriesInDomains(NSCacheDirectories,NSUserDomainMask,YES).firstObject;
Library/Preferences:

iTunes備份的時候會保存這個文件夾,適合存放用戶的偏好設置,程序的設置信息

tmp:

臨時文件,iTunes不會備份

NSString *path = NSTemporatyDirectory();

方案:

1、寫入plist文件
2、NSUserDefault存儲
3、歸檔
4、core data
5、sqlite

1)plist

plist通過XML的形式保存在目錄中,只能存儲以下類型的值:

NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;

獲取路徑:

NSString *documentPath = NSSearchPathForDirectotiesInDomains(NSDocumentDirectoty,NSUserDomainMask,YES).firstObject;
NSString *path = [doucumentPath stringByAppendingPathComponent:@"data.plist"];

存儲

NSArray *array = @[@"12",@"11",@"10"];
//atomically表示是否需要先寫入一個輔助文件,再把輔助文件拷貝到目標文件地址。這是更安全的寫入文件方法,一般都寫YES
[array writeToFile:path automically:YES];

讀取

NSArray *arr = [array arrayWithContentsOfFile:path];
2)NSUserDefault

用法

NSUserDefault *defualt = [NSUserDefault standUserDefault];
//存儲
[default setObjet:@"object1" forKey:@"object1"];
//存儲之后應立即同步,防止數據丟失
[default synchronize];
//讀取
NSString *object = default[@"object1"];

tip:

  • 偏好設置是專門用來保存應用程序的配置信息的,一般不要在偏好設置中保存其他數據。
  • 如果沒有調用synchronize方法,系統會根據I/O情況不定時刻地保存到文件中。所以如果需要立即寫入文件的就必須調用synchronize方法。
  • 偏好設置會將所有數據保存到同一個文件中。即preference目錄下的一個以此應用包名來命名的plist文件。
3)NSKeyedAchiever歸檔

1、遵循NSCoding協議,實現協議方法

  @interface Person : NSObject<NSCoding>   
  @property (strong, nonatomic) UIImage *avatar;
  @end

2、實現協議方法

//歸檔
-(void) encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encoderObject:self.avator forKey:@"avator"];
}
//解檔
-(id) initWithCoder:(NSCoder *)aDecoder{
      self = [super init];
      if self {
          self.avator = [aDecoder decoderObjectForKey:@"avator"];
      }
      return self;
}
注意:

如果需要歸檔的類是某個自定義類的子類時,就需要在歸檔和解檔之前先實現父類的歸檔和解檔方法。即 [super encodeWithCoder:aCoder] 和 [super initWithCoder:aDecoder] 方法;

3、存入數據

NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectories,NSUserDomainMask,YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [Person new];
person.avator = self.avator;
[NSkeyedArchiver archiveRootObject:person toFile:path];

4、讀取數據

NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectories,NSUserDomainMask,YES).firstObject stringByAppendingPathComponent:@"person.data"];
Person *person = [NSKeyedArchiver unarchiveObjectWithFile:path];
if person{
   self.avator = person.avator;
}

注意:前面三種存儲方式都是覆蓋存儲,每次需要存儲數據的時候都必須把所有的數據都讀取出來,修改數據后再覆蓋寫入,不適用于大文件的存儲。

4)CoreData

CoreData能減少50%~70%model層的代碼量,實現model層的代碼高效復用。

Core Data typically decreases by 50 to 70 percent the amount of code you write to support the model layer.

通過建立實體的屬性、關系表格,能夠實現數據一對多,多對多的對應關系,使增刪改查更加高效。

新建一個demo工程,簡單實現CoreData的增刪改查功能:

新建工程,勾選Use Core Data

新建工程

新建工程之后會看到一個.xcdatamodel的文件,選中它,在左下方添加一個實體文件,然后添加屬性

創建實體,添加屬性

新建一個file,選擇 Core Data / NSManagedObjext Subclass,之后選中. xcdatamodel對象的文件名,選擇剛創建的對應實體


新建文件
選擇. xcdatamodel對象的文件名
選中對應實體

之后會得到一個Person的model文件,包含剛才添加的兩個屬性:name和age。

coredata的增刪改查都依賴于NSManagedObjectContext的實例。
創建NSManagedObjectContext實例

NSURL *URL = [[NSBundle mainBundle] URLForResource:@"CoreDataDemo" withExtension:@"mood"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentOfURL:URL];
//獲取database文件路徑
NSURL *storeURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSPersistentStoreCoordinate *persistent = [[NSPersistentStoreCoordinate alloc] initWithManageObjectModel:model];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentCoordinate:persistent];

插入:

//創建模型
Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:self.context];
person.name = [NSString stringWithFormat:@"name%d",arc4random()];
person.age  = @8;
NSError *error;
//保存
[self.context save:&error];

刪除:

   NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:self.context];
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setEntity:entity];
    //檢索條件
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@",@"hah"];
    [request setPredicate:predicate];
    NSArray *arr = [self.context executeFetchRequest:request error:nil];
    if (arr.count && arr.count > 0) {
        for (Person *p in arr) {
            [self.context deleteObject:p];
        }
        [self.context save:nil];
    }else {
        NSLog(@"nodata");
    }

查詢:

//獲取實體
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:self.context];
    //創建請求
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setEntity:entity];
    //獲取所有的Person實體的對象
    NSError *error;
    NSArray *arr = [self.context executeFetchRequest:request error:&error];
    for (Person *p in arr) {
        NSLog(p.name);
    }

修改:

    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:self.context];
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setEntity:entity];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@",@"hah1"];
    [request setPredicate:predicate];
    
    //搜索符合條件的對象
    NSArray *arr = [self.context executeFetchRequest:request error:nil];
    for (Person *p in arr) {
        p.name = @"hah";
    }
    [self.context save:nil];
SQLite3

表面上SQLite將數據分為以下幾種類型:

integer:整數
real:實數
text:文本
blob:二進制數據,圖片,文件等

實際上,SQLite能存儲所有的數據。使用SQLite,要先添加libsqlite3.dylib庫文件并導入頭文件。
在操作數據庫之前,首先要先打開數據庫文件,然后新建或者指定一張表。

建表:

/*
   打開數據庫并創建一張表
*/
-(void)openDatabase{
    //設置文件名
    NSString *path = [NSSearchPathForDirectoriesInDomain(NSDocumentDirectory,NSUserDomainMask,YES).lastObject stringByAppendingPathComponent:@"person.db"];
    //打開數據庫文件,如果沒有會自動創建一個。
    NSInteger result = sqlite3_open(path.UTF8String, &sqlite);
    if (result == SQLITE_OK) {
        NSLog(@"打開數據庫文件");
        //建表
        char *errmsg = NULL;
        sqlite3_exec(sqlite, "CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)", NULL, NULL, &errmsg);
        if (errmsg) {
            NSLog(@"建表成功");
        }else{
            NSLog(@"建表失敗");
        }
    }else{
        NSLog(@"打開數據庫文件失敗");
    }
}

sqlite_exec()方法可以做很多操作,創表、增刪改都行,但是一般不用于查詢操作,因為sqlite_exec()不會返回查詢的結果,而查詢操作是要求返回查詢結果的。

插入數據:

- (void)insertData{
    NSString *name = @"sqlite insert data";
    NSInteger count = 40;
    NSString *operation = [NSString stringWithFormat:@"INSERT INTO t_person(name,age) VALUE('%@','%ld',)",name,count];
    sqlite3_exec(sqlite, operation.UTF8String, NULL, NULL, &errmsg);
    if (errmsg) {
        NSLog(@"錯誤:%s",errmsg);
    }else{
        NSLog(@"插入成功");
    }
}

讀取數據

- (void)readData {
   NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1000];
   char *sql = "select name, age from t_person;";
   sqlite3_stmt *stmt;
   NSInteger result = sqlite3_prepare_v2(_sqlite3, sql, -1, &stmt, NULL);
   if (result == SQLITE_OK) {
       while (sqlite3_step(stmt) == SQLITE_ROW) {
           char *name = (char *)sqlite3_column_text(stmt, 0);
           NSInteger age = sqlite3_column_int(stmt, 1);
           //創建對象
           Person *person = [Person personWithName:[NSString stringWithUTF8String:name] Age:age];
           [mArray addObject:person];
       }
       self.dataList = mArray;
   }
   sqlite3_finalize(stmt);
}

開發過程中,SQLite的操作都使用第三方開源庫FMDB,它是用OC封裝了sqlite的API,相對于用c語言函數使用sqlite3更加容易理解,也更輕量級。

FMDB

FMDB使用起來更加面向對象,省去了很多冗余的C語言代碼,對比CoreData更加輕量級,更靈活,同時也提供了多線程安全的數據庫操作方法,有效防止數據混亂。

核心類
  • FMDatabase:

一個FMDatabase對象就代表一個單獨的數據庫,用來執行sqlite語句。

  • FMRequestSet

用來存儲執行查詢后的結果集合

  • FMDatabaseQueue

用于在多線程中進行查詢和更新,它是線程安全的。

簡單使用

和sqlite3一樣,使用FMDB需要先打開數據庫,創建一張表。
FMDB中的除了查詢外的所有操作都可以用executeUpdate...方法來操作,比如,插入、刪除、更改。

//常用方法:   
- (BOOL)executeUpdate:(NSString*)sql, ...
- (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments

1、創建一個庫

NSString *filePath = [NSSearchPathForDirectoriesInDomain(NSDocumentDirectory,NSUSerDomainmasks,YES).lastObject stringByAppendingPathComponent:@"person.db"];
//打開數據之前先清空舊的數據庫
NSFileManager *manager = [NSFileManeger defaultManager];
[manager removeItemAtPath:filePath error:nil];
//打開數據庫
FMDatabase *databse = [FMDatabase databaseWithPath:filePath];
//判斷是否成功打開數據庫
if (![databse open]){
    NSLog(@"打開數據庫失敗");
}

傳入的path可以有以下三種情況,FMDatabse會根據傳入的path值做不同的處理

1、具體文件路徑:路徑存在直接打開,不存在會自動創建
2、空字符串@"":創建一個臨時目錄下的的數據庫文件,當數據庫連接關閉時,數據庫文件會被銷毀
3、nil:創建內存中的臨時數據庫文件,當數據庫連接關閉時,數據庫文件銷毀

2、創建表格

[database executeUpdate:@"CREATE TABLE IF NOT EXIST t_person(id integer primary key autoincrement,name text,age integer)"];

3、插入

事務處理有開始,結束,回滾三種。
如:向一個主表和子表插入數據時。
如果順利不出錯在insert(主)前開始事務,在insert(子)后
結束事務。出錯的話他就會回滾到insert前的狀態。
如正常的寫法:
{
1、開始事務
2、執行操作
。。。。
3、結束事務;
回滾事務
執行提示等一些信息
。。。
}

FMDB中開始事務:beginTransaction,結束事務:commit,

[database beginTransaction];
[database executeUpdate:@"INSERT INTO t_person(name,age) VALUE(?,?)", @"laowang",[NSNumber numberWithInteger56]];
[databae commit];

4、查詢

FMRequestSet *requestSet = [database executeQuery:@"SELECT * FROM t_person"];
while([requestSet next]){
    NSString *name = [requestSet stringForColum:@"name"];
    NSInteger age = [requestSet intForColum:@"age"];
}
[requestSet close];

5、線程安全

當多個線程同時對一個數據操作時容易造成數據的丟失損壞,因此不要讓多個線程同時分享同一個FMDatabase實例。使用FMDatabaseQueue來保證不造成數據混亂,它是線程安全的。

創建隊列:
FMDatabaseQueue *queue = [FMDatabaseQueue databaseWithPath:filePath];

//在隊列中操作數據庫
[queue inDatabase:^(FMDatabase *database){
    [database executeUpdate:@"CREATE TABLE IF NOT EXIST t_person(id integer autoincrements,name text ,age integer)"];
    [database executeUpdate:@"INSERT INTO t_person(name,age) VALUE('laozhang',?)",[NSNumber numberOfInt:35];
    FMResultSet *result = [database executeQuery:@"select * from t_person"];    
         while([result next]) {   
         }  
}];

還能把任務輕松的包裝好放到事務中

[queue inTransaction:^(FMDatabase *database, BOOL *rollback) {    
          [database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_1", [NSNumber numberWithInt:1]];    
          [database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_2", [NSNumber numberWithInt:2]];    
          [database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_3", [NSNumber numberWithInt:3]];      
          FMResultSet *result = [database executeQuery:@"select * from t_person"];    
             while([result next]) {   
             }   
           //回滾
           *rollback = YES;  
    }];
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容