Core Data 使用教學(一)

對於熟悉 SQL 語法的開發者而言,使用 SQLite 作為 App 端的資料儲存方案是很方便實用的選擇。然而並非每位開發者皆熟悉 SQL,而且 SQLite 並不提供物件化的存取方式,而且使用 C 函式的方式操作,不小心就會讓程式碼變得太複雜,對此 Apple 提供了 Core Data Framework:它使用物件化的操作方式,不需要具備底層資料庫知識又可以達成同樣的功能。

Core Data 元件

Core Data 由幾個元件進行運作:Managed Object(實際操作的物件), Managed Object Context(抽象的操作空間), Persistent Store Coordinator, Managed Object Model(像是Database Schema 資料關聯藍圖), Persistent Object Store(如實體檔案),關連圖如下:

Core Data Architecture
Core Data Architecture

要使用 Core Data 的話就需要先對每個元件進行了解。

Managed Objects

Managed objects 是你的程式裡實際上使用的資料物件,你所產生的 Core Data 物件都會繼承自 NSManagedObject,它就像是資料庫裡的一個 Row 或是 Record。因此,要新增資料就要先新增 Managed object,而要查詢目前的資料列表,回傳的就是 Managed object 列表。而所有的 Managed objects 由 Managed object context 保存管理。

Managed Object Context

使用 Core Data 的 App 並不會直接存取實際的儲存檔案,而是透過 Managed Object Context 來存取 Managed Objects 而運作。Managed Object Context 處理 Managed objects 物件與 Persistent Object Store 之間的狀態,以及各 Managed objects 之間的關聯。所有的變動像是 Managed objects 的新增刪除修改等等,會暫存在 Managed Object Context 裡,直到收到 Managed Object Context 要儲存的命令才真的寫入 Persistent Object Store。

Managed Object Model

Managed objects 的物件定義,以及物件之間關聯就是定義在 Managed Object Model 裡。它的作用有點近似於 Database Schema ,是物件關聯的藍圖。每個物件會定義屬性(Attribute),以及關聯(Relationships),fetched property,fetch request。

Attribute

可定義屬性的名稱,與資料型別。由於這些名稱與型別在轉換為 NSManagedObject subclass 時會立即變為該物件的屬性,因此名稱最好是以小寫開頭。

Relationship

與關聯式資料庫裡的 relation 相同,用來定義Managed Object 之間的關聯性。有分為 one-to-one,one-to-many,many-to-many 三種。

Fetch property

這是另一種定義 Managed Object 關聯性的另一種方法。它允許一個物件單向的存取另一個 Managed Object。而 Relationship 則為雙向的關係。

Fetch request

預先定義好的查詢條件,讓使用端方便的取用。比如說可以先定義好查詢:今天沒有報到的學生人數。

Persistent Store Coordinator

Persistent Store Coordinator用來處理多個 persistent object store (如實體檔案)的存取。iOS 開發者並不會直接使用到這個服務,雖然還是需要創建這個服務。大部份情況下我們只需要一個 persistent object store 即可,即始使用複數個persistent object store,經由 Persistent Store Coordinator 的管理,上層的 App 也不需要知道與處理,就當作單一一個 Object Store 的方式來進行作業。

Persistent Object Store

Persistent Object Store 是 Core Data 實際存取實體裝置的地方,Core Data 支援的儲存裝置有三種硬碟類型以及一種記憶體類型。三種硬碟類型分別為有:SQLite, XML, 以及 binary 檔案。預設是使用 SQLite 檔案,實際上在使用 Core Data 開發的 App 並不會感覺實際上是使用什麼儲存方式。因為無論使用那種儲存方式,Core Data 操作的命令與程式碼都是相同的。

建立Entity Description

在使用 Core Data 的第一步,就是要建立 Entity Description。建立好之後這些 Entity Description 即可快速轉換為 Objective-C Class。在建立一個新的 Project 時,選擇使用 Core Data 將會自動產生一個<Project name>.xcdatamodeld的檔案,點選該檔案之後在最下方點選 Add Entity 即可增加一個新的 Entity。

core_data_new_project

core_data_new_entity

預設的名稱就叫作 Entity,可以用滑鼠雙點來改名字。接著在右方的 Attribute 下方點擊 + 號來新增Attribute。由於這裡的 Attribute 名稱會對映到之後自動產生的 Class attribute,所以建議應該用小寫開頭,比如說要用 name 而不是用 Name。而型別也會直接複製過去。
接著,我們也可以在這裡建立relationship,比如說我們建立了 User 與 Company 二個 Entity,接著分別在 User 與 Company 的 Relationships下方按+號來新增 relationship,互相設為 Inverse。設好之後可以切換 Editor Style 來觀察關聯圖。

core_data_relationship

取用  Managed Object Context

由於許多 Core Data  的操作都會需要 Managed Object Context,所以接著我們需要知道如何取用。不過作法其實很簡單,因為在建立一個有 Core Data 的 Project時,程式就已經自動幫我們在 AppDelegate 裡建好程式碼了,因此只要呼叫以下的程式碼即可取得 Managed Object Context

AppDelegate *appDelegate=(AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext=[appDelegate managedObjectContext];

 

另:如果你的 Project 已經開發了一段時間才臨時要增加 Core Data 的使用,最簡單的作法就是直接建一個新的使用  Core  Data 的 Project 然後將這些程式碼複製過去即可。

取用 Entity Description

在操作 Core Data 時使用的 FetchRequest 都會需要 Entity Description 作為參數,要取用 Entity Description 的程式碼如下:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Entity名稱" inManagedObjectContext: managedObjectContext];
[fetchRequest setEntity:entity];

 

新增 Managed Object

在準備好 Managed Object Context 與 Entity Description 之後,就可以開始新增 Managed Object 了。新增程式碼如下:

NSManagedObject *newUser = [NSEntityDescription
insertNewObjectForEntityForName:@"User"
inManagedObjectContext:managedObjectContext];

[newUser setValue:@“Orange” forKey:@“name”]; // 設定屬性

NSError *error;
[managedObjectContext save:&error];  // 要呼叫 save 才會儲存

可以觀察到 NSManagedObject 的屬性存取方式是像 Dictionary 的 key-value 設定法。

取得 Managed Object

透過NSFetchRequest可以對 Core Data 取得所需要的 Managed Object 列表。下面的程式碼將取得全部的 User 物件:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext: managedObjectContext];
[fetchRequest setEntity:entity];

NSError *error = nil;
NSArray *fetchedObjects = [ managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(@"Fetch error, something's wrong. %@",error);
}
return fetchedObjects;

若要對物件作篩選,則只要加上NSPredicate條件即可:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext: managedObjectContext];
[fetchRequest setEntity:entity];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name=%@", name];
[fetchRequest setPredicate:predicate];

NSError *error = nil;
NSArray *fetchedObjects = [ managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(@"Fetch error, something's wrong. %@",error);
}
return fetchedObjects;

NSPredicate 所接受的語法有很多,簡單的=語法若不夠用的話,可以好好研究一下官方的網頁:Predicate Programming Guide
不過還是要再提醒一下新手工程師:簡單的方式可以達成任務就一定要用簡單的方式,複雜的程式碼往往會增加程式撰寫時間,除錯時間,以及後續修改維護的工程師時間。

To be continued…

基本的 NSManagedObject 的 key-value 的取用方法很實際,而且若使用在迴圈裡時會很方便。但是也不無缺點:若給的 attribute String 打錯的話,無法在 compile 時就發現錯誤,而必需要到執行時間才能發覺。下一篇將介紹我個人很喜歡的 NSManagedObject subclass 的存取方式…

 

Leave a Reply

Please rate*

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.