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初探与实践

  2. 在现有项目中通过新建文件手动创建
    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. 使用NSPersistentContainer(name: "BestBefore") 传入模型文件名称给容器,拿到管理句柄
  2. 调用容器的 container.loadPersistentStores 方法去初始化存储方式,这里我们没有给出参数Core Data会默认使用SQLite作为底层的存储器
  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. 我们实例化了 Item 结构体,并传入 viewContext,这么做的目的是通知viewContext将当前实例存入其缓存
  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. 我们定义了一个变量 items 来存储查询结果
  2. items添加了一个包装器 @FetchRequest 来获取数据,并且被包装器包裹的属性会自动实现双向绑定(类似于Vue的 v-model),当数据发生变化会自动触发视图重绘,包装器的sortDescriptors参数则允许我们按照多个属性进行升序或降序的排序,当然我猜应该也支持一些聚合函数
  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. 这里我们定义了一个 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. 我们通过给deleteItem传入一个 items 中的索引,去找到对应数据对象(即Item的实例)
  2. 将查找到的实例直接传入 viewContext.delete 方法中进行删除
  3. 删除数据库中的数据 viewContext.save()

总结

  1. 我们了解到Core Data是一个抽象层类ORM框架,它不直接操作底层,其底层存储方式可以有多种,默认的是SQLite
  2. Core DataNSPersistentContainermanagedObjectContext 有了初步的了解
  3. 通过CRUD操作,跑通了基本的数据增删改查流程,了解到 Core Data执行 save 方法前,所有的操作都是在缓存中进行,并没有真正入库

别错过精彩内容,快来关注我们的微信公众号【寻桃】!

原文链接:https://juejin.cn/post/7257441765975670841 作者:寻桃

(0)
上一篇 2023年7月20日 上午10:46
下一篇 2023年7月20日 上午10:57

相关推荐

发表回复

登录后才能评论