SwiftUI:如何从ObservableObject访问AppDelegate数据及最优方案?
Great question! Relying on global static properties can lead to tight coupling and make your code harder to test, so it's smart to look for better alternatives. Here are a few clean, maintainable approaches to get your FCM token into your AppState class:
1. Inject AppState into AppDelegate (Weak Reference)
This is the most direct approach. We'll pass a weak reference of your AppState to the AppDelegate so it can call methods on it directly when the FCM token is received.
Step 1: Update AppDelegate to hold a weak reference to AppState
import UIKit import FirebaseMessaging class AppDelegate: NSObject, UIApplicationDelegate, MessagingDelegate { // Use weak to avoid retain cycles weak var appState: AppState? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { FirebaseApp.configure() Messaging.messaging().delegate = self // Request push notification permissions if needed UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { _, _ in } application.registerForRemoteNotifications() return true } func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { guard let token = fcmToken else { return } // Pass the token directly to AppState appState?.updateFCMToken(token) } }
Step 2: Inject AppState into AppDelegate from your SwiftUI App
import SwiftUI @main struct ExampleApp: SwiftUI.App { @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate @StateObject var appState = AppState() init() { // Pass the AppState reference to the delegate delegate.appState = appState } var body: some Scene { WindowGroup { ContentView() .environmentObject(appState) } } }
Step 3: Add the token-handling method to AppState
import Foundation import Combine class AppState: ObservableObject { @Published var fcmToken: String? func updateFCMToken(_ token: String) { fcmToken = token // Send the token to your server here sendTokenToServer(token) } private func sendTokenToServer(_ token: String) { // Implement your API call logic } }
2. Use NotificationCenter for Decoupled Communication
If you prefer a more decoupled approach (so AppDelegate doesn't need a direct reference to AppState), you can use NotificationCenter to broadcast the token when it's received.
Step 1: Define a notification name
extension Notification.Name { static let fcmTokenReceived = Notification.Name("FCMTokenReceived") }
Step 2: Post the notification from AppDelegate
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { guard let token = fcmToken else { return } NotificationCenter.default.post( name: .fcmTokenReceived, object: nil, userInfo: ["token": token] ) }
Step 3: Listen for the notification in AppState
class AppState: ObservableObject { @Published var fcmToken: String? private var cancellables = Set<AnyCancellable>() init() { // Subscribe to the notification NotificationCenter.default.publisher(for: .fcmTokenReceived) .compactMap { $0.userInfo?["token"] as? String } .sink { [weak self] token in self?.fcmToken = token self?.sendTokenToServer(token) } .store(in: &cancellables) } // ... sendTokenToServer method as before }
Why Avoid Global Static Properties?
You're right to question this practice:
- Tight coupling: Static properties create hard dependencies between components, making it harder to modify or test parts of your code in isolation.
- Memory risks: Static properties live for the entire app lifecycle, which can lead to unintended memory retention if not managed carefully.
- State inconsistency: If your
AppStateis ever reinitialized (unlikely with@StateObject, but possible in edge cases), the static property might hold stale data.
Both approaches above avoid these issues while keeping your code clean and maintainable. The first approach is more direct, while the second gives you looser coupling—choose whichever fits your app's architecture best!
内容的提问来源于stack exchange,提问作者ambassador

