Swift UI 被高频@StateObject 更新淹没? [英] Swift UI overwhelmed by high-frequency @StateObject updates?

查看:27
本文介绍了Swift UI 被高频@StateObject 更新淹没?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

场景

一个简单的 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.

  1. 为什么 TabView 卡住了?
  2. 为什么更新 @StateObject 会导致此问题?
  3. 如何调整这个简单的例子,让 TabView 不卡住?
  1. Why is TabView stuck?
  2. Why is updating @StateObject causing this issue?
  3. 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 的throttledebounce 功能来减少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.

看看这个示例,我添加了类 UpdateEmittingComponentTimer 生成更新.这可能是您的后台组件快速更新.

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