#76 Core Data 的修煉-1:從簡單的任務開始 , 存取單一欄位資料表(新增、刪除資料)。
Core Data 的目的是讓我們輕鬆使用資料庫,但它的高級抽象設計可能會讓人望而卻步。因此,我們將從一個簡單的任務開始,只用最少的程式碼來完成一個基本的資料存取任務,順利跨進 Core Data 的世界。
APP操作 GIF:
- Add String 新增 Entity 資料
- 在Label 裡顯示 Entity 資料
- Delete All 刪除 Entity 裡所有資料
螢幕截圖:
實驗目標
- 了解 Core Data 的基本工作原理及設定。
- 探討如何使用 Core Data 進行基本的數據操作,包括插入、查詢和刪除。
- 通過程式範例來展示如何在 iOS 應用中使用 Core Data 進行數據持久化。
建立專案
建立新專案時,在 Storage 選項中勾選 Core Data。
專案自動增加的地方
AppDelegate.swift
- 新增
NSPersistentContainer
變數persistentContainer
。 - 使用
persistentContainer
在saveContext()
方法中進行資料儲存。
.xcdatamodeld
- 新增
.xcdatamodeld
檔案並可以在這裡設置資料模型。
程式重點
簡化 AppDelegate
將預設的 Core Data 相關代碼移至 CoreDataStack
類別,並在 SceneDelegate
中引用該類別。
import CoreData
class CoreDataStack {
static let shared = CoreDataStack()
// MARK: - Core Data stack
let persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "phw23_2_coredata")
container.loadPersistentStores { storeDescription, error in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
return container
}()
var context: NSManagedObjectContext {
return persistentContainer.viewContext
}
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
private init() {}
}
修改 SceneDelegate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
...
func sceneDidEnterBackground(_ scene: UIScene) {
//(UIApplication.shared.delegate as? AppDelegate)?.saveContext()
CoreDataStack.shared.saveContext()
}
}
建立 Entity
在 .xcdatamodeld
中新增一個名為 "SimpleEntity" 的 Entity,並添加一個 "simple_value" 屬性(只能使用小寫、底線和數字)。
儲存資料
在 ViewController 建立一個新增資料的函數 ,程式很簡單,就是輸入一個字串,儲起來。
func insertSimpleValue(_ value: String) {
let context = CoreDataStack.shared.context
let entity = NSEntityDescription.entity(forEntityName: "SimpleEntity", in: context)!
let simpleObject = NSManagedObject(entity: entity, insertInto: context)
simpleObject.setValue(value, forKey: "simple_value")
do {
try context.save()
} catch {
print("Failed to save value: \(error)")
}
}
然後在 viewDidLoad()
方法中呼叫該函數。
override func viewDidLoad() {
super.viewDidLoad()
insertSimpleValue("Jason:\(Int.random(in: 0..<100))")
}
就這樣,資料就儲存好了。
讀取資料
接下來我們讀取資料出來看看。
建立抓資料的函數,回傳值是不包含 nil 值的字串陣列。
func fetchSimpleValues() -> [String]? {
let context = CoreDataStack.shared.context
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "SimpleEntity")
do {
let results = try context.fetch(fetchRequest)
return results.map { $0.value(forKey: "simple_value") as? String }.compactMap { $0 }
} catch {
print("Failed to fetch values: \(error)")
return nil
}
}
為何使用 compactMap
compactMap
會對數組中的每個元素應用一個閉包,並返回一個過濾掉 nil
值的新數組。如果 values
包含 nil
,直接將它們拼接成字符串會出現問題,甚至導致應用崩潰。所以使用 compactMap
來確保只有非 nil
的值被處理和顯示。
在 ViewController
中,我們可以使用 fetchSimpleValues
來更新顯示的資料:
class ViewController: UIViewController {
@IBOutlet weak var stringLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
insertSimpleValue("Jason:\(Int.random(in: 0..<100))")
updateStringLabel()
}
func updateStringLabel() {
if let values = fetchSimpleValues() {
stringLabel.text = values.joined(separator: "\n")
}
}
...
}
這樣,我們就可以在 viewDidLoad()
方法中呼叫 insertSimpleValue
和 updateStringLabel
來顯示插入的資料。
執行看看
增加和刪除資料
那我們再添加兩個按鈕,一個用於新增資料,一個用於刪除所有資料。
刪除資料的函數
首先,建立一個刪除資料的函數:
func deleteAllData() {
let context = CoreDataStack.shared.context
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "SimpleEntity")
let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try context.execute(batchDeleteRequest)
try context.save() // 保存上下文
} catch {
print("Failed to delete all data: \(error)")
}
}
然後,實現按鈕的動作方法。新增資料 insertSimpleValue 剛才已經寫好了,直接呼叫就行。
@IBAction func AddString(_ sender: Any) {
insertSimpleValue("Jason:\(Int.random(in: 0..<100))")
updateStringLabel()
}
@IBAction func deleteAll(_ sender: Any) {
deleteAllData()
updateStringLabel()
}
結語
就這麼簡單,通過使用 Core Data,我們輕鬆地建立了一個簡單的資料表。透過 Core Data 提供的方便的工具,我們能夠輕鬆地存取和管理這個資料表。這個筆記實驗了如何使用 Core Data 在應用程式中插入、查詢和刪除資料,實現了數據持久化。希望這個簡單的範例能夠幫助我們更好地理解如何利用 Core Data 來建立和管理你的應用程式中的資料。
深入研究
接下來,我們來看一下剛才的程式碼,詳細了解 Core Data 的工作原理。
Core Data Stack
這段程式碼建立了一個 NSPersistentContainer
實例,它管理 Core Data 的持久性存儲:
// MARK: - Core Data stack
let persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "phw23_2_coredata")
container.loadPersistentStores {…}
return container
}()
當你想要在應用程式中使用 Core Data 來保存資料時,你需要一個容器來管理這些資料。這個容器就像是一個盒子,可以幫助你管理所有的 Core Data 相關事務,比如保存、讀取、刪除資料等。
這段程式碼的目的就是創建這個盒子,也就是 NSPersistentContainer 實例。當你第一次使用這個盒子時(也就是第一次訪問 persistentContainer 屬性),程式碼會自動幫你初始化這個盒子,並且準備好使用。這樣,你就可以方便地進行 Core Data 相關的操作,比如存儲和獲取資料。
儲存上下文(Context)
在 Core Data 中,上下文(Context)是管理對象生命週期、讀取和寫入數據的地方。它是你在應用程式中與 Core Data 進行交互的主要接口之一。
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
Context 充當了數據的暫存區域,它存儲了從持久存儲區(通常是 SQLite 數據庫)中檢索到的數據的副本。當你從持久存儲區中讀取數據時,Core Data 會將數據加載到 Context 中,使你可以在應用程式中對其進行操作。同樣地,當你對 Context 中的對象進行修改或創建新對象時,這些修改和新對象也會暫時存在於 Context 中。
Context還負責管理對象的生命週期。當你創建一個新對象並將其添加到 Context 中時,Context 會負責追蹤這個對象,直到你將其保存到持久存儲區中。同樣地,當你從持久存儲區中刪除一個對象時,Context 也會負責處理這個操作,並確保對象從 Context 中移除。
Entity 的自動生成程式碼
Codegan 設定
Xcode 預設會自動幫我們產生 NSManagedObject 的類別,使得我們的代碼更加簡潔和方便。上面的程式實驗,其實還沒有用到,這裡我們來研究自動產生的程式碼怎麼使用。
例如,SimpleEntity
的自動生成代碼如下:
// phw23_2_coredata+CoreDataModel.swift
import Foundation
import CoreData
// SimpleEntity+CoreDataClass.swift
import Foundation
import CoreData
@objc(SimpleEntity)
public class SimpleEntity: NSManagedObject {
}
// SimpleEntity+CoreDataProperties.swift
import Foundation
import CoreData
extension SimpleEntity {
@nonobjc public class func fetchRequest() -> NSFetchRequest<SimpleEntity> {
return NSFetchRequest<SimpleEntity>(entityName: "SimpleEntity")
}
@NSManaged public var simple_value: String?
}
extension SimpleEntity : Identifiable {
}
這樣,我們的 insertSimpleValue
和 fetchSimpleValues
函數可以進一步簡化:
Attribute
他幫我們寫好了欄位的變數,這真的很方便。
所以原本的新增程式
func insertSimpleValue(_ value: String) {
let context = CoreDataStack.shared.context
let entity = NSEntityDescription.entity(forEntityName: "SimpleEntity", in: context)!
let simpleObject = NSManagedObject(entity: entity, insertInto: context)
simpleObject.setValue(value, forKey: "simple_value")
do {
try context.save()
} catch {
print("Failed to save value: \(error)")
}
}
可以改寫為
func insertSimpleValue(_ value: String) {
let context = CoreDataStack.shared.persistentContainer.viewContext
let simpleEntity = SimpleEntity(context: context)
simpleEntity.simple_value = value
CoreDataStack.shared.saveContext()
}
fetchRequest
他幫我們寫好了 SimpleEntity.fetchRequest()
所以原本的讀取程式
func fetchSimpleValues() -> [String]? {
let context = CoreDataStack.shared.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "SimpleEntity")
do {
let results = try context.fetch(fetchRequest)
return results.map { $0.value(forKey: "simple_value") as? String }.compactMap { $0 }
} catch {
print("Failed to fetch values: \(error)")
return nil
}
}
可以改寫為
func fetchSimpleValues() -> [String]? {
let context = CoreDataStack.shared.persistentContainer.viewContext
let fetchRequest = SimpleEntity.fetchRequest()
do {
let results = try context.fetch(fetchRequest)
return results.map { $0.simple_value ?? "" }
} catch {
print("Failed to fetch values: \(error)")
return nil
}
}
參考:
Swift Arcade Youtube : Getting Started With CoreData、Core Data Codegen Explained、Getting Started With NSFetchedResultsController