看板 Knuckles_note
作者 Knuckles (站長 那克斯)
標題 [Xcode] 使用 Core Data 儲存資料
時間 2014年09月22日 Mon. AM 02:16:20



在 iOS APP 中要儲存使用者的資料,讓 APP 關掉再打開後資料還在

若資料只是單純的 key-value 值的話,可以使用 NSUserDefaults
若是複雜的資料要用到資料庫的話,可以使用 Core Data

在 CoreData 底層是使用 SQLite 這個資料庫來儲存資料
只是把 SQL 的指令用物件導向包裝起來

在新增專案時,如果 template 是選 Empty 或 Master-Detail Application 的話
可以勾選 Use Core Data
 
會自動加入 CoreData.framework、
建立一個 .xcdatamodeld 檔、
以及在類別 AppDelegate 中建立一堆存取 CoreData 要用到的成員函數


如果一開始沒勾的話,可依照以下步驟自己加上去

1. 到專案設定的 General 頁,下面的 Linked Frameworks and Libraries,
   加上「CoreData.framework」

   編輯 Supporting Files/專案名-Prefix.pch 檔
   在 #import <Foundation/Foundation.h> 這行下面加上

    #import <CoreData/CoreData.h>

2. Command+n 新增檔案,選 iOS / Core Data / Data Model
   Save as 的名稱可自訂,例如就輸入「CoreData」
   在檔案列表就會產生一個 CoreData.xcdatamodeld 檔

3. 在 AppDelegate.h 新增三個成員變數

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

   在 AppDelegate.m 的 @implementation AppDelegate 下一行加上

@synthesize managedObjectContext = _managedObjectContext;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;

   在 - applicationWillTerminate: 裡加上一行

    [self saveContext];

   在 APP 要被關閉前執行後面會加上的 - saveContext  

   在 @end 前加上以下這幾個成員函數,主要就是要用來取得上面的三個成員變數用的
===========================================================

//將有更改的資料存進資料庫
- (void)saveContext
{
    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) {
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
}

#pragma mark - Core Data stack

//取得 managedObjectContext
- (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }
   
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}

//取得 managedObjectModel
- (NSManagedObjectModel *)managedObjectModel
{
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreData" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

//取得 persistentStoreCoordinator
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
   
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreData.sqlite"];
   
    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
   
    return _persistentStoreCoordinator;
}

#pragma mark - Application's Documents directory

// 取得 APP 用來儲存使用者資料的資料夾路徑
- (NSURL *)applicationDocumentsDirectory
{
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

===========================================================

以上這些是在建立專案時如果有勾使用 CoreData 的話會自動產生的
其中最重要的就是取得 managedObjectContext
透過這個就可以用 CoreData 提供的類別存取資料庫了

在其他的 ViewController 要取得 AppDelegate 裡的 managedObjectContext
可以用

    NSManagedObjectContext *context = nil;
    id delegate = [[UIApplication sharedApplication] delegate];
    if ([delegate performSelector:@selector(managedObjectContext)]) {
        context = [delegate managedObjectContext];
    }

下面的程式就直接使用 context 來存取資料庫


要在 CoreData 裡新增資料的話

建立資料結構

開啟 .xcdatamodeld 檔
建立一個 Entity,在裡面加上一組 Attributes,像這樣
 
Entity 就是資料庫裡的 Table,Attributes 就是 Table 的結構

這邊我們想儲存使用者的看板閱讀記錄
所以新增一個 Entity: boardHistory
Attributes 有三個分別為 bi, name, title

新增一筆資料 (INSERT)

    NSManagedObject *newBoard = [NSEntityDescription insertNewObjectForEntityForName:@"BoardHistory" inManagedObjectContext:context];
    [newBoard setValue:[NSNumber numberWithInt:self.bi] forKey:@"bi"];
    [newBoard setValue:self.boardName forKey:@"name"];
    [newBoard setValue:self.boardTitle forKey:@"title"];

在記憶體新建一筆資料,存在 NSManagedObject *newBoard
修改 newBoard 的值為想存的資料
然後執行 [context save:&error] 即可寫入資料庫

    NSError *error = nil;
    if (![context save:&error]) {
        NSLog(@"Can't Save! %@ %@", error, [error localizedDescription]);
    }


取得儲存的資料列表 (SELECT)

    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"BoardHistory"];
    NSArray *boardHistoryList = [context executeFetchRequest:request error:nil];

這樣即可將所有儲存的看板列表存成 NSMutableArray
取得的 NSMutableArray 裡存的是 NSManagedObject

例如要讀取第一筆資料的看板名稱的話,可以這樣:

   NSManagedObject *board = boardHistoryList[0];
   NSLog(@"board name:%@", [board valueForKey:[@"name"]);


如果要抓出特定條件的資料,要在 request 加上 NSPredicate
例如要抓出看板名為 boardName 的資料

    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"BoardHistory"];
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name == %@", boardName];
    [request setPredicate:predicate];
    NSArray *boardList = [context executeFetchRequest:request error:nil];


修改資料 (UPDATE)

例如要修改某一筆資料的看板名稱
先用上面的方法將看板名為 boardName 的資料抓出來,存進 boardList
修改內容

    NSManagedObject *board = boardList[0];
    [board setValue:newBoardName forKey:@"name"];

然後使用 [context save:nil] 即可存進資料庫


刪除資料 (DELETE)

例如要刪除看板名稱為 boardName 的那一筆資料
先用上面的方法將看板名為 boardName 的資料抓出來,存進 boardList
接著刪除這筆資料

    NSManagedObject *board = boardList[0];
    [context deleteObject:board];

然後使用 [context save:nil] 即可更新資料庫


參考:

Introduction to Core Data for iOS and iPhone Programming
Looking for solution to save data in your iOS app? This tutorial gives you an introduction of Core Data and shows you how to build an app with Core Data. ...
 
iOS Core Data Tutorial: Fetch, Update, Delete Data from Database The Core Data tutorial shows you how to update and delete data from SQLite database in iOS app. You'll also learn how to enable SQL statement for debugging purpose. ...




===== 問題解決記錄 ========

若是有修改 Core Data 資料結構的話,可能會在使用 Core Data 時程式就當掉

需要將本來建立的資料清除,在手機的主畫面長按APP圖示,將APP刪除後再重裝即可


--
--
※ 作者: Knuckles 時間: 2014-09-22 02:16:20
※ 編輯: Knuckles 時間: 2015-03-24 07:43:53