在 iOS(OSX) 應用程式中,要儲存資料可以使用資料庫或檔案,以及現在要介紹的 Core Data,所以 Core Data 的用途就是儲存資料。Core Data 是在 OSX 10.4 及 iOS 3.0 之後開始使用,它可以將物件序列化後儲存在 XML、binary(二位元檔)或SQLite資料庫。
Core Data 是一個儲存資料的框架,它的底層本質上還是使用 SQLite 資料庫,它提供簡單易用的方式讓你儲存資料,而不用撰寫複雜的 SQL 語法。如果你的專案有使用 Core Data,可以在該 App 的 Document 目錄中找到 sqlite 檔案。
關於效能的問題,到底直接使用 SQLite 好,還是使用 Core Data 好,可以參考這篇文章 iOS Data Storage: Core Data vs. SQLite 。簡單來說,Core Data 是以空間換取時間,意思是比較佔記憶體空間,但速度比較快;另外一個好處是,使用 Core Data 的程式碼比較簡單易讀,不會有複雜的 SQL 語法,官方的說法是,會減少 50~70% 的程式碼。
大部份的 Core Data 的功能,依賴你所建立的綱要(schema)去描述應用程式的實體(Entity),包含屬性及關聯等等。Core Data 使用一個被稱為 Managed object model 的網要(shcema),它是一個 NSManagedObjectModel 物件的實體。
簡單的說,一個 Managed object model 會對應到資料儲存(persistent store)的一組紀錄,這裡的 persistent store 相當於資料庫;而 Managed object model 即一組紀錄,相當於資料表(table)。
由於 Core Data 並不把自己當成關聯式資料庫(雖然它的底層是使用 SQLite,但這只是它的儲存格式之一),它把這些傳統關聯式資料庫的概念抽象化,所以這邊在說明時會在抽象的定義中,以關聯式資料庫的概念來類比。
因此:
一個實體描述(entity description)表示一個實體(即table),實體(Entity)有一個類別名稱,用來表示 Entity,並且會有特質(property)來表示屬性(attribute)及實體間的關聯(relationship)。一群 Entity (table)組合起來就是 Model。使用 NSEntityDescription 物件用來操作實體描述。
因此,在 Core Data 中:
會有一個 Model(=資料庫), 包含至少一個 Entity(=資料表), 這個 Entity 會有屬性及關聯(=欄位)。
註:本範例使用 XCode 7.0 及 Swift 2.0
開啟 XCode ,建立一個 Single View Application 專案:
專案名稱為 CoreDataDemo,記得勾選 Core Data 選項:
當你勾選 Core Data 時,在 AppDelegate.swift 中會被加入 Core Data 相關的程式碼。你會在其中找到像是 "SingleViewCoreData.sqlite" 這段字串,這就是你的 SQLite 資料庫的名稱。
只要在建立專案時有勾選 Core Data,XCode 就會自動產生一個 *.xcdatamodeld 檔案,選擇它會開啟 model (即database)的管理界面:
目前這個資料庫是空的,讓我們來加第一個實體(Entity,即table)。點選下方的 "Add Entity",在左邊欄的會出現新的 "Entity" 項目,你可以點選它來改名,或在右邊欄中改名。我們將名稱改為 "Product":
接著新增這個 Product entity 的屬性(Attirbutes),點選 Attributes 欄位下方的 "+" (或下方的 "Add Attribute" 也可以)即可新增。加入 name 及 price 兩個屬性,並且選擇它的 Type:
再來要讓程式碼中可以使用這個 Entity,我們要建立一個 Product 類別,並且繼承 NSManagedObject。XCode 可以幫我們自動產生,在選單列中選擇 Editor > Create NSManagedObject Subclass...:
選擇要產生的 Data Model 及 Entity 之後,將要產生的檔案選擇建立在專案中。
XCode 會產生兩個檔案:
Product.swift:
import Foundation import CoreData class Product: NSManagedObject { // Insert code here to add functionality to your managed object subclass }
Product+CoreDataProperties.swift:
import Foundation import CoreData extension Product { @NSManaged var name: String? @NSManaged var price: NSNumber? }
Product+CoreDataProperties.swift 用來存放 Entity 的屬性,Product.swift 就專心在該 Entity 要提供的方法。你會注意到這個 Product 類別是繼承 NSManagedObject。
註:如果不知道為什麼這裡會有兩個檔案,可以研究一下 Swift 的 extension 功能,它和 Objective-C 的 Category 是同樣用途。
NSManagedObject 類別的屬性是 @NSManaged,表示它是受管理的,因此我們不必再去寫 getter/setter 或檢查資料型態是否符合等等的工作。
前置工作都完成後,就可以開始寫程式了,首先,我們要新增一筆資料,打開 ViewController.swift,加入新增資料的程式碼:
let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext override func viewDidLoad() { super.viewDidLoad() self.addProduct("iPhone 6s 16GB", price: 24500) } func addProduct(name:String, price:Int) { let product = NSEntityDescription.insertNewObjectForEntityForName("Product", inManagedObjectContext: self.moc) as! Product product.name = name product.price = price do { try self.moc.save() }catch{ fatalError("Failure to save context: /(error)") } }
註:記得先 import CoreData,否則可能會出現找不到類別的錯誤。
XCode 在 AppDelegate 中幫我們建立了 managedObjectContext 這個參考到 NSManagedObjectContext 類別的變數,任何對資料表的操作都必須透過它,所以第一步就是先取得它。
addProduct() 方法用來新增產品,要新增資料到資料表,必須透過 NSEntityDescription 類別的 insertNewObjectForEntityForName() 方法,第一個參數是 Entity 的名稱,第二個參數則是 NSManagedObjectContext,這個方法會回傳我們要求的 Entity,接著只要對該 Entity 的屬性給值即可。
動作到此為止,資料表中已經有一筆資料了,可是目前的動作都僅存在於記憶體中,如果這時把程式關閉,這些資料就會消失。因此,最後記得要呼叫 NSManagedObjectContext 的 save() 方法真正的把資料儲存下來。
來看看剛才新增的產品是否有新增成功。加入顯示全部產品的方法:
func showProducts() { let request = NSFetchRequest(entityName: "Product") do { let results = try moc.executeFetchRequest(request) as! [Product] for result in results { print("Product Name: /(result.name!), Price: /(result.price!)") } }catch{ fatalError("Failed to fetch data: /(error)") } }
然後在原本新增產品的後面加入此方法:
self.addProduct("iPhone 6s 16GB", price: 24500) self.showProducts()
我執行了 3 次後,結果如下:
因為每次重新執行程式時,就會新增一次重覆的資料,我們並不想這樣,所以在每次重新執行時,就把資料表清空,把裡面的資料全部刪除。
func cleanUpProducts() { let request = NSFetchRequest(entityName: "Product") do { let results = try moc.executeFetchRequest(request) as! [Product] for result in results { moc.deleteObject(result) } }catch{ fatalError("Failed to fetch data: /(error)") } }
刪除的動作是,先如同查詢的動作一樣,把資料表中的資料全部取出,然後使用 NSManagedObjectContext 物件的 deleteObject() 方法來一一刪除。
現在 viewDidLoad() 裡是這樣:
self.cleanUpProducts() self.addProduct("iPhone 6s 16GB", price: 24500) self.showProducts()
更新的動作,就是先找到該筆資料,修改它的屬性值,然後儲存就完成了。這裡假設要新增三筆資料,但有一筆的價格打錯了,於是把它更新。現在的 viewDidLoad():
self.cleanUpProducts() self.addProduct("iPhone 6s 16GB", price: 24500) self.addProduct("iPhone 6s 64GB", price: 28500) self.addProduct("iPhone 6s 128GB", price: 22500) self.updateProductPrice() self.showProducts()
我們要找到 22500 這筆資料,把它改成 32500,新增一個方法:
func updateProductPrice(){ let request = NSFetchRequest(entityName: "Product") request.predicate = NSPredicate(format: "name == %@", "iPhone 6s 128GB") do{ let results = try moc.executeFetchRequest(request) as! [Product] if (results.count > 0){ let product = results[0] product.price = 32500 try self.moc.save() } } catch { fatalError("Failed to update data: /(error)") } }
在原本的查詢中,指定 request 的 predicate 屬性來過濾資料,找到後,只要將新的值指定給它,然後儲存,這樣就完成更新了。
NSPredicate 的設定方法和 SQL 語法很像,詳細內容可以參考 這裡 。
以上是最基本的 Core Data 的 CRUD 操作。
Written by Tony at Tony Blog - http://blog.tonycube.com/
----