SwiftUI中如何在主线程发布值?解决后台线程发布警告
我刚开始接触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

