当前位置: 首页> 教育> 锐评 > 由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(二)

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

时间:2025/7/12 10:06:00来源:https://blog.csdn.net/mydo/article/details/141754284 浏览次数:0次

在这里插入图片描述

概述

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

在这里插入图片描述

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

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

  • 概述
  • 3. SwiftData 如何在后台改变数据?
  • 4. 如何将后台的更改同步到界面中?
  • 总结

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

Let‘s dive in!!!😉


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

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

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

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

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

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

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

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:

struct ContentView: View {@Environment(\.modelContext) var modelContext@Query var items: [Item]@State var refreshID = falsevar body: some View {NavigationStack {VStack {List(items) { item inText(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 数据变化了。


更多与 SwiftUI 界面刷新相关内容的介绍,请小伙伴们移步如下链接观赏:

  • Xcode 15 预览 SwiftUI 视图中 @FetchRequest 查询结果不能正确刷新的解决
  • SwiftUI 如何动态条件显示和隐藏 Toolbar 按钮且不做无谓刷新
  • SwiftUI 如何快速识别视图(View)界面的刷新是由哪个状态的改变导致的?
  • SwiftUI 后台刷新多个 Section 导致 global index in collection view 与实际不匹配问题的解决
  • iOS 16 中 CoreData 托管对象发生变化但其衍生 (Derived) 属性在 SwiftUI 中不刷新的解决
  • SwiftUI上下文菜单(Context Menu)内容不随对象状态刷新的解决

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

答案是肯定的!

总结

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

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

感谢观赏,再会 😎

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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: