ios持久化儲存

weixin_34198583發表於2016-12-12

前言

iOS中常用的持久化儲存方式有好幾種:

  • 偏好設定(NSUserDefaults)
  • plist檔案儲存
  • 歸檔
  • SQLite3
  • Core Data

沙盒

每個iOS應用都有自己的應用沙盒(應用沙盒就是檔案系統目錄),與其他檔案系統隔離。應用必須待在自己的沙盒裡,其他應用不能訪問該沙盒。沙盒下的目錄如下:

  • Application:存放程式原始檔,上架前經過數字簽名,上架後不可修改
  • Documents: 儲存應⽤執行時生成的需要持久化的資料,iTunes同步裝置時會備份該目錄。例如,遊戲應用可將遊戲存檔儲存在該目錄
  • tmp: 儲存應⽤執行時所需的臨時資料,使⽤完畢後再將相應的檔案從該目錄刪除。應用 沒有執行時,系統也可能會清除該目錄下的檔案。iTunes同步裝置時不會備份該目錄。
  • Library/Caches: 儲存應用執行時⽣成的需要持久化的資料,iTunes同步裝置時不會備份 該目錄。⼀一般儲存體積大、不需要備份的非重要資料,比如網路資料快取儲存到Caches下
  • Library/Preference: 儲存應用的所有偏好設定,如iOS的Settings(設定) 應⽤會在該目錄中查詢應⽤的設定資訊。iTunes同步裝置時會備份該目錄

雖然沙盒中有這麼多資料夾,但是沒有資料夾都不盡相同,都有各自的特性。所以在選擇存放目錄時,一定要認真選擇適合的目錄。

"應用程式包": 這裡面存放的是應用程式的原始檔,包括資原始檔和可執行檔案。

 NSString *path = [[NSBundle mainBundle] bundlePath];
  NSLog(@"%@", path);

Documents: 最常用的目錄,iTunes同步該應用時會同步此資料夾中的內容,適合儲存重要資料。

  NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
  NSLog(@"%@", path);

Library/CachesiTunes不會同步此資料夾,適合儲存體積大,不需要備份的非重要資料。

 NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
  NSLog(@"%@", path);
  • Library/PreferencesiTunes同步該應用時會同步此資料夾中的內容,通常儲存應用的設定資訊。

  • tmpiTunes不會同步此資料夾,系統可能在應用沒執行時就刪除該目錄下的檔案,所以此目錄適合儲存應用中的一些臨時檔案,用完就刪除。
NSString *path = NSTemporaryDirectory();
NSLog(@"%@", path);

 

NSUserDefaults

NSUserDefaults是個單例類,用於儲存少量資料。NSUserDefaults實際上對plist檔案操作的封裝,更方便我們直接操作,一般用於儲存系統級別的偏好設定。比如我們經常將登入後的使用者的一些設定通過NSUserDefaults儲存到plist檔案中。

有很多App,他們也是將使用者的賬號和密碼儲存在偏好設定中。我們不講安全性問題,因此不討論儲存在偏好設定下是否安全。

使用起來非常簡單,如下:

// 寫入檔案
- (void)saveUserName:(NSString *)userNamepassword:(NSString *)password {
  [[NSUserDefaults standardUserDefaults] setObject:userNameforKey:@"username"];
  [[NSUserDefaults standardUserDefaults] setObject:passwordforKey:@"password"];
  [[NSUserDefaults standardUserDefaults] synchronize];
}
 
// 在用的時候,就可以讀取出來使用
NSString * userName = [[NSUserDefaults standardUserDefaults] objectForKey:@"username"];
NSString * password = [[NSUserDefaults standardUserDefaults] objectForKey:@"password"];

儲存到偏好設定的只有系統已經提供好的型別,比如基本型別、NSNumber、NSDictionary、NSArray等。對於NSObject及繼承於NSObject的型別,是不支援的。如下:

NSObject * obj = [[NSObject alloc] init];
[[NSUserDefaults standardUserDefaults] setObject:objforKey:@"obj"];
 
// 就會崩潰
Terminating appduetouncaughtexception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object <NSObject: 0x7fb502680cb0> for key obj'

plist儲存

  有的時候,我們需要將下載的資料儲存到檔案中儲存起來,比如,有時候我們將下載起來的城市的資料儲存到本地,當更新城市的順序時,下次也能夠按照最後一次操作的順序來顯示出來。

  plist檔案是將某些特定的類,通過XML檔案的方式儲存在目錄中。

NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;
1.獲得檔案路徑
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;

2.儲存
NSString *fileName = [path stringByAppendingPathComponent:@"123.plist"];
NSArray *array = @[@"123", @"456", @"789"];
[array writeToFile:fileName atomically:YES];

3.讀取
NSArray *result = [NSArray arrayWithContentsOfFile:fileName];
NSLog(@"%@", result);

4.注意
只有以上列出的型別才能使用plist檔案儲存。
儲存時使用writeToFile: atomically:方法。 其中atomically表示是否需要先寫入一個輔助檔案,再把輔助檔案拷貝到目標檔案地址。這是更安全的寫入檔案方法,一般都寫YES。
讀取時使用arrayWithContentsOfFile:方法。

 

 
// 資料儲存,是儲存到手機裡面,
// Plist儲存,就是把某些資料寫到plist檔案中
// plist儲存一般用來儲存陣列和字典
// Plist儲存是蘋果特有,只有蘋果才能生成plist
// plist不能儲存自定義物件,如NSObject、model等
NSDictionary *dict = @{@"age":@"18",@"name":@"USER"};
  
// 儲存應用沙盒(app安裝到手機上的資料夾)
// Caches資料夾
// 在某個範圍內容搜尋資料夾的路徑
// directory:獲取哪個資料夾
// domainMask:在哪個範圍下獲取 NSUserDomainMask:在使用者的範圍內搜尋
// expandTilde是否展開全路徑,YES:展開
NSString * cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
NSLog(@"%@",cachePath);
  
// 拼接檔案路徑
NSString * filePath = [cachePath stringByAppendingPathComponent:@"data.plist"];
  
// 獲取應用沙盒
NSString *homePath = NSHomeDirectory();
NSLog(@"%@",homePath);
  
// File:檔案全路徑 => 所有資料夾路徑 + 檔案路徑
[dict writeToFile:filePath atomically:YES];
// 將資料取出來
NSLog(@"%@", [NSDictionary dictionaryWithContentsOfFile:filePath]);

我們看看列印的結果:

2016-02-1722:14:43.055 iOSPersistentStorageDemo[25471:809758] /Users/huangyibiao/Library/Developer/CoreSimulator/Devices/CF3A5A4C-486F-4A72-957B-2AD94BD90EC1/data/Containers/Data/Application/65E8F814-45E5-420C-A174-822A7830748E/Library/Caches
2016-02-1722:14:43.055 iOSPersistentStorageDemo[25471:809758] /Users/huangyibiao/Library/Developer/CoreSimulator/Devices/CF3A5A4C-486F-4A72-957B-2AD94BD90EC1/data/Containers/Data/Application/65E8F814-45E5-420C-A174-822A7830748E
2016-02-1722:14:43.056 iOSPersistentStorageDemo[25471:809758] {
    age = 18;
    name = USER;
}

注意:操作plist檔案時,檔案路徑一定要是全路徑。

歸檔(NSKeyedArchiver)

自定義物件應用範圍很廣,因為它對應著MVC中的Model層,即實體類。在程式中,我們會在Model層定義很多的entity,例如User、Teacher、Person等。

那麼對自定義物件的歸檔顯得重要的多,因為很多情況下我們需要在Home鍵之後儲存資料,在程式恢復時重新載入,那麼,歸檔便是一個好的選擇。

下面我們自定義一個Person類:

// 要使物件可以歸檔,必須遵守NSCoding協議
@interfacePerson: NSObject<NSCoding>
 
@property (nonatomic, assign) int age;
@property (nonatomic, strong) NSString *name;
 
@end
 
@implementation Person
 
// 什麼時候呼叫:只要一個自定義物件歸檔的時候就會呼叫
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.nameforKey:@"name"];
    [aCoder encodeInt:self.ageforKey:@"age"];
}
 
- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
      self.name = [aDecoder decodeObjectForKey:@"name"];
      self.age = [aDecoder decodeIntForKey:@"age"];
    }
    return self;
}
@end

