天天看點

SwiftUI入門 - Core Data初探與實踐

作者:黑桃

置頂

菜鳥入門筆記,如有謬誤之處還請大佬指出

深耕細作 笃行緻遠

前言

在開發IOS應用的過程中,不可避免會遇到需要存儲大量複雜資料的場景,在對比如下幾種方式後,我認為Core Data的支撐場景應該更為廣泛,随即決定先拿下它。

  • • UserDefaults:一種簡單鍵值對,值為string,類似web中的cookie
  • • plist:是 Property List(屬性清單)的縮寫,是一種用于存儲和序列化資料的檔案格式,它最初是由蘋果公司引入的,并廣泛用于 macOS 和 iOS 平台上的應用程式和配置檔案中
  • • JSON:常見的JSON文本存儲
  • • Core Data:Core Data是以面向對象的方式存儲和管理資料的架構,其主要的底層存儲方式包括 SQLite、Binary、XML、In-Memory。SQLite 作為預設的底層存儲方式,無需編寫SQL語句,支援事務和多種資料類型的存儲

在我的了解來看,Core Data就是一種ORM架構

建立模型檔案

  1. 建立項目時勾選Core Data,初始化模闆檔案中會預設建立一個模型檔案和調用Core Data的示例,并完成了基本的CRUD操作
SwiftUI入門 - Core Data初探與實踐
  1. 在現有項目中通過建立檔案手動建立
SwiftUI入門 - Core Data初探與實踐
SwiftUI入門 - Core Data初探與實踐

填寫完模型檔案名後,模型檔案将出現在目錄樹中

SwiftUI入門 - Core Data初探與實踐

鑒于以學習為目的,我們采用第二種手動建立的方式

實體與屬性

當模型檔案建立好之後,下一步則是建立模型的實體與屬性,在這裡先給出幾個重要的概念:

  • • 實體(Entity):對應的是資料庫中的表
  • • 屬性(Attribute):對應的是資料庫中的字段
  • • 關聯關系(Relationship):關聯關系與資料庫中也是一緻的,通過外鍵來引用對方的資料,在本文中不會使用到關聯關系,不做過多贅述

按照下圖完成實體與屬性的添加

SwiftUI入門 - Core Data初探與實踐

通路模型

先不考慮封裝,我們的目的是做一個最小化實作并了解Core Data的大緻調用流程

在入口檔案處編寫如下代碼(初始化時入口檔案名一般為[項目名稱+App]如 BestBeforeApp.swift,如果不知道入口檔案請全局搜尋 @main):

import SwiftUI
    import CoreData

    @main
    struct BestBeforeApp: App {
        // 容器
        let container: NSPersistentContainer
        
        init(){
            // 指定模型檔案
            container = NSPersistentContainer(name: "BestBefore")
            // 加載存儲類型
            container.loadPersistentStores { description, error in
                if let error = error {
                    fatalError("Unable to load persistent stores: \(error)")
                }
            }
        }

        var body: some Scene {
            WindowGroup {
                // 将環境變量賦給 ContentView 視圖,即 ContentView.swift 檔案
                ContentView().environment(\.managedObjectContext, container.viewContext)
            }
        }
    }           

NSPersistentContainer類用于管理模型、内容上下文和存儲方式,貼一張官方的示意圖

SwiftUI入門 - Core Data初探與實踐

代碼執行邏輯:

  1. 1. 使用NSPersistentContainer(name: "BestBefore")傳入模型檔案名稱給容器,拿到管理句柄
  2. 2. 調用容器的 container.loadPersistentStores 方法去初始化存儲方式,這裡我們沒有給出參數Core Data會預設使用SQLite作為底層的存儲器
  3. 3. 将環境變量managedObjectContext賦給 ContentView 視圖,即 ContentView.swift 檔案

如果通路失敗,上述代碼會抛出錯誤,一般情況下可能是模型檔案名填寫錯誤

新增資料

在 ContentView.swift 檔案中編寫代碼:

