Swift UI被频繁的@StateObject更新淹没了



场景

一个简单的SwiftUIApp,由一个带有两个选项卡的TabView组成。App结构具有@StateObject属性,该属性由simulateFastStateUpdate重复且非常快速地(每秒30次(更新。

在这个例子中,simulateFastStateUpdate没有做任何有用的工作,但它非常类似于快速更新应用程序状态的真实功能。该函数在短时间内对后台队列执行一些工作,然后安排对主队列的状态更新。例如,当使用相机API时,应用程序可能会以每秒30次的频率更新预览图像。

问题

当应用程序运行时,TabView不会对敲击做出响应。它被永久地卡在第一个选项卡上。删除liveController.message = "Nice"行可以解决这个问题。

  1. 为什么TabView被卡住
  2. 为什么更新@StateObject会导致此问题
  3. 如何调整这个简单的例子,使TabView不被卡住
import SwiftUI
class LiveController: ObservableObject {
@Published var message = "Hello"
}
@main
struct LiveApp: App {
@StateObject var liveController = LiveController()
var body: some Scene {
WindowGroup {
TabView() {
Text(liveController.message)
.tabItem {
Image(systemName: "1.circle")
}
Text("Tab 2")
.tabItem {
Image(systemName: "2.circle")
}
}
.onAppear {
DispatchQueue.global(qos: .userInitiated).async {
simulateFastStateUpdate()
}
}
}
}

func simulateFastStateUpdate() {
DispatchQueue.main.async {
liveController.message = "Nice"
}

// waits 33 ms ~ 30 updates per second
usleep(33 * 1000)

DispatchQueue.global(qos: .userInitiated).async {
simulateFastStateUpdate()
}
}
}

您正在用这些持续更新阻塞主线程,应用程序正忙于处理UI更新,无法处理触摸输入(也在主线程上接收(。

任何创建这种快速事件流的东西都需要被抑制。您可以使用Combine的throttledebounce功能来降低UI更新的频率。

在这个示例中,我添加了UpdateEmittingComponent类,该类使用Timer生成更新。这可能是您的后台组件正在快速更新。

在您的LiveController中,我用Combine观察结果。在那里,我在管道中添加了一个throttle,这将导致message发布者每秒通过删除所有介于值之间的值来激活一次。

删除throttle将导致TabView没有响应。

import SwiftUI
import Combine
/// class simulating a component emitting constant events
class UpdateEmittingComponent: ObservableObject {
@Published var result: String = ""

private var cancellable: AnyCancellable?

init() {
cancellable = Timer
.publish(every: 0.00001, on: .main, in: .default)
.autoconnect()
.sink {
[weak self] _ in
self?.result = "(Date().timeIntervalSince1970)"
}
}
}
class LiveController: ObservableObject {
@Published var message = "Hello"

@ObservedObject var updateEmitter = UpdateEmittingComponent()
private var cancellable: AnyCancellable?

init() {
updateEmitter
.$result
.throttle(for: .seconds(1),
scheduler: RunLoop.main,
latest: true
)
.assign(to: &$message)
}
}
@main
struct LiveApp: App {
@StateObject var liveController = LiveController()
var body: some Scene {
WindowGroup {
TabView() {
Text(liveController.message)
.tabItem {
Image(systemName: "1.circle")
}
Text("Tab 2")
.tabItem {
Image(systemName: "2.circle")
}
}
}
}
}

相关内容

  • 没有找到相关文章

最新更新