如何將自定義物件歸檔和解檔:

- (void)savePerson { 
    // 歸檔:plist儲存不能儲存自定義物件,此時可以使用歸檔來完成
    Person *person = [[Person alloc]init];
    person.age = 18;
    person.name = @"USER";
        
    // 獲取tmp目錄路徑
  
NSString *tempPath = NSTemporaryDirectory(); // 拼接檔名 NSString *filePath = [tempPathstringByAppendingPathComponent:@"person.data"]; // 歸檔 [NSKeyedArchiverarchiveRootObject:persontoFile:filePath]; } - (void)readPerson { // 獲取tmp NSString *tempPath = NSTemporaryDirectory(); // 拼接檔名 NSString *filePath = [tempPath stringByAppendingPathComponent:@"person.data"]; // 解檔 Person *p = [NSKeyedUnarchiverunarchiveObjectWithFile:filePath]; NSLog(@"%@ %d",p.name,p.age); }

假設我們定義了一個自定義的view,這個view是用xib或者storybard來生成的,那麼我們我一定如下方法時,就需要如下實現:

@implementation CustomView
 
// 解析xib,storyboard檔案時會呼叫
- (id)initWithCoder:(NSCoder *)aDecoder {
    // 什麼時候呼叫[super initWithCoder:aDecoder]?
    // 只要父類遵守了NSCoding協議,就呼叫[super initWithCoder:aDecoder]
    if (self = [super initWithCoder:aDecoder]) {
        NSLog(@"%s",__func__);
    }
    return  self;
}
@end

 

 //1.遵循NSCoding協議 
  @interface Person : NSObject <NSCoding>

  //2.設定屬性
  @property (strong, nonatomic) UIImage *avatar;
  @property (copy, nonatomic) NSString *name;
  @property (assign, nonatomic) NSInteger age;

  @end

//實現協議方法
//解檔
  - (id)initWithCoder:(NSCoder *)aDecoder {
      if ([super init]) {
          self.avatar = [aDecoder decodeObjectForKey:@"avatar"];
          self.name = [aDecoder decodeObjectForKey:@"name"];
          self.age = [aDecoder decodeIntegerForKey:@"age"];
      }

      return self;
  }

  //歸檔
  - (void)encodeWithCoder:(NSCoder *)aCoder {
      [aCoder encodeObject:self.avatar forKey:@"avatar"];
      [aCoder encodeObject:self.name forKey:@"name"];
      [aCoder encodeInteger:self.age forKey:@"age"];
  }

//特別注意

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


//需要把物件歸檔是呼叫NSKeyedArchiver的工廠方法 archiveRootObject: toFile: 方法。
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];

  Person *person = [[Person alloc] init];
  person.avatar = self.avatarView.image;
  person.name = self.nameField.text;
  person.age = [self.ageField.text integerValue];

  [NSKeyedArchiver archiveRootObject:person toFile:file];

//需要從檔案中解檔物件就呼叫NSKeyedUnarchiver的一個工廠方法 unarchiveObjectWithFile: 即可。
 NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];

  Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
  if (person) {
     self.avatarView.image = person.avatar;
     self.nameField.text = person.name;
     self.ageField.text = [NSString stringWithFormat:@"%ld", person.age];
  }

SQLite3

之前的所有儲存方法,都是覆蓋儲存。如果想要增加一條資料就必須把整個檔案讀出來,然後修改資料後再把整個內容覆蓋寫入檔案。所以它們都不適合儲存大量的內容。

1.欄位型別

表面上SQLite將資料分為以下幾種型別:

  • integer : 整數
  • real : 實數(浮點數)
  • text : 文字字串
  • blob : 二進位制資料,比如檔案,圖片之類的

實際上SQLite是無型別的。即不管你在創表時指定的欄位型別是什麼,儲存是依然可以儲存任意型別的資料。而且在創表時也可以不指定欄位型別。SQLite之所以什麼型別就是為了良好的程式設計規範和方便開發人員交流,所以平時在使用時最好設定正確的欄位型別!主鍵必須設定成integer

2. 準備工作

準備工作就是匯入依賴庫啦,在iOS中要使用SQLite3,需要新增庫檔案:libsqlite3.dylib並匯入主標頭檔案,這是一個C語言的庫,所以直接使用SQLite3還是比較麻煩的。

