返回

SwiftUI中在 @Observable 类访问 AppDelegate 的正确方法

IOS

如何在 @Observable 类中访问 AppDelegate

在 SwiftUI 应用开发中,使用 @Observable 宏来管理状态数据越来越常见。不过,有时我们需要在使用了 @Observable 的类中访问 AppDelegate,这可能会引发一些问题。 本文会讨论这个问题,并给出可行的解决方案。

问题所在

当你尝试直接在标记为 @Observable 的类中使用 UIApplication.shared.delegate as! AppDelegate 获取 AppDelegate 时,应用可能会崩溃。 这是因为在使用了 @UIApplicationDelegateAdaptor 后,UIApplication.shared.delegate 返回的是 SwiftUI 内部管理的代理,而不是我们自定义的 AppDelegate 实例。 类型转换因此失败。简单来说,你使用 @UIApplicationDelegateAdaptor 创建的是由 SwiftUI 管理的一个新代理,不是应用根结构体实例化的代理。而通过 UIApplication.shared.delegate 拿到的则是 SwiftUI 管理的那个新代理。两者不兼容,转换自然失败。

解决方案

这里介绍几种可以解决这个问题的方案,你可以根据应用的具体情况选择合适的方案。

1. 使用环境对象(EnvironmentObject)

这种方案是通过 SwiftUI 的环境来传递 AppDelegate 实例,它允许你从视图层深入到数据模型层访问到相同的实例。这避免了通过 UIApplication.shared.delegate 直接获取而可能导致的类型不匹配问题。

操作步骤:

  1. MyApp 结构体中将 AppDelegate 作为环境对象传递。
  2. AuthModel 类中使用 @EnvironmentObject 来获取 AppDelegate 的实例。

代码示例:

首先,修改 MyApp

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
    
    var body: some Scene {
        WindowGroup {
           ContentView()
              .environmentObject(appDelegate)
        }
    }
}

然后,修改 AuthModel

import SwiftUI

@Observable
class AuthModel {
    @EnvironmentObject var appDelegate: AppDelegate

    func login() async {
         await withCheckedContinuation { continuation in
            appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in
                continuation.resume()
           }
         }
    }
}

原理:

通过 .environmentObject(appDelegate),我们让视图层级的任何视图都能够访问到同一个 AppDelegate 的实例。在 AuthModel 中,@EnvironmentObject 将自动获取最近传递的 AppDelegate 实例, 避免了通过 UIApplication.shared 获取时出现类型转换错误。这种方式使依赖注入更加清晰,并能确保在整个应用中都访问到唯一的 AppDelegate 实例。

2. 使用单例模式 (Singleton)

另一种选择是将 AppDelegate 设计成一个单例。这样,你可以在应用的任何地方直接访问它,确保你始终使用相同的 AppDelegate 实例。

操作步骤:

  1. AppDelegate 类中创建静态的共享实例属性。
  2. 将初始化方法设置为私有,防止外部创建多个实例。
  3. 从任何其他地方通过共享实例访问。

代码示例:

修改 AppDelegate:

class AppDelegate: NSObject, UIApplicationDelegate {
    static let shared = AppDelegate()
    private override init(){}

    var currentAuthorizationFlow: OIDExternalUserAgentSession?
}

AuthModel中获取AppDelegate实例:

@Observable
class AuthModel {
    func login() async {
      let appDelegate = AppDelegate.shared
        await withCheckedContinuation { continuation in
            appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in
                 continuation.resume()
            }
         }
     }
}

原理:

单例模式通过 static let shared = AppDelegate() 保证了应用中只有一个 AppDelegate 实例。 使用私有的初始化函数 private override init(){} 禁止从外部初始化更多的实例。 使用AppDelegate.shared 获取唯一实例确保了类型的一致性和全局访问,这样就能直接访问 AppDelegate 中的 currentAuthorizationFlow 属性。 这适合需要在应用的多个地方访问同一个对象的情况。

3. 依赖注入(Dependency Injection)

如果你的应用采用更为灵活的架构模式,可以考虑使用依赖注入。 这样做的好处是可以在测试期间轻松的更换实例,并且减少组件之间的依赖,从而提升应用的可维护性。

操作步骤:

  1. AppDelegate作为参数注入到 AuthModel中.
  2. ContentView 构造 AuthModel 的实例.
  3. 传入环境中的 AppDelegate 对象。

代码示例:

修改AuthModel:

@Observable
class AuthModel {
    
    var appDelegate: AppDelegate

    init(appDelegate: AppDelegate) {
         self.appDelegate = appDelegate
    }

    func login() async {
        await withCheckedContinuation { continuation in
            appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in
                  continuation.resume()
           }
        }
     }
}

MyApp 注入:

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate

    var body: some Scene {
         WindowGroup {
            ContentView(authModel: AuthModel(appDelegate: appDelegate))
         }
      }
}

修改 ContentView :

struct ContentView: View {
    @ObservedObject var authModel: AuthModel

    var body: some View {
         // other Views
        Button("Login") {
              Task{
                  await authModel.login()
              }
         }
     }
}

原理:

依赖注入方法将 AppDelegate 作为 AuthModel 的依赖项传递。 此举可以使类与其依赖的具体实例分离,提供了更强的可测试性和代码灵活性,同时确保类只访问由外部注入的单例实例。 构造器负责接收 AppDelegate 实例。 这样可以防止硬编码对特定 AppDelegate 的依赖。 这提供了灵活的代码重用性,使得不同的环境中使用不同实例更加简单。

安全性建议

无论你选择哪个方案,重要的是确保 AppDelegate 的生命周期与应用的生命周期一致。 不要随意创建新的实例。

在涉及到外部库交互或需要保证应用行为一致性的情况下,使用环境对象或者单例模式都可以较好地避免 UIApplication.shared.delegate 潜在的风险。

总结

访问 @Observable 类中的 AppDelegate 需要考虑 Swift 和 SwiftUI 之间的交互细节。上述方案提供了不同的视角和方法来解决问题。选择哪一种方案取决于项目的特定需求和开发偏好。 环境对象或依赖注入方法通常被认为是更安全且更灵活的。