由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(二)

概述

从 WWDC 23 开始,苹果推出了崭新的数据库框架 SwiftData。默认在 SwiftData 中所有对数据的操作都会在主线程中进行,稍有不慎就会让 App 变得"鹅行鸭步"

那么,对于耗时的数据操作我们该如何优雅的面对?又如何让界面与其"一心一力"的同步呢?

在本篇博文中,您将学到如下内容:

  1. SwiftData 如何在后台改变数据?
  2. 如何将后台的更改同步到界面中?

这是本系列第二篇博文。闲言少叙,让我们马上开始 SwiftData 精彩的探究之旅吧!

Let's dive in!!!;)


3. SwiftData 如何在后台改变数据?

现在虽然我们已经圆满解决了之前那个崩溃问题,但是 SwiftData 中数据操作的"水还很深",值得大家进一步"磨砥刻厉"的研究一番。

首先,我们从简单且实用的话题的聊起:SwiftData 如何在后台修改数据?

SwiftData 对于数据的操作是通过模型上下文来完成的,而通过之前的介绍可知:主模型上下文(Main Model Context,以下简称为主上下文)只能在主线程或 MainActor 上修改数据,而私有模型上下文则适合在其它线程或 Actor 中操作数据。

假设这样一种常见的场景:我们的 App 要在启动时生成大量数据,如果将这一操作用主上下文在主线程上执行就会阻塞界面,这在 App 开发中是绝对不能容忍的!

所以,一种方法就是将它们放在私有上下文在后台线程中执行。

将之前 ContentView 视图的代码略作修改,我们现在暂时抛弃 Model 类型,下面所有的代码都只涉及 Item 托管类型:

swift 复制代码
struct ContentView: View {
    @Environment(\.modelContext) var modelContext
    @Query var items: [Item]
    
    var body: some View {
        VStack {
            if let item = items.first {
                Text(item.name)
            }
        }
        .padding()
        .task {
            Task.detached {
                let modelContext = ModelContext(.preview)                
                let item = Item(name: "\(Int.random(in: 0...10000))")
                modelContext.insert(item)
                
                try! modelContext.save()
            }
        }
    }
}

从上面的代码可以看到,我们在 ContentView 显示时创建了一个包含随机值的 Item,并视图通过 @Query 将其"抓取"到主界面上显示。

值得注意的是,我们还做了下面几件事:

  • 通过 Task.detached 创建了一个"分离"任务以确保"脏活累活"都在后台线程中运行;
  • 使用 ModelContext 构造器创建了一个私有上下文,该上下文一旦创建就会和它处在的线程或 Actor 所绑定;

到目前为止一切都很简单惬意,不是吗?

不过当我们编译运行后,视图中心却空空如也!创建的 Item 跑哪去了呢?

4. 如何将后台的更改同步到界面中?

其实,后台线程新创建的 Item 托管对象就在那里,只是它还没有被同步到主上下文中而已。

对于目前的情况来说,SwiftUI 中的 @Query 只能自动同步主上下文中数据的改变,私有上下文中的改变却不在此列。这意味着:我们上面在后台线程中新增的 Item 对象并不能及时刷新到界面中。

这该如何是好呢?

一种简单却略显"粗暴"的方式是,在后台线程插入新 Item 对象后立即强制刷新 UI:

swift 复制代码
struct ContentView: View {
    @Environment(\.modelContext) var modelContext
    @Query var items: [Item]
    @State var refreshID = false
    
    var body: some View {
        NavigationStack {
            VStack {
                List(items) { item in
                    Text(item.name).font(.headline.weight(.heavy))
                }
                .id(refreshID)
            }
            .toolbar {
                ToolbarItem(placement:.topBarTrailing) {
                    Button("New", systemImage: "plus.app") {
                        Task.detached {
                            let modelContext = ModelContext(.preview)
                            
                            let item = Item(name: "\(Int.random(in: 0...10000))")
                            modelContext.insert(item)
                            
                            try! modelContext.save()
                            
                            await MainActor.run {
                                refreshID.toggle()
                            }
                        }
                    }
                    .foregroundStyle(.white)
                    .tint(.green)
                }
            }
        }
    }
}

从上面的代码不难看出,我们每次在后台新插入 Item 对象后立即刷新了 List 视图,这样做会导致 SwiftUI 重新计算 @Query 宏中 items 的内容。

如此这般,我们即可在界面中及时反映出后台线程里私有上下文所导致的 SwiftData 数据变化了。

虽说手动刷新整个视图可以勉强"得偿所愿",但它毕竟会对渲染性能造成或多或少的潜在影响。有没有更好的方法呢?

答案是肯定的!

总结

在本篇博文中,我们讨论了如何在后台线程处理 SwiftData 的数据操作,又如何将这些更改同步到界面中去。

在下一篇博文里,我们将会介绍 SwiftData 2.0 中新引入的 History Trace 机制,并用它来更优雅的解决问题。

感谢观赏,再会 8-)

相关推荐
不羁。。21 分钟前
【撸靶笔记】第七关:GET - Dump into outfile - String
数据库·笔记·oracle
yangchanghua1112 小时前
pgsql 如何查询今天范围内的数据(当天0点0分0秒 - 当天23点59分59秒....)
数据库·pgsql
larance2 小时前
SQLAlchemy 的异步操作来批量保存对象列表
数据库·python
python_chai2 小时前
从数据汇总到高级分析,SQL 查询进阶实战(下篇)—— 分组、子查询与窗口函数全攻略
数据库·sql·mysql
在努力的前端小白2 小时前
Spring Boot 敏感词过滤组件实现:基于DFA算法的高效敏感词检测与替换
java·数据库·spring boot·文本处理·敏感词过滤·dfa算法·组件开发
未来之窗软件服务2 小时前
自建知识库,向量数据库 (九)之 量化前奏分词服务——仙盟创梦IDE
数据库·仙盟创梦ide·东方仙盟·自建ai·ai分词
麦兜*3 小时前
Swift + Xcode 开发环境搭建终极指南
开发语言·ios·swiftui·xcode·swift·苹果vision pro·swift5.6.3
冒泡的肥皂6 小时前
MVCC初学demo(一
数据库·后端·mysql
.Shu.7 小时前
Redis Reactor 模型详解【基本架构、事件循环机制、结合源码详细追踪读写请求从客户端连接到命令执行的完整流程】
数据库·redis·架构
薛晓刚9 小时前
当MySQL的int不够用了
数据库