3.使用

  • 建立資料庫並開啟

    運算元據庫之前必須先指定資料庫檔案和要操作的表,所以使用SQLite3,首先要開啟資料庫檔案,然後指定或建立一張表。

/**
*  開啟資料庫並建立一個表
*/
- (void)openDatabase {

   //1.設定檔名
   NSString *filename = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.db"];

   //2.開啟資料庫檔案,如果沒有會自動建立一個檔案
   NSInteger result = sqlite3_open(filename.UTF8String, &_sqlite3);
   if (result == SQLITE_OK) {
       NSLog(@"開啟資料庫成功!");

       //3.建立一個資料庫表
       char *errmsg = NULL;

       sqlite3_exec(_sqlite3, "CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)", NULL, NULL, &errmsg);
       if (errmsg) {
           NSLog(@"錯誤:%s", errmsg);
       } else {
           NSLog(@"創表成功!");
       }

   } else {
       NSLog(@"開啟資料庫失敗!");
   }
}

執行指令 

使用 sqlite3_exec() 方法可以執行任何SQL語句,比如創表、更新、插入和刪除操作。但是一般不用它執行查詢語句,因為它不會返回查詢到的資料。

/**
*  往表中插入1000條資料
*/
- (void)insertData {

NSString *nameStr;
NSInteger age;
for (NSInteger i = 0; i < 1000; i++) {
  nameStr = [NSString stringWithFormat:@"Bourne-%d", arc4random_uniform(10000)];
  age = arc4random_uniform(80) + 20;

  NSString *sql = [NSString stringWithFormat:@"INSERT INTO t_person (name, age) VALUES('%@', '%ld')", nameStr, age];

  char *errmsg = NULL;
  sqlite3_exec(_sqlite3, sql.UTF8String, NULL, NULL, &errmsg);
  if (errmsg) {
      NSLog(@"錯誤:%s", errmsg);
  }
}

NSLog(@"插入完畢!");
}

查詢指令

前面說過一般不使用 sqlite3_exec() 方法查詢資料。因為查詢資料必須要獲得查詢結果,所以查詢相對比較麻煩。示例程式碼如下:

  • sqlite3_prepare_v2() : 檢查sql的合法性
  • sqlite3_step() : 逐行獲取查詢結果,不斷重複,直到最後一條記錄
  • sqlite3_coloum_xxx() : 獲取對應型別的內容,iCol對應的就是SQL語句中欄位的順序,從0開始。根據實際查詢欄位的屬性,使用sqlite3_column_xxx取得對應的內容即可。
  • sqlite3_finalize() : 釋放stmt
/**
*  從表中讀取資料到陣列中
*/
- (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);
}

4.總結

總得來說,SQLite3的使用還是比較麻煩的,因為都是些c語言的函式,理解起來有些困難。不過在一般開發過程中,使用的都是第三方開源庫 FMDB,封裝了這些基本的c語言方法,使得我們在使用時更加容易理解,提高開發效率。

FMDB

1.簡介

FMDB是iOS平臺的SQLite資料庫框架,它是以OC的方式封裝了SQLite的C語言API,它相對於cocoa自帶的C語言框架有如下的優點:

  • 使用起來更加物件導向,省去了很多麻煩、冗餘的C語言程式碼
  • 對比蘋果自帶的Core Data框架,更加輕量級和靈活
  • 提供了多執行緒安全的資料庫操作方法,有效地防止資料混亂

注:FMDB的gitHub地址

2.核心類

FMDB有三個主要的類:

  • FMDatabase
    一個FMDatabase物件就代表一個單獨的SQLite資料庫,用來執行SQL語句

  • FMResultSet
    使用FMDatabase執行查詢後的結果集

  • FMDatabaseQueue
    用於在多執行緒中執行多個查詢或更新,它是執行緒安全的

3.開啟資料庫

和c語言框架一樣,FMDB通過指定SQLite資料庫檔案路徑來建立FMDatabase物件,但FMDB更加容易理解,使用起來更容易,使用之前一樣需要匯入sqlite3.dylib。開啟資料庫方法如下:

NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.db"];

FMDatabase *database = [FMDatabase databaseWithPath:path];    
if (![database open]) {
    NSLog(@"資料庫開啟失敗!");
}

