Swift UI 被高频@StateObject 更新淹没? [英] Swift UI overwhelmed by high-frequency @StateObject updates?
问题描述
场景
一个简单的 SwiftUI App
,由一个带有两个选项卡的 TabView
组成.App
结构有一个 @StateObject
属性,它被 simulateFastStateUpdate
重复且非常快速(每秒 30 次)更新.
A simple SwiftUI App
that consists of a TabView
with two tabs. The App
struct has a @StateObject
property, which is being repeatedly and very quickly (30 times per second) updated by simulateFastStateUpdate
.
在这个例子中,simulateFastStateUpdate
没有做任何有用的工作,但它非常类似于一个快速更新应用程序状态的真实函数.该函数在短时间内对后台队列进行一些工作,然后在主队列上安排状态更新.例如,在使用相机 API 时,应用可能会以每秒 30 次的频率更新预览图像.
In this example, simulateFastStateUpdate
is not doing any useful work, but it closely resembles a real function that quickly updates the app's state. The function does some work on a background queue for a short interval of time and then schedules a state update on the main queue. For example, when using the camera API, the app might update the preview image as frequently as 30 times per second.
问题
当应用程序运行时,TabView
不响应点击.它永久卡在第一个选项卡上.删除 liveController.message = "Nice"
行可以解决问题.
When the app is running, the TabView
does not respond to taps. It's permanently stuck on the first tab. Removing liveController.message = "Nice"
line fixes the issue.
- 为什么
TabView
卡住了? - 为什么更新
@StateObject
会导致此问题? - 如何调整这个简单的例子,让
TabView
不卡住?
- Why is
TabView
stuck? - Why is updating
@StateObject
causing this issue? - How to adapt this simple example, so that the
TabView
is not stuck?
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 更新并且无法处理触摸输入(也在主线程上收到)).
You are blocking the main thread with these constant updates and the app is busy processing your UI updates and can't handle touch inputs (also received on the main thread).
任何创建这种快速事件流的东西都需要加以限制.您可以使用Combine 的throttle
或debounce
功能来减少UI 更新的频率.
Whatever creates this rapid event stream needs to be throttled. You can use Combine's throttle
or debounce
functionality to reduce the frequency of your UI updates.
看看这个示例,我添加了类 UpdateEmittingComponent
用 Timer
生成更新.这可能是您的后台组件快速更新.
Look at this sample, I added the class UpdateEmittingComponent
producing updates with a Timer
. This could be your background component updating rapidly.
在您的 LiveController
中,我正在观察使用 Combine 的结果.在那里,我在管道中添加了一个 throttle
,这将导致 message
发布者通过丢弃所有中间值每秒触发一次.
In your LiveController
I'm observing the result with Combine. There I added a throttle
into the pipeline which will cause the message
publisher to fiere once per second by dropping all in-between values.
移除 throttle
将导致 TabView
无响应.
Removing the throttle
will end up in an unresponsive 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")
}
}
}
}
}
这篇关于Swift UI 被高频@StateObject 更新淹没?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!