前言
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/Caches
: iTunes不會同步此資料夾,適合儲存體積大,不需要備份的非重要資料。
NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject; NSLog(@"%@", path);
-
Library/Preferences
: iTunes同步該應用時會同步此資料夾中的內容,通常儲存應用的設定資訊。 tmp
: iTunes不會同步此資料夾,系統可能在應用沒執行時就刪除該目錄下的檔案,所以此目錄適合儲存應用中的一些臨時檔案,用完就刪除。
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框架,更加輕量級和靈活
- 提供了多執行緒安全的資料庫操作方法,有效地防止資料混亂
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的簡單使用
準備工作
-
建立資料庫
- 新建檔案,選擇
CoreData
->DataModel
- 新增實體(表),
Add Entity
- 給表中新增屬性,點選
Attributes
下方的‘+’號
- 新建檔案,選擇
-
建立模型檔案
- 配置Minimum、Language和Codegen三個選項。
- 選擇檔案,Editor->
NSManaged Object subclass
- 根據提示,選擇實體
-
通過程式碼,關聯資料庫和實體
建立專案的時候建立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
#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]; }