值得注意的是,Path的值可以傳入以下三種情況:

  • 具體檔案路徑,如果不存在會自動建立

  • 空字串@"",會在臨時目錄建立一個空的資料庫,當FMDatabase連線關閉時,資料庫檔案也被刪除

  • nil,會建立一個記憶體中臨時資料庫,當FMDatabase連線關閉時,資料庫會被銷燬

4.更新

在FMDB中,除查詢以外的所有操作,都稱為“更新”, 如:create、drop、insert、update、delete等操作,使用executeUpdate:方法執行更新:

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

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

//或者  
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES(?, ?)", @"Bourne", [NSNumber numberWithInt:42]];

5.查詢

查詢方法也有3種,使用起來相當簡單:

- (FMResultSet *)executeQuery:(NSString*)sql, ...
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments

查詢示例:

//1.執行查詢
FMResultSet *result = [database executeQuery:@"SELECT * FROM t_person"];

//2.遍歷結果集
while ([result next]) {
    NSString *name = [result stringForColumn:@"name"];
    int age = [result intForColumn:@"age"];
}

6.執行緒安全

在多個執行緒中同時使用一個FMDatabase例項是不明智的。不要讓多個執行緒分享同一個FMDatabase例項,它無法在多個執行緒中同時使用。 如果在多個執行緒中同時使用一個FMDatabase例項,會造成資料混亂等問題。所以,請使用 FMDatabaseQueue,它是執行緒安全的。以下是使用方法:

  • 建立佇列。
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
  • 使用佇列
[queue inDatabase:^(FMDatabase *database) {    
          [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]) {   

         }    
}];

而且可以輕鬆地把簡單任務包裝到事務裡:

[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;  
    }];

FMDatabaseQueue 後臺會建立系列化的G-C-D佇列,並執行你傳給G-C-D佇列的塊。這意味著 你從多執行緒同時呼叫呼叫方法,GDC也會按它接收的塊的順序來執行。

 

 

CoreData的簡單使用

準備工作

  • 建立資料庫

    1. 新建檔案,選擇CoreData -> DataModel
    2. 新增實體(表),Add Entity
    3. 給表中新增屬性,點選Attributes下方的‘+’
  • 建立模型檔案

    1. 配置Minimum、Language和Codegen三個選項。
    2. 選擇檔案,Editor-> NSManaged Object subclass
    3. 根據提示,選擇實體
  • 通過程式碼,關聯資料庫和實體

 

建立專案的時候建立coreData

系統自動生成程式碼

#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (readonly, strong) NSPersistentContainer *persistentContainer;

- (void)saveContext;


@end
- (void)applicationWillTerminate:(UIApplication *)application {
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    // Saves changes in the application's managed object context before the application terminates.
    [self saveContext];
}


#pragma mark - Core Data stack

@synthesize persistentContainer = _persistentContainer;

- (NSPersistentContainer *)persistentContainer {
    // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
    @synchronized (self) {
        if (_persistentContainer == nil) {
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"AutoCoreData"];
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    // Replace this implementation with code to handle the error appropriately.
                    // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                    
                    /*
                     Typical reasons for an error here include:
                     * The parent directory does not exist, cannot be created, or disallows writing.
                     * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                     * The device is out of space.
                     * The store could not be migrated to the current model version.
                     Check the error message to determine what the actual problem was.
                    */
                    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
                    abort();
                }
            }];
        }
    }
    
    return _persistentContainer;
}

#pragma mark - Core Data Saving support

- (void)saveContext {
    NSManagedObjectContext *context = self.persistentContainer.viewContext;
    NSError *error = nil;
    if ([context hasChanges] && ![context save:&error]) {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", error, error.userInfo);
        abort();
    }
}

新增元素 - Create 和 讀取資料 - Read

