SwiftUI入门 - Core Data初探与实践

置顶

菜鸟入门笔记,如有谬误之处还请大佬指出
深耕细作 笃行致远

相关文章

如需获取其他SwiftUI知识点,请移步至SwiftUI入门专栏

前言

在开发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操作
    在这里插入图片描述

  2. 在现有项目中通过新建文件手动创建
    在这里插入图片描述
    在这里插入图片描述
    填写完模型文件名后,模型文件将出现在目录树中
    在这里插入图片描述

鉴于以学习为目的,我们采用第二种手动创建的方式

实体与属性

当模型文件创建好之后,下一步则是创建模型的实体与属性,在这里先给出几个重要的概念:

  • 实体(Entity):对应的是数据库中的表
  • 属性(Attribute):对应的是数据库中的字段
  • 关联关系(Relationship):关联关系与数据库中也是一致的,通过外键来引用对方的数据,在本文中不会使用到关联关系,不做过多赘述

按照下图完成实体与属性的添加
在这里插入图片描述

访问模型

先不考虑封装,我们的目的是做一个最小化实现并理解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类用于管理模型、内容上下文和存储方式,贴一张官方的示意图

在这里插入图片描述

代码执行逻辑:

  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之前做多次实体数据的操作,并一次性完成保存,而在保存之前数据都存储在缓存中,并没有真正入库
    在这里插入图片描述

查看数据

这一步我们将刚才添加的数据展示到视图中

    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遍历展示
    在这里插入图片描述

编辑数据

编辑数据与新增数据是相同的逻辑,这里我就偷个懒,只贴一个代码片段

    // 编辑数据
    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 方法前,所有的操作都是在缓存中进行,并没有真正入库

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寻桃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值