You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何在主线程发布值?解决后台线程发布警告

问题:SwiftUI集成AppAuth时触发“后台线程发布变更”警告

我刚开始接触SwiftUI开发,用AppAuth库开发带登录功能的简单应用,已经实现了基础的登录、登出,也能通过获取到的access token发起请求。但重构代码时遇到了一条紫色警告:

"Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates."

简化代码:

MyApp.swift

@main
struct MyApp: App {
    @ObservedObject var authStore = AuthStore()

    var body: some Scene {
        WindowGroup {
            if authStore.isAuthenticated {
                MainView().environmentObject(authStore)
            } else {
                LoginView()
            }
        }
    }
}

AuthStore.swift

class AuthStore: ObservableObject {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    private var authState : OIDAuthState?
    
    @Published var isAuthenticated = false // <- Xcode在此行给出警告

    func login() {
        // 准备授权URL、clientID等必要认证数据
        self.appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: scene!.keyWindow!.rootViewController!) { authState, error in
            if let authState = authState {
                self.setAuthState(authState)
                print("Got authorization tokens. Access token: " +
                      "\(authState.lastTokenResponse?.accessToken ?? "nil")")
            } else {
                print("Authorization error: \(error?.localizedDescription ?? "Unknown error")")
                self.setAuthState(nil)
            }
        }
    }

    private func setAuthState(_ authState: OIDAuthState?) {
        if (self.authState == authState) {
            return;
        }
        
        self.authState = authState
        self.isAuthenticated = authState?.isAuthorized == true
    }
}

我试过给AuthStore类加@MainActor注解、用DispatchQueue.main.async包裹isAuthenticated的赋值、在MyApp中对authStore.isAuthenticated使用.receive(on:),但警告仍然存在。

解决方案

核心原因

AppAuth的认证回调闭包是在后台线程执行的,即便给AuthStore标记@MainActor,闭包本身的执行上下文还是后台线程,导致修改@Published属性时触发警告。

有效解决方法

方法1:在回调闭包中切换到主线程调用setAuthState

直接把闭包内的逻辑包裹在DispatchQueue.main.async中,确保所有修改@Published属性的操作都在主线程执行:

func login() {
    // 准备授权URL、clientID等必要认证数据
    self.appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: scene!.keyWindow!.rootViewController!) { authState, error in
        // 切换到主线程
        DispatchQueue.main.async {
            if let authState = authState {
                self.setAuthState(authState)
                print("Got authorization tokens. Access token: \(authState.lastTokenResponse?.accessToken ?? "nil")")
            } else {
                print("Authorization error: \(error?.localizedDescription ?? "Unknown error")")
                self.setAuthState(nil)
            }
        }
    }
}

方法2:给setAuthState标记@MainActor并通过Task调度

给修改状态的核心方法单独标记@MainActor,确保它必须在主线程执行,同时在回调闭包中用Task指定主线程上下文:

class AuthStore: ObservableObject {
    // ... 其他代码不变

    // 给setAuthState添加@MainActor注解
    private @MainActor func setAuthState(_ authState: OIDAuthState?) {
        guard self.authState != authState else { return }
        self.authState = authState
        self.isAuthenticated = authState?.isAuthorized == true
    }

    func login() {
        // 准备授权URL、clientID等必要认证数据
        self.appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: scene!.keyWindow!.rootViewController!) { authState, error in
            // 用Task指定主线程上下文执行
            Task { @MainActor in
                if let authState = authState {
                    self.setAuthState(authState)
                    print("Got authorization tokens. Access token: \(authState.lastTokenResponse?.accessToken ?? "nil")")
                } else {
                    print("Authorization error: \(error?.localizedDescription ?? "Unknown error")")
                    self.setAuthState(nil)
                }
            }
        }
    }
}

额外优化建议

在MyApp结构体中,应该用@StateObject替代@ObservedObject来持有AuthStore,因为@StateObject是SwiftUI中管理应用级ObservableObject的正确方式,避免对象被意外重建:

@main
struct MyApp: App {
    @StateObject var authStore = AuthStore() // 替换@ObservedObject

    var body: some Scene {
        WindowGroup {
            if authStore.isAuthenticated {
                MainView().environmentObject(authStore)
            } else {
                LoginView()
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 13:59:53