SwiftUI中在 @Observable 类访问 AppDelegate 的正确方法
2025-01-29 20:00:50
如何在 @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
直接获取而可能导致的类型不匹配问题。
操作步骤:
- 在
MyApp
结构体中将AppDelegate
作为环境对象传递。 - 在
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
实例。
操作步骤:
- 在
AppDelegate
类中创建静态的共享实例属性。 - 将初始化方法设置为私有,防止外部创建多个实例。
- 从任何其他地方通过共享实例访问。
代码示例:
修改 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)
如果你的应用采用更为灵活的架构模式,可以考虑使用依赖注入。 这样做的好处是可以在测试期间轻松的更换实例,并且减少组件之间的依赖,从而提升应用的可维护性。
操作步骤:
- 将
AppDelegate
作为参数注入到AuthModel
中. - 从
ContentView
构造AuthModel
的实例. - 传入环境中的
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 之间的交互细节。上述方案提供了不同的视角和方法来解决问题。选择哪一种方案取决于项目的特定需求和开发偏好。 环境对象或依赖注入方法通常被认为是更安全且更灵活的。