引言
在這個教程中,你會看到在Xcode提供的初始化代碼模板和數據模型編輯器資源中,用Swift語言寫出你的第一個Core Data app,將會是一件多么容易上手的事情
- 使用Xcode的模型編輯器在Core Data中創建你想存儲的模型數據
- 在Core Data中添加一條新記錄
- 從Core Data中獲取一組記錄
- 在列表視圖中顯示用戶的數據
你還可以看到一個Core Data在在后臺是如何與各組件進行交互的。我們正在超越自我,但-是時候簡歷一個應用程序了!
本文翻譯自 http://www.raywenderlich.com/85578/first-core-data-app-using-swift
Ray的說明:本文是Core Data by Tutorials一書中iOS8 Feast部分章節的微縮版本,讓你了解一下這是一本什么樣的書。我們希望你喜歡!
開始教程
打開Xcode,然后選擇 Single View Application 模板,創建一個新的iPhone工程,將其命名為 HitList,并勾選 Use Core data。
檢查看看Xcode的 Use Core data 框架 AppDelegate.swift
中自動生成了 Core data stack 樣板代碼。
Core Data 堆棧是由一組便于保存和檢索信息的對象所組成。還有一個目的是管理Core Data狀態作為一個表示數據模型等等的整體。
Note:不是所有的Xcode模板都可以選擇 Use Core data 的選項。在Xcode 6中,只有 Master-Detail Application 和 Single View Application 兩個模板可以有該選項。
本文例子中得app非常簡單。就是會有一個命名為“hit list”的列表視圖,你能夠將名稱添加到列表中,你將使用Core Data來保證數據存儲。在本教程中,我們不會以任何暴力形式強迫你,你可以認為這個app是個“favorites list”,用于記錄你的好友,這當然沒有問題!
點擊進入Main.storyboard
在Interface Builder中打開。選擇View Controller 然后改變他的常規高度和寬度匹配iPhone的縱向模式:
接著,嵌入一個導航控制器到視圖控制器中。從Xcode的 Editor 菜單,選擇 Embed In…\ Navigation Controller 。
回到Interface Builder,從對象庫的標示圖中拖拽一個Table View
出來,并覆蓋整個視圖。
然后再拖拽一個Bar Button Item
放置到新增加的視圖控制器的導航欄上。最后,雙擊Bar Button Item
將文本設置為 Add 。你的畫布應該像下面的截圖那樣:
每次你點擊頂端右側的Add
時,將會在屏幕上出現一個包含文本框的警告,你可以在里面輸入某人的名字到文本框中。退去警告將保存名字并刷新列表視圖顯示所有你保存過的名字。
在你完成這些功能之前,你需要連接視圖控制器和列表視圖的數據源。按住Ctrl拖拽列表視圖到導航欄上方黃色的視圖控制器圖標,如下圖所示,然后點擊dataSource
:
你可能會疑惑為什么不需要設置列表視圖的代理,這是因為點擊cell并不會觸發任何的事件。沒有比這更簡單的了!
通過按下 Command-Option-Enter 或者選擇Xcode工具條目上的中間按鈕,打開輔助編輯器。按住Ctrl拖拽
列表視圖到ViewController.swift
,在類定義中插入outlet:
取名為tableView,如下行所示:
@IBOutlet weak var tableView: UITableView!
按住Ctrl拖拽Add
按鈕到ViewController.swift
,同時創建一個action代替outlet生成一個方法名為addName
的事件:
@IBAction func addName(sender: AnyObject){
}
現在你可以參考下表格視圖和按鈕條目的action代碼。接著,設置標示圖的模型,在ViewController.swift
中添加如下屬性。
//Insert below the tableView IBOutlet
var names = [String]()
names
是一個可變的數組,用來保存顯示列表視圖上的字符。
將viewDidLoad
的實現替換成如下代碼:
override func viewDidLoad() {
super.viewDidLoad()
title = "\"The List\""
tableView.registerClass(UITableViewCell.self,
forCellReuseIdentifier: "Cell")
}
這里將在列表視圖中注冊一個UITableViewCell
的類,你這樣做將會使你再隊列cell的時候,讓列表視圖返回一個正確類型的cell。
還是在ViewController.swift
, ViewController
將通過編輯類聲明來確認遵守UITableViewDataSource
協議:
//Add UITableViewDataSource to class declaration
class ViewController: UIViewController, UITableViewDataSource {
此時,Xcode將報錯關于viewController沒有遵守協議。
在viewDidLoad
下面實現如下數據源方法來解決這個問題:
// MARK: UITableViewDataSource
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return names.count
}
func tableView(tableView: UITableView,
cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell {
let cell =
tableView.dequeueReusableCellWithIdentifier("Cell")
as UITableViewCell
cell.textLabel!.text = names[indexPath.row]
return cell
}
如果你使用過UITableView
,這些代碼將會看起來很熟悉。第一個方法是說明列表視圖的行數和names
數組的字符串個數一樣多。
第二個方法tableView(_:cellForRowAtIndexPath:)
,隊列他們的列表視圖cell并按照names
中字符串填充.
先不要運行app。你還需要輸入字符串好讓列表視圖來顯示他們。
實現你之前拖拽的代碼addName IBAction
:
//Implement the addName IBAction
@IBAction func addName(sender: AnyObject) {
var alert = UIAlertController(title: "New name",
message: "Add a new name",
preferredStyle: .Alert)
let saveAction = UIAlertAction(title: "Save",
style: .Default) { (action: UIAlertAction!) -> Void in
let textField = alert.textFields![0] as UITextField
self.names.append(textField.text)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel",
style: .Default) { (action: UIAlertAction!) -> Void in
}
alert.addTextFieldWithConfigurationHandler {
(textField: UITextField!) -> Void in
}
alert.addAction(saveAction)
alert.addAction(cancelAction)
presentViewController(alert,
animated: true,
completion: nil)
}
每當你點擊Add
按鈕時,該方法將會彈出一個帶有文本框和兩個按鈕Save
和Cancel
的UIAlertController
。
點擊Save
將會插入你當前文本框上的內容到names
數組中,并且會刷新列表視圖。由于names數組是支持列表視圖的模型,所以無論你輸入什么到文本框中,都會在列表視圖上展示。
最后,該第一次運行你的程序了,點擊Add
按鈕,警告控制器就會像這個樣子:
添加4到5個名稱到列表中。你應該會獲得如下樣式:
你的列表視圖會展示你保存名稱的數組中的數據,但這里最大的問題是缺少持續性。這個數組只是存在內存中,如果你強退或者重啟你的設備,你的名單將會消失。
Core Data提供了持續性,這意味著他能以一個更持久的狀態存儲數據,使他能在app重啟或者設備重啟之后還存在。
你還沒有添加Core Data, 所以在你退出app后沒有什么可以保存下來。讓我們試一下看看。如果你用的是真機調試,按住Home建,或者在你的模擬器中按下 Shift+Command+H,這樣將會讓你回到熟悉的網格主頁。
從主頁上點擊HitList
圖標,重新回到app的前臺。名稱還是留在屏幕上,怎么回事?
但你點擊Home鍵時,app從前臺進入到后臺。這時,操作系統會暫時凍結你內存上的所有內容,包括你的names數組。同樣地道理,當你喚醒app返回到前臺,操作系統將恢復到之前的狀態,就像你沒有離開過一樣。
Apple 在iOS4之后引入了多線程, 他們為用戶創建了一個無縫的體驗,但這為開發者的持久性數據帶來了來疑惑。這些名稱真的是持久的嗎?
不,這并不是持久的。如果你在程序快速切換頁面完全殺死app或者關掉你的手機,那些名稱將會消失。你當然可以去驗證他。在程序運行的前臺,雙擊Home鍵進入程序快速切換頁面,如下:
從這里,向上滑出HitList的快照來終止應用程序。這應該不會存在一絲HitList的內存(這里沒有雙關語)。返回到主頁點擊HitList的圖標重新啟動app,驗證名稱是否消失。
如果你開發過iOS或者熟悉多線程編程的方法,內存暫存和持久性的區別顯而易見。但在用戶的眼里,他們并不關心名稱是否還存在內存中,他們不關心app是進入后臺,然后回來,或者app是否被保存,并重新加載。
他們在意的是當他們返回的時候,名稱是否還在。
所以,在本教程中你即將學會的是你一個讓你在重新啟動app時依然保存有之前數據的持久化教程。
為你的數據創建模型
現在你知道改如何驗證數據的持久性了,那就讓我們開始Core Data吧!HitList的目標非常簡單:持久化你輸入的名稱,使它們在你重新啟動app后,依然可見。
到現在為止,你已經實現用Swift字符串在內存中儲存名稱。在這個章節,你將用Core Data對象替換這些字符串。
第一步,先創建一個闡述在磁盤上表示Core Data的managed object model
。默認情況下,Core Data 使用SQLite數據庫進行持久化存儲,所以你可以認為數據模型就是數據庫架構。
Note
: 你會在這個教程中遇到頗多managed
單詞。如果你再類名中見到managed
這個單詞,比如NSManagedObjectContrxt
,說明你再處理一個Core Data的類。Managed
表示Core Data對象生命周期的Core Data數據管理。
但是不要以為所有的Core Data類都包含managed
,事實上,大多數是不包含的。對于Core Data類的完整列表,請查看OC中得頭文件CoreData/CoreData.h
當你在創建HitList工程時勾選了Core Data,Xcode會為你自動生成一個名為HitList.xcdatamodeld
的數據模型文件。
點擊并打開HitList.xcdatamodeld
,你將看到如下強大的數據模型編輯器界面:
數據模型編輯器界面有很多功能,從現在開始,我們專注于創建單一的Core Data實體。
在左側點擊 Add Entity
創建一個新的實體,雙擊新的實體,將名稱更改為Person
,如下:
你可能會疑惑衛生么模型編輯器要使用術語"Enity",難道你只是簡單地定義一個新的類?正如你很快要看到,Core Data有他自己的詞匯。下面是你會經常遇到的一些術語的簡單整合:
-
entity
在Core Data中是一個類定義。典型例子是雇員
或者公司
。在關系型數據庫中,一個實體相當于一個表。 -
attribute
是附加到特定實體上的一條信息。例如雇員
實體能包含雇員的名字,職位和年薪。在數據庫中,一個屬性相當于一個表中得特定字段。 -
relationship
是多個實體之間的鏈接。在Core Data中,兩個實體之間的關系叫做對一關系,多個實體間的關系叫做對多關系。例如,一個經理可以和多個雇員這個就是對多關系,但一個雇員只能有一個經理,這就是對一關系。
Note
:你可能已經注意到,實體看起來有點像類,類似的,attribute/relationship看起來有點像properties。他們有什么區別呢?你可以認為Core Data的實體當做一個定義類,把Core Data管理對象當做這個類的實例
現在你已經知道attribute是什么了,回到模型編輯器界面在Person
中添加一個attribute。選擇左手邊的Person
,點擊Attributes
下的加號(+)。設置一個新的屬性名,取名為name
并設置他的type為String
:
在Core Data中,屬性有很多種數據類型--其中一種是String。
保存數據到Core Data
在ViewController.swift
頂端引入頭文件:
//Add below "import UIKit"
import CoreData
如果你之前使用過OC的框架,你可能需要在你工程的Build Phases
中鏈接框架,在Swift中,一個簡單地import
就是你所有需要開始使用Core Data的API要做的事情。
接著,替換之前列表視圖的模型如下:
//Change [String] to [NSManagedObject]
var people = [NSManagedObject]()
你需要存儲的時Person
的實體,而不僅僅只是名字,所以你需要將列表視圖的數據模型數組重命名為people
。它現在擁有的時一個NSManagedObject
的實例而不僅僅只是簡單地字符串。
NSManagedObject
表示存儲在Core Data中的一個單一對象,你必須使用它來創建,編輯,保存和刪除來完成你的Core Data持久化存儲。正如你很快就要看到的,NSManagedObject
是一個模型接口(shap shifter)
。他以實體的形式存在在你的數據模型中,你可以隨意使用你定義的attributes和relationship。
因為你改變了列表視圖模型,你必須替換掉你之前實現的數據源的方法,如下:
//Replace both UITableViewDataSource methods
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(tableView: UITableView,
cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell {
let cell =
tableView.dequeueReusableCellWithIdentifier("Cell")
as UITableViewCell
let person = people[indexPath.row]
cell.textLabel!.text = person.valueForKey("name") as String?
return cell
}
最顯著的變化在cellForRowAtIndexPath
中,替換模型數組中與cell匹配的對應字符串,你可以使用語cell相對應的NSManagedObject
。
設置你從NSManagedObject
中獲取的名字屬性,如下:
cell.textLabel.text = person.valueForKey("name") as String
為什么必須這樣做?事實證明,NSManagedObject
并不知道你再數據模型重定義的name
屬性,所以沒有辦法直接用屬性訪問它。Core Data 提供讀出值的唯一辦法只有鍵-值編碼,就是通常所說的KVC。
Note
:如果你是iOS開發的新手,你可能對KVC或者鍵-值編碼不熟悉。
KVC是Cocoa和Cocoa Touch通過間接使用標識對象屬性的字符串來訪問對象屬性的機制。在這種情況下,KVC使NSManagedObject
感覺像是字典。
鍵-值編碼可用于繼承自NSObject
的所有類,包括NSManagedObject
。你不能夠在沒有繼承自NSObject
的Swift對象中用KVC訪問屬性
接著,替換保存事件addName @IBAction
方法如下:
let saveAction = UIAlertAction(title: "Save",
style: .Default) { (action: UIAlertAction!) -> Void in
let textField = alert.textFields![0] as UITextField
self.saveName(textField.text)
self.tableView.reloadData()
}
這需要在輸入文本到文本框中,然后通過一個方法叫saveName
。添加saveName
到ViewController.swift
,如下所示:
func saveName(name: String) {
//1
let appDelegate =
UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
//2
let entity = NSEntityDescription.entityForName("Person",
inManagedObjectContext:
managedContext)
let person = NSManagedObject(entity: entity!,
insertIntoManagedObjectContext:managedContext)
//3
person.setValue(name, forKey: "name")
//4
var error: NSError?
if !managedContext.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
//5
people.append(person)
}
這里是Core Data的補充,這段代碼的作用:
-
在你可以保存或者檢索你的Core Data存儲之前,你需要先獲得你的
NSManagedObjectContext
,你可以把托管對象文本認為是在內存中暫存的管理對象。想象一下把儲存一個新的管理對象到Core Data看成兩個步驟:首先,你插入一個新的管理對象到managed object context;然后,隨你樂意提交你的管理對象到managed object context來儲存到磁盤上。
Xcode已經生成了一個managed object context 當做新工程模板的一部分——記住,這里僅僅是在你一開始已經選擇了
Use Core Data
選項。這樣會默認創建managed object context的property在application delegate當中。你要先得到app delegate的引用,你才訪問它。 -
你創建了一個新的管理對象,然后插入到managed object context中。你可以同時完成
NSManagedObject
的初始化:init(entity:insertIntoManagedObjectContext:)
。你可能想知道
NSEnityDescription
是怎么一回事,回想一下,我們把NSManagedObject
稱作模型接口(shap shifter)
類是因為它表示一個實體。一個實體描述是在運行時從帶有NSManagedObject
實例的數據模型的實體定義的鏈接。 隨著得到
NSManagedObject
,你可以通過使用鍵-值編碼設置name
屬性。你必須拼寫KVC的key值(這里指"name"),而且它必須存在在你的數據模型中,否則你的app可能會在運行時奔潰。提交你你修改到
person
然后通過調用managed object context中得save
來保存到磁盤上。注意,save
的參數是一個指向NSError
的指針。如果你的存儲操作有任何的錯誤,你將能夠檢查出錯誤,并在必要的時候提醒用戶。恭喜你!你新的管理對象現在已經可以安全的存儲在Core data的持久性存儲中。插入新的管理對象到
people
數組中,這樣就能在你刷新時,顯示在列表視圖里。
這里會比一個字符串數組稍稍復雜一些,但并不會難。這里的一些代碼-獲取managed object context 和實體-能搞在你的init
或者viewDidLoad
只生成一次,然后重復使用。這里為了簡單,在每一個方法你都實現了一次。
編譯并運行app,然后添加一些名字:
如果你的名字確實被儲存在Core Data中,你的HitList app應該能通過持久化測試。雙擊Home鍵回到快速app切換頁面,上滑HitList app殺死進程。
從頁面中點擊app并重新啟動,稍等片刻,怎么回事?列表視圖怎么會是空的?
你已經保存到Core Data了,但是重啟app后,people
數組卻依然還是什么都沒有!其實那些數據已經坐在那里等你餓了,只是你還沒有取得它。
從Core Data中讀取數據
為了從你的持久化存儲中為managed object context讀取數據,你必須把他拿出來。添加如下方法到ViewController.swift
中:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
//1
let appDelegate =
UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
//2
let fetchRequest = NSFetchRequest(entityName:"Person")
//3
var error: NSError?
let fetchedResults =
managedContext.executeFetchRequest(fetchRequest,
error: &error) as [NSManagedObject]?
if let results = fetchedResults {
people = results
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}
一步一步來解釋這些代碼都做了什么:
正如上一章節提到的,在你使用Core Data之前,你需要一個managed object context,讀取數據也一樣。你需要通過application delegate來獲取一個managed object context的引用。
顧名思義,
NSFetchRequest
是負責從Core Data中讀取數據的類。獲取請求十分強大且靈活,你可以用請求來讀取一組滿足特定條件的對象(例如:給我讀取所有舉著在Wisconsin并且至少在公司工作三年以上的雇員),也可以單個值(例如:給我讀取數據庫中名字最長的雇員),功能不單單這些。
讀取請求有一些提煉返回結果的修飾詞。從現在開始,你應該知道NSEntityDescription
是其中一個修飾語(這一個是必須的)。
設置過去請求的實體property,或者以init(entityName:)
進行初始化,讀取特定實體的對象。這就是你再獲取所有Person
實體要做的事。處理讀取請求給managed object context去做一些繁重的任務。
executeFetchRequest(_:error:)
返回一個在請求中指定標準的管理對象可選數組。
Note
:如果沒有匹配的讀取請求條件的對象,該方法返回一個包含空數組的可選值。
如果在讀取時發生了錯誤,則方法返回一個包含0的可選值。如果發生這種情況,你可以檢查NSError
并采取相應措施。
再次編譯并運行代碼,現在,你應該可以看到你之前添加的名字列表。
非常棒!他們起死回生了!再添加一些名字到列表里,然后重新啟動app來驗證保存和讀取是否正確運行。只要不是刪除app,重置你的模擬器或把你的手機進行出廠恢復,無論如何你的米那個字列表都會出現在列表視圖當中。
接下來該何去何從?
在這個教程中,你已經體驗到了基本的Core Data概念:數據模型,實體,attributes,管理對象,managed object context和讀取請求。這里是完整的Hitlist工程。