在重构采用
ObservableObject
协议并使用 @Observable
宏标记类的类时,我在访问 AppDelegate
时遇到问题。
AppDelegate
看起来像这样。
class AppDelegate: NSObject, UIApplicationDelegate {
var currentAuthorizationFlow: OIDExternalUserAgentSession?
}
在我的应用程序的主结构中,委托由 SwiftUI 根据 Apple 文档中的示例进行实例化和管理。
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
var body: some Scene { ... }
}
在我正在重构的类中,
AppDelegate
再次被实例化。尽管Apple文档说它应该只实例化一次并且只能在应用程序的主要部分中实例化,但我没有遇到任何问题,一切正常。
class AuthModel: ObservableObject {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
func login() async {
await withCheckedContinuation { continuation in
appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in
continuation.resume()
}
}
}
}
我正在尝试重构
AuthModel
类以不再使用 @UIApplicationDelegateAdaptor
注释(宏?),因此 main 是 AppDelegate
实例化和管理的唯一位置。
class AuthModel: ObservableObject {
func login() async {
await withCheckedContinuation { continuation in
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: self) { authState, error in
continuation.resume()
}
}
}
}
这不起作用,应用程序在运行时在
let appDelegate = UIApplication.shared.delegate as! AppDelegate
线上崩溃,并显示错误消息 Could not cast value of type 'SwiftUI.AppDelegate' (0x1ee8bafe8) to 'MyApp.AppDelegate' (0x1052a1c80)
。如何从 AuthModel
类访问应用程序委托?
AuthModel
在整个应用程序中使用,主要是在视图中。登录视图就是一个例子。
struct LoginView: View {
@EnvironmentObject var authModel: AuthModel
var body: some View {
Button {
Task {
await authModel.login()
}
} label: {
Text("Log in")
}
.buttonStyle(.borderedProminent)
}
}
在我看来,您一直在从 AppAuth 文档中复制粘贴代码,但没有真正理解它。目前还不清楚你是否真的得到了一些东西。
据我所知,AppAuth 库似乎在设计时并未考虑到 SwiftUI。这有点过时了,需要连接到 AppDelegate。一个好的起点是了解如何在 SwiftUI 应用程序中执行此操作。这在
@UIApplicationDelegateAdaptor
的文档中得到了很好的解释。
使用 AppAuth 文档中的代码,这是一个简单的 SwiftUI 实现:
import SwiftUI
import AppAuth
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@EnvironmentObject private var appDelegate: AppDelegate
var body: some View {
Button {
appDelegate.login()
} label: {
Text("Login")
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject {
var currentAuthorizationFlow: OIDExternalUserAgentSession?
var authState: OIDAuthState?
func login() {
let authorizationEndpoint = URL(string: "https://accounts.google.com/o/oauth2/v2/auth")!
let tokenEndpoint = URL(string: "https://www.googleapis.com/oauth2/v4/token")!
let configuration = OIDServiceConfiguration(authorizationEndpoint: authorizationEndpoint, tokenEndpoint: tokenEndpoint)
let request = OIDAuthorizationRequest(configuration: configuration,
clientId: "<clientID>",
clientSecret: "<clientSecret>",
scopes: [OIDScopeOpenID, OIDScopeProfile],
redirectURL: URL(string: "<redirectURL>")!,
responseType: OIDResponseTypeCode,
additionalParameters: nil)
print("Initiating authorization request with scope: \(request.scope ?? "nil")")
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
let vc = scene!.keyWindow!.rootViewController!
currentAuthorizationFlow = OIDAuthState.authState(byPresenting: request, presenting: vc) { authState, error in
if let authState = authState {
self.authState = authState
print("Got authorization tokens. Access token: " +
"\(authState.lastTokenResponse?.accessToken ?? "nil")")
} else {
print("Authorization error: \(error?.localizedDescription ?? "Unknown error")")
self.authState = nil
}
}
}
func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if let authorizationFlow = self.currentAuthorizationFlow,
authorizationFlow.resumeExternalUserAgentFlow(with: url) {
self.currentAuthorizationFlow = nil
return true
}
return false
}
}
@UIApplicationDelegateAdaptor
只能用在一个地方,例如在 App
结构中:
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
如果你想先在其他地方访问它,你应该符合
ObservableObject
,例如
class AppDelegate: UIResponder, ObservableObject {
}
然后在您的
View
模型中您可以执行以下操作:
struct ContentView: View {
@EnvironmentObject var appDelegate: AppDelegate
如果您需要
AppDelegate
对象中的 @Observable
,那么您可以在 AppDelegate
内的惰性变量内初始化另一个对象并传入 weak self
,以便它可以在没有保留周期的情况下访问委托。
然后,如果您想在
View
模型中观察到这一点,那么在 body
中,您可以初始化一个子 View
模型并传入 appDelegate.someObservable
,然后它将使用 let someObservable
,然后如果您在 中访问其属性body
事实是@Observable
应该负责自动监控更新,例如
@Observable
class MyObject {
weak var appDelegate: AppDelegate?
init(appDelegate: AppDelegate? = nil) {
self.appDelegate = appDelegate
}
}
class AppDelegate: AppDelegateSuperclass, ObservableObject {
lazy var myObject: MyObject = MyObject(appDelegate: self)
}
struct SomeView: {
let myObject: MyObject
var body: some View {
Text(myObject.text) // body should be called when text changes
}
}
struct ContentView: View {
@EnvironmentObject var appDelegate: AppDelegate
var body: some View {
SomeView(myObject: appDelegate.myObject)
}
}
如您所见,管理对象有点像噩梦,因此最好在 Swift/SwiftUI 中尽可能坚持使用 func 和 structs。