import SwiftUI

    struct CoreDataView: View {
        // 擷取 BestBeforeApp 傳入的環境變量并指派給 viewContext
        @Environment(\.managedObjectContext) var viewContext
        
        var body: some View{
            Button {
                addItem()
            } label: {
                Text("新增")
            }
        }
        
        // 新增資料
        func addItem(){
            // 執行個體化一條 Item 實體的資料
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()
            
            do {
                try viewContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }           

在 let newItem = Item(context: viewContext)中的 Item 結構體是我們在建立模型實體後,Xcode自動為我們生成的全局結構體,在項目中任何位置無需導入,可以直接使用viewContext 則是内容上下文管理器,用來管理資料的增删改查,是底層存儲方式的上層封裝

代碼執行邏輯:

  1. 1. 我們執行個體化了 Item 結構體,并傳入 viewContext,這麼做的目的是通知viewContext将目前執行個體存入其緩存
  2. 2. 然後通過 viewContext.save() 将緩存中的資料儲存到資料庫,這樣的解耦意味着我們可以在save之前做多次實體資料的操作,并一次性完成儲存,而在儲存之前資料都存儲在緩存中,并沒有真正入庫
SwiftUI入門 - Core Data初探與實踐

檢視資料

這一步我們将剛才添加的資料展示到視圖中

import SwiftUI

    struct CoreDataView: View {
        // 實體 Item 的資料
        @FetchRequest(
            sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
            animation: .default)
        var items: FetchedResults<Item>

        var body: some View{
            // 循環渲染資料
            ForEach(items){item in
                Text(item.timestamp!,style: .date)
            }
            
            Spacer()
            
            Button {
                addItem()
            } label: {
                Text("新增")
            }
        }
        ...
    }           

代碼執行邏輯:

  1. 1. 我們定義了一個變量 items 來存儲查詢結果
  2. 2. 給items添加了一個包裝器 @FetchRequest來擷取資料,并且被包裝器包裹的屬性會自動實作雙向綁定(類似于Vue的 v-model),當資料發生變化會自動觸發視圖重繪,包裝器的sortDescriptors參數則允許我們按照多個屬性進行升序或降序的排序,當然我猜應該也支援一些聚合函數
  3. 3. 當拿到資料後在 body 中使用ForEach周遊展示
SwiftUI入門 - Core Data初探與實踐

編輯資料

編輯資料與新增資料是相同的邏輯,這裡我就偷個懶,隻貼一個代碼片段

// 編輯資料
    func editItem(item: Item){
        // 更新目前資料的時間戳
        item.timestamp = Date()
        
        do {
            // 注意這裡同樣需要儲存入庫
            try viewContext.save()
        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
    }           
  1. 1. 這裡我們定義了一個 editItem 方法,支援傳入一個Item執行個體去修改其屬性,當然這裡我們也可以通過 @Binding 包裝器将屬性綁定到表單控件,這種情況下則不需要傳入Item執行個體,直接調用viewContext.save() 即可

删除資料

// 删除資料
    func deleteItem(index: Int){
        if let item = items.indices.contains(index) ? items[index] : nil{
            viewContext.delete(item)
            do{
                try viewContext.save()
            }catch{
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }           

代碼執行邏輯:

  1. 1. 我們通過給deleteItem傳入一個 items 中的索引,去找到對應資料對象(即Item的執行個體)
  2. 2. 将查找到的執行個體直接傳入 viewContext.delete 方法中進行删除
  3. 3. 删除資料庫中的資料 viewContext.save()

總結

  1. 1. 我們了解到Core Data是一個抽象層類ORM架構,它不直接操作底層,其底層存儲方式可以有多種,預設的是SQLite
  2. 2. 對 Core Data 的 NSPersistentContainer與 managedObjectContext 有了初步的了解
  3. 3. 通過CRUD操作,跑通了基本的資料增删改查流程,了解到 Core Data執行 save 方法前,所有的操作都是在緩存中進行,并沒有真正入庫

别錯過精彩内容,快來關注我們的微信公衆号【尋桃】!