I have a bit of a tricky severe hang in my app launch processing code path.
Here is the detail:
I have a .task
modifier from the main ContentView that calls into the signInWithAppleManager.checkUserAuth method, which is marked async.
I've tried wrapping the offending line in a Task block to get it off of the main thread, but it still hangs, and is still running on the main thread. Ironically, I found the hang after watching "Analyze Hangs With Instruments" from WWDC 23. However, at the point in the video towards the end where he discusses shared singletons, he mentions resolving a similar issue by making the shared singleton async, and then skips over how he would do it, kind of seemingly presenting a gap in analysis and debugging, while also explaining idle state ... kind of more irony.
Thanks in advance!
Task {
let appleIDProvider = ASAuthorizationAppleIDProvider()
Is there anything else that I can do to resolve this?
Here is the code:
public class SignInWithAppleManager: ObservableObject {
@Published public private(set) var userAuthenticationState: AuthState = .undefined
public static let shared = SignInWithAppleManager()
private init() { }
func signOutUser() async {
KeychainItem.deleteUserIdentifierFromKeychain()
await SignInWithAppleManager.shared.updateUserAuthenticationState(authState: .signedOut)
}
@MainActor
func userAuthenticated() async {
self.userAuthenticationState = .signedIn
}
@MainActor
func userSignedOut() async {
self.userAuthenticationState = .undefined
}
func simulateAuthenticated() async -> Bool {
return false
}
public var isAuthenticated: Bool {
return self.userAuthenticationState == .signedIn
}
@MainActor
func updateUserAuthenticationState(authState: AuthState) async {
debugPrint("Current authstate: \(self.userAuthenticationState) New auth state: \(authState)")
self.userAuthenticationState = authState
}
public func checkUserAuth() async -> AuthState {
debugPrint(#function)
//completion handler defines authstate
if KeychainItem.currentUserIdentifier == "" || KeychainItem.currentUserIdentifier == "simulator" {
debugPrint("User identifier is empty string")
await updateUserAuthenticationState(authState: .undefined)
//userid is not defined in User defaults bc empty, something went wrong
} else {
await updateUserAuthenticationState(authState: .signedIn)
}
if await !self.simulateAuthenticated() {
// HERE: ‼️ hangs for 2 seconds
let appleIDProvider = ASAuthorizationAppleIDProvider() // HERE: ‼️ hangs for 2 seconds
do {
let credentialState = try await appleIDProvider.credentialState(forUserID: KeychainItem.currentUserIdentifier)
switch credentialState {
case .authorized:
debugPrint("checkUserAuth:authorized")
// The Apple ID credential is valid. Show Home UI Here
await updateUserAuthenticationState(authState: .signedIn)
break
case .revoked:
debugPrint("checkUserAuth:revoked")
// The Apple ID credential is revoked. Show SignIn UI Here.
await updateUserAuthenticationState(authState: .undefined)
break
case .notFound:
debugPrint("checkUserAuth:notFound")
// No credential was found. Show SignIn UI Here.
await updateUserAuthenticationState(authState: .signedOut)
break
default:
debugPrint("checkUserAuth:undefined")
await updateUserAuthenticationState(authState: .undefined)
break
}
} catch {
// Handle error
debugPrint("checkUserAuth:error")
debugPrint(error.localizedDescription)
await updateUserAuthenticationState(authState: .undefined)
}
}
return self.userAuthenticationState
}
}