You need to enable JavaScript to run this app. SwiftUI中@Published/ObservableObject变量变更后视图未动态更新问题
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中@Published/ObservableObject变量变更后视图未动态更新问题

根本原因定位
  • 线程死锁导致最新权限状态读取失败
    你编写的同步获取通知设置的方法,依赖信号量阻塞当前线程等待系统回调返回。而UNUserNotificationCenter的所有回调默认都会派发到主队列执行。在权限请求完成的回调流程中,你通过DispatchQueue.main.sync把任务切到主线程后,立刻执行带阻塞等待逻辑的获取设置方法,此时主线程正卡在信号量等待逻辑上,永远无法执行主队列上派发的设置获取回调,最终只能超时返回旧的权限状态,根本拿不到用户完成授权操作后的最新值。
    这套同步阻塞写法在冷启动初始化阶段,因为主队列任务量少偶尔能正常返回值;但在系统权限弹窗交互完成的场景下,主队列正处理弹窗相关的系统任务,必然触发死锁。
  • 手动重写objectWillChange导致属性绑定失效
    你尝试手动补充事件触发逻辑时,在类内部重新声明了let objectWillChange = ObservableObjectPublisher()。这会直接覆盖ObservableObject协议对@Published属性的自动事件绑定能力——原本@Published属性值变更时会自动触发系统默认的objectWillChange事件通知视图刷新,重写后虽然你在willSet里手动补充了send逻辑,但受前面死锁问题影响,属性根本没有被赋入新值,自然无法触发视图更新。
  • 阻塞式读取系统异步API的写法本身不符合开发规范
    用信号量、线程休眠阻塞等待系统异步回调的写法,既会造成主线程无响应卡顿,也会在特定队列场景下触发不可预期的死锁,完全没必要为了同步读值牺牲稳定性。
可行的修复方案

彻底去掉所有阻塞式的同步读值逻辑,全链路使用系统API原生的异步回调完成状态读取与更新,核心调整点:

  • 移除手动重写的objectWillChange逻辑,保留@Published+ObservableObject的原生绑定实现即可
  • 去掉信号量、线程休眠这类阻塞逻辑,把通知设置的读取改成全异步流程
  • 权限请求回调触发后,预留极短延迟再拉取设置,避免系统未完成权限状态同步时读到旧值

修复后的核心代码参考:

class TestViewModel: ObservableObject {
    @Published var notificationSettings: UNNotificationSettings?
    
    init() {
        // 初始化时直接异步拉取权限状态
        fetchNotificationSettings()
    }
    
    func requestNotificationPermissions() {
        let permissionsToRequest: UNAuthorizationOptions = [.alert, .sound, .carPlay, .announcement, .badge]
        UNUserNotificationCenter.current().requestAuthorization(options: permissionsToRequest) { [weak self] _, _ in
            DispatchQueue.main.async {
                // 预留50ms等待系统完成权限状态本地同步
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
                    self?.fetchNotificationSettings()
                }
            }
        }
    }
    
    // 全异步的权限状态读取方法
    func fetchNotificationSettings() {
        UNUserNotificationCenter.current().getNotificationSettings { [weak self] settings in
            // 系统回调不一定在主线程,切主线程更新@Published属性
            DispatchQueue.main.async {
                self?.notificationSettings = settings
            }
        }
    }
}

配套的视图代码调整(处理设置值未加载完成的状态):

struct ContentView: View {
    @EnvironmentObject var theViewModel: TestViewModel
    
    var body: some View {
        VStack {
            if let settings = theViewModel.notificationSettings {
                Text(verbatim: "Notifications are: \(settings.authorizationStatus)")
                    .padding()
                    .onTapGesture {
                        if settings.authorizationStatus == .notDetermined {
                            theViewModel.requestNotificationPermissions()
                        }
                    }
            } else {
                Text("加载通知权限状态中...")
                    .padding()
            }
        }
    }
}

关键避坑提示:永远不要在后台队列通过DispatchQueue.main.sync切回主线程后,再执行带阻塞等待逻辑的方法。这种写法在主队列被其他系统任务占用时,会直接触发应用卡死,是iOS开发中的经典死锁场景。

内容的提问来源于stack exchange,提问作者Jordan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.09 00:24:02