场景
一个简单的SwiftUIApp
,由一个带有两个选项卡的TabView
组成。App
结构具有@StateObject
属性,该属性由simulateFastStateUpdate
重复且非常快速地(每秒30次(更新。
在这个例子中,simulateFastStateUpdate
没有做任何有用的工作,但它非常类似于快速更新应用程序状态的真实功能。该函数在短时间内对后台队列执行一些工作,然后安排对主队列的状态更新。例如,当使用相机API时,应用程序可能会以每秒30次的频率更新预览图像。
问题
当应用程序运行时,TabView
不会对敲击做出响应。它被永久地卡在第一个选项卡上。删除liveController.message = "Nice"
行可以解决这个问题。
- 为什么
TabView
被卡住 - 为什么更新
@StateObject
会导致此问题 - 如何调整这个简单的例子,使
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的throttle
或debounce
功能来降低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")
}
}
}
}
}