SwiftUI异步处理通知授权触发Alert的实现问题求助
问题概述
- 开发依赖
UserNotifications的iOS提醒类App(Swift 5.1 + iOS 13.3),无通知权限则无法正常使用 - 用户更新
Reminder实例触发保存操作时,需重新检查通知权限;未授权则弹出Alert引导前往设置(系统首次权限请求仅弹出一次,需自行触发Alert) - 实现时遇到两个核心问题:
- 在
App的init闭包、异步上下文中修改@State变量触发Alert时,出现「逃逸闭包捕获mutating 'self'参数」编译错误 - 重构后出现主线程外修改绑定变量的运行时紫色警告
- 在
问题代码
import SwiftUI import Combine import UserNotifications class NotificationManager: NSObject, UNUserNotificationCenterDelegate { // ObservableObject static let shared = NotificationManager() override private init() {} // #MARK - UNNotificationCenterDelegate methods func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) { print(center) } func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { print(response) completionHandler() } } @main struct ReminderApp: App { @ObservedObject private var reminder: Reminder // @ObservedObject private var notificationManager = NotificationManager.shared @MainActor @State var shouldRequestAuthorization: Bool = false private var cancellables = Set<AnyCancellable>() private var persistAction: (() -> Void)? = nil init() { let reminder = Reminder.loadOrCreate() self._reminder = ObservedObject(wrappedValue: reminder) let notificationCenter = UNUserNotificationCenter.current() notificationCenter.delegate = NotificationManager.shared notificationCenter.removeAllDeliveredNotifications() // TODO: trigger this more func requestAuthorization(completion: @escaping (Bool) -> ()) async throws { // TODO: Not re-appearing after first refusal, link user to settings in Alert.. Task { let authorized = try await notificationCenter.requestAuthorization(options: [.sound, .alert]) completion(authorized) } } self.persistAction = { try! reminder.save() // Escaping closure captures mutating self parameter Task { @MainActor in try await requestAuthorization { authorized in self.shouldRequestAuthorization = authorized } } notificationCenter.removeAllPendingNotificationRequests() if reminder.enabled { // Schedule notifications... } } reminder .objectDidChange .sink(receiveValue: persistAction!) .store(in: &cancellables) // // notificationManager // .objectDidChange // .sink(receiveValue: ...) // .store(in: &cancellables) } @Environment(\.scenePhase) private var scenePhase var body: some Scene { WindowGroup { HomeView() .environmentObject(reminder) .padding(Edge.Set(arrayLiteral: [.horizontal, .vertical]), 50) .alert(isPresented: $shouldRequestAuthorization) { let settingsButton = Alert.Button.default(Text("Go to Settings")) { guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { return } if UIApplication.shared.canOpenURL(settingsUrl) { UIApplication.shared.open(settingsUrl, completionHandler: { (success) in print("Settings opened: \(success)") // Prints true }) } } return Alert(title: Text("Notifications Permission Needed"), message: Text("Our app needs this to send reminders"), primaryButton: settingsButton, secondaryButton: .cancel()) } } .onChange(of: scenePhase) { phase in if phase == .background { // Perform cleanup when all scenes within // MyApp go to the background. } } } }
解决方案
1. 重构NotificationManager为ObservableObject
将权限状态管理逻辑封装到NotificationManager中,利用@Published变量控制Alert显示,避免在App结构体中直接处理异步逻辑:
import SwiftUI import Combine import UserNotifications class NotificationManager: NSObject, ObservableObject, UNUserNotificationCenterDelegate { static let shared = NotificationManager() @Published var shouldShowPermissionAlert = false override private init() { super.init() UNUserNotificationCenter.current().delegate = self } // 检查并处理通知权限状态 func checkNotificationAuthorization() async throws { let status = await UNUserNotificationCenter.current().notificationSettings().authorizationStatus switch status { case .notDetermined: // 首次请求权限,未授权则触发Alert let authorized = try await UNUserNotificationCenter.current().requestAuthorization(options: [.sound, .alert]) if !authorized { DispatchQueue.main.async { self.shouldShowPermissionAlert = true } } case .denied, .provisional, .ephemeral: // 已拒绝或临时权限,直接触发Alert DispatchQueue.main.async { self.shouldShowPermissionAlert = true } case .authorized: // 已授权,无需处理 break @unknown default: break } } // UNNotificationCenterDelegate实现 func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) {} func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { completionHandler() } }
2. 调整App结构体逻辑
遵循SwiftUI生命周期规范,移除init中的异步闭包,改用onChange监听Reminder变化,确保所有UI操作在主线程执行:
@main struct ReminderApp: App { @StateObject private var reminder = Reminder.loadOrCreate() @StateObject private var notificationManager = NotificationManager.shared private var cancellables = Set<AnyCancellable>() @Environment(\.scenePhase) private var scenePhase var body: some Scene { WindowGroup { HomeView() .environmentObject(reminder) .padding([.horizontal, .vertical], 50) .alert(isPresented: $notificationManager.shouldShowPermissionAlert) { Alert( title: Text("Notifications Permission Needed"), message: Text("Our app needs this to send reminders"), primaryButton: .default(Text("Go to Settings")) { guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else { return } if UIApplication.shared.canOpenURL(settingsUrl) { UIApplication.shared.open(settingsUrl) } }, secondaryButton: .cancel() ) } .onAppear { // App启动时检查权限 Task { try await notificationManager.checkNotificationAuthorization() } } } .onChange(of: reminder.objectDidChange) { _ in // Reminder更新时触发保存和权限检查 try! reminder.save() UNUserNotificationCenter.current().removeAllPendingNotificationRequests() if reminder.enabled { // 这里添加通知调度逻辑 } // 重新检查权限 Task { try await notificationManager.checkNotificationAuthorization() } } .onChange(of: scenePhase) { phase in if phase == .background { // 后台清理逻辑 } } } }
关键修正点
- 将
NotificationManager改为ObservableObject,用@Published变量管理Alert状态,避免在App的init中直接操作@State - 权限检查逻辑移至
NotificationManager内部,所有修改UI状态的操作通过DispatchQueue.main.async确保在主线程执行 - 移除
init中的persistAction闭包,改用onChange(of: reminder.objectDidChange)监听Reminder变化,解决逃逸闭包捕获self的问题 - 使用
@StateObject替代@ObservedObject初始化核心实例,符合SwiftUI的生命周期管理规范
内容的提问来源于stack exchange,提问作者Jason Newell
相关产品推荐
相关产品推荐