- (void)readData {
    AppDelegate * appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"PostCode"];
    request.sortDescriptors = @[
                                [NSSortDescriptor sortDescriptorWithKey:@"province" ascending:NO],
                                [NSSortDescriptor sortDescriptorWithKey:@"city" ascending:NO],
                                [NSSortDescriptor sortDescriptorWithKey:@"district" ascending:NO]];
    NSError * error = nil;
    NSArray * array = [appDelegate.persistentContainer.viewContext executeFetchRequest:request error:&error];
    if (error) {
        NSLog(@"%@", error);
    }
    
    
    if (!array || ([array isKindOfClass:[NSArray class]] && [array count] <= 0)) {
        // 新增資料到資料庫
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSString * strPath = [[NSBundle mainBundle] pathForResource:@"城市郵編最終整理_方便匯入資料庫" ofType:@"txt"];
            NSString * text = [NSString stringWithContentsOfFile:strPath encoding:NSUTF16StringEncoding error:nil];
            NSArray * lineArr = [text componentsSeparatedByString:@"\n"];

            AppDelegate * appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
            NSEntityDescription * description = [NSEntityDescription entityForName:@"PostCode" inManagedObjectContext:appDelegate.persistentContainer.viewContext];
            for (NSString * line in lineArr) {
                NSArray * items = [line componentsSeparatedByString:@"\t"];
                PostCode * postcode = [[PostCode alloc] initWithEntity:description insertIntoManagedObjectContext:appDelegate.persistentContainer.viewContext];
                postcode.id = items[0];
                postcode.province = items[1];
                postcode.city = items[2];
                postcode.district = items[3];
                postcode.cityId = ((NSString *)items[4]).length >=4 ? items[4]:[@"0" stringByAppendingString:items[4]];
                postcode.postCode = items[5];
            }
            [appDelegate saveContext];
            
            NSError *error = nil;
            NSArray *arr = [appDelegate.persistentContainer.viewContext executeFetchRequest:request error:&error];
            if (error) {
                NSLog(@"%@", error);
            } else {
                _dataSource = [[NSMutableArray alloc] initWithArray:arr];
                dispatch_async(dispatch_get_main_queue(), ^{
                    [_tableView reloadData];
                });
            }
        });
    } else {
        _dataSource = [[NSMutableArray alloc] initWithArray:array];
        [_tableView reloadData];
    }
}

刪除資料 - Delete

 // 刪除所有資料
           for (PostCode *postcode in a) {
                [del.managedObjectContext deleteObject:postcode];
           }
            [del saveContext];

查詢資料

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
    if (!searchText.length) {
        [self readData];
        return;
    }
    AppDelegate * appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"PostCode"];
    request.sortDescriptors = @[
                                [NSSortDescriptor sortDescriptorWithKey:@"province" ascending:NO],
                                [NSSortDescriptor sortDescriptorWithKey:@"city" ascending:NO],
                                [NSSortDescriptor sortDescriptorWithKey:@"district" ascending:NO]];
    request.predicate = [NSPredicate predicateWithFormat:@"province CONTAINS %@ OR city CONTAINS %@ OR district CONTAINS %@ OR cityId CONTAINS %@ OR postCode CONTAINS %@ OR id CONTAINS %@", searchText, searchText, searchText, searchText, searchText, searchText];
    NSError * error = nil;
    NSArray * array = [appDelegate.persistentContainer.viewContext executeFetchRequest:request error:&error];
    _dataSource = [[NSMutableArray alloc] initWithArray:array];
    [_tableView reloadData];
}

 

後續新增coreData

 

 我們可以運算元據的方法放在一個物件中CoreDataManager。
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@interface CoreDataManager : NSObject
@property (readonly, strong) NSPersistentContainer *persistentContainer;
- (void)saveContext;
+ (instancetype) sharedCoreDataManager;
@end



#import "CoreDataManager.h"

@implementation CoreDataManager

static CoreDataManager *coredataManager;
+ (instancetype) sharedCoreDataManager{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        coredataManager = [[self alloc] init];
    });
    return coredataManager;
}

#pragma mark - Core Data stack

@synthesize persistentContainer = _persistentContainer;
- (NSPersistentContainer *)persistentContainer {
    // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
    @synchronized (self) {
        if (_persistentContainer == nil) {
            _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"Model"];
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    // Replace this implementation with code to handle the error appropriately.
                    // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                    
                    /*
                     Typical reasons for an error here include:
                     * The parent directory does not exist, cannot be created, or disallows writing.
                     * The persistent store is not accessible, due to permissions or data protection when the device is locked.
                     * The device is out of space.
                     * The store could not be migrated to the current model version.
                     Check the error message to determine what the actual problem was.
                     */
                    NSLog(@"Unresolved error %@, %@", error, error.userInfo);
                    abort();
                }
            }];
        }
    }
    
    return _persistentContainer;
}

#pragma mark - Core Data Saving support

- (void)saveContext {
    NSManagedObjectContext *context = self.persistentContainer.viewContext;
    NSError *error = nil;
    if ([context hasChanges] && ![context save:&error]) {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", error, error.userInfo);
        abort();
    }
}

運算元據

#import "ViewController.h"
#import "CoreDataManager.h"
#import "User+CoreDataProperties.h"

@interface ViewController ()
@property (nonatomic, strong) CoreDataManager * manager;
@property (nonatomic, strong) User * user;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    _manager = [CoreDataManager sharedCoreDataManager];
}

1、獲取展示資料

- (IBAction)displayData:(UIButton *)sender {
    // 建立取回資料請求
    NSFetchRequest * request = [[NSFetchRequest alloc] init];
    
    // 設定要檢索哪種型別的實體物件
    NSEntityDescription * entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:_manager.persistentContainer.viewContext];
    
    // 設定請求實體
    [request setEntity:entity];
    
    // 指定對結果的排序方式
//    NSSortDescriptor * sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"age" ascending:NO];
    NSArray * sortDescriptions = @[
      [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO],
      [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:NO],
      [NSSortDescriptor sortDescriptorWithKey:@"sex" ascending:NO]];
    
    [request setSortDescriptors:sortDescriptions];
    NSError * error = nil;
    
    // 執行獲取資料請求,返回陣列
    NSArray * fetchResult = [_manager.persistentContainer.viewContext executeFetchRequest:request error:&error];
    if (!fetchResult)
    {
        NSLog(@"error:%@,%@",error,[error userInfo]);
    }
    
    //    NSLog(@"fetchResult :%@",fetchResult);
    for (User * user in fetchResult) {
        NSLog(@"age :%@, name :%@, sex :%@",user.age,user.name,user.sex);
    }
}

2、新增資料

- (IBAction)insertData:(UIButton *)sender {
    //新增資料
        _user = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext];
        [_user setName:@"fengmin111"];
        [_user setSex:@"diannao111"];
        [_user setAge:@(123)];
        NSError * error = nil;
    //    託管物件準備好後,呼叫託管物件上下文的save方法將資料寫入資料庫
        BOOL insertIsSaveSuccess = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext save:&error];
        if (!insertIsSaveSuccess) {
            NSLog(@"Error: %@,%@",error,[error userInfo]);
        }else
        {
            NSLog(@"Save successFull");
        }
}

3、修改資料

- (IBAction)changeData:(UIButton *)sender {
    //修改資料
    //對同一個實體做資料改變
//        _user = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext];
        [_user setName:@"sdfsdagsdfg"];
        [_user setSex:@"bijibasgfsdgsaen"];
        [_user setAge:@(888)];
        NSError * error = nil;
        //託管物件準備好後,呼叫託管物件上下文的save方法將資料寫入資料庫
        BOOL changeIsSaveSuccess = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext save:&error];
        if (!changeIsSaveSuccess) {
            NSLog(@"Error: %@,%@",error,[error userInfo]);
        }else
        {
            NSLog(@"Change successFull");
        }
}

4、刪除資料

- (IBAction)deleteData:(UIButton *)sender {
    //刪除資料
//    _user = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext];
    [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext deleteObject:_user];
        NSError *error = nil;
    
    //託管物件準備好後,呼叫託管物件上下文的save方法將資料寫入資料庫
    BOOL deleteIsSaveSuccess = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext save:&error];
    if (!deleteIsSaveSuccess) {
        NSLog(@"Error: %@,%@",error,[error userInfo]);
    }else
    {
        NSLog(@"del successFull");
    }
    
    
    //刪除所有資料
//    NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:@"User"];
//    NSArray * array = [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext executeFetchRequest:request error:&error];
//    for (User * user in array) {
//        [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext deleteObject:user];
//    }
//    [[CoreDataManager sharedCoreDataManager].persistentContainer.viewContext save:&error];
}

 

 

 

 

 



 

 

相關文章