Xcode更新后模拟器报错:UIViewControllerRepresentables需为值类型
Xcode更新后模拟器报错,物理设备正常的问题解析
问题详情
Xcode更新后,在新模拟器中运行App时抛出错误:Thread 1: Fatal error: UIViewControllerRepresentables must be value types: SignInWithAppleToFirebase
但在物理设备上运行完全正常。需要明确两个核心问题:
- 物理设备能正常运行的原因是什么?
- 是否必须将
SignInWithAppleToFirebase类重构为struct(重构过程存在问题,若为Xcode Bug则不想处理)
相关代码
final class SignInWithAppleToFirebase: UIViewControllerRepresentable { private var appleSignInDelegates: SignInWithAppleDelegates! = nil private let onLoginEvent: ((SignInWithAppleToFirebaseResponse) -> ())? private var currentNonce: String? init(_ onLoginEvent: ((SignInWithAppleToFirebaseResponse) -> ())? = nil) { self.onLoginEvent = onLoginEvent } func makeUIViewController(context: Context) -> UIViewController { let vc = UIHostingController(rootView: SignInWithAppleButton().onTapGesture(perform: showAppleLogin)) return vc as UIViewController } func updateUIViewController(_ uiView: UIViewController, context: Context) { } private func showAppleLogin() { let nonce = randomNonceString() currentNonce = nonce let appleIDProvider = ASAuthorizationAppleIDProvider() let request = appleIDProvider.createRequest() request.requestedScopes = [.fullName, .email] request.nonce = sha256(nonce) performSignIn(using: [request]) } private func performSignIn(using requests: [ASAuthorizationRequest]) { guard let currentNonce = self.currentNonce else { return } appleSignInDelegates = SignInWithAppleDelegates(window: nil, currentNonce: currentNonce, onLoginEvent: self.onLoginEvent) let authorizationController = ASAuthorizationController(authorizationRequests: requests) authorizationController.delegate = appleSignInDelegates authorizationController.presentationContextProvider = appleSignInDelegates authorizationController.performRequests() } private func randomNonceString(length: Int = 32) -> String { precondition(length > 0) let charset: Array<Character> = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._") var result = "" var remainingLength = length while remainingLength > 0 { let randoms: [UInt8] = (0 ..< 16).map { _ in var random: UInt8 = 0 let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random) if errorCode != errSecSuccess { fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)") } return random } randoms.forEach { random in if length == 0 { return } if random < charset.count { result.append(charset[Int(random)]) remainingLength -= 1 } } } return result } private func sha256(_ input: String) -> String { let inputData = Data(input.utf8) let hashedData = SHA256.hash(data: inputData) let hashString = hashedData.compactMap { return String(format: "%02x", $0) }.joined() return hashString } }
问题解析与解决方案
1. 物理设备正常运行的原因
SwiftUI的UIViewControllerRepresentable协议官方明确要求实现者必须是值类型(struct或enum),这是SwiftUI状态管理机制的核心规则。物理设备能正常运行的原因是:
- 旧版本Xcode或物理设备的iOS运行时对该规则的校验不够严格,保留了对类类型实现该协议的兼容;
- Xcode更新后,模拟器的运行时逻辑被强化,严格执行了协议的类型要求,因此触发了错误,而物理设备的运行时暂时未同步这个校验逻辑。
2. 是否需要重构为struct
这不是Xcode Bug,而是代码违反了SwiftUI的设计规范。如果继续使用类类型实现UIViewControllerRepresentable,后续可能会出现状态不一致、视图更新异常等隐藏问题。必须重构为struct,以下是重构方案:
重构核心思路
将原类中的可变状态(appleSignInDelegates、currentNonce)和代理逻辑转移到Coordinator中。Coordinator是UIViewControllerRepresentable专门设计用来管理引用类型状态和代理的类类型容器,完美适配SwiftUI的状态管理机制。
重构后的代码示例
struct SignInWithAppleToFirebase: UIViewControllerRepresentable { private let onLoginEvent: ((SignInWithAppleToFirebaseResponse) -> ())? init(_ onLoginEvent: ((SignInWithAppleToFirebaseResponse) -> ())? = nil) { self.onLoginEvent = onLoginEvent } // 用Coordinator管理代理和状态 class Coordinator: NSObject, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding { var currentNonce: String? var onLoginEvent: ((SignInWithAppleToFirebaseResponse) -> ())? init(onLoginEvent: ((SignInWithAppleToFirebaseResponse) -> ())?) { self.onLoginEvent = onLoginEvent } // 实现ASAuthorizationControllerDelegate方法 func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { // 迁移原SignInWithAppleDelegates中的授权成功处理逻辑 let response = SignInWithAppleToFirebaseResponse(/* 填充授权数据 */) onLoginEvent?(response) } func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { // 迁移原SignInWithAppleDelegates中的错误处理逻辑 let response = SignInWithAppleToFirebaseResponse(error: error) onLoginEvent?(response) } // 实现ASAuthorizationControllerPresentationContextProviding方法 func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { return UIApplication.shared.connectedScenes .filter { $0.activationState == .foregroundActive } .first(where: { $0 is UIWindowScene }) .flatMap({ $0 as? UIWindowScene })?.windows.first ?? UIWindow() } } func makeCoordinator() -> Coordinator { Coordinator(onLoginEvent: onLoginEvent) } func makeUIViewController(context: Context) -> UIViewController { let button = SignInWithAppleButton().onTapGesture { context.coordinator.showAppleLogin() } return UIHostingController(rootView: button) } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { // 无需额外更新逻辑 } } // 给Coordinator扩展登录相关方法 extension SignInWithAppleToFirebase.Coordinator { func showAppleLogin() { let nonce = randomNonceString() currentNonce = nonce let appleIDProvider = ASAuthorizationAppleIDProvider() let request = appleIDProvider.createRequest() request.requestedScopes = [.fullName, .email] request.nonce = sha256(nonce) performSignIn(using: [request]) } private func performSignIn(using requests: [ASAuthorizationRequest]) { guard currentNonce != nil else { return } let authorizationController = ASAuthorizationController(authorizationRequests: requests) authorizationController.delegate = self authorizationController.presentationContextProvider = self authorizationController.performRequests() } private func randomNonceString(length: Int = 32) -> String { precondition(length > 0) let charset: Array<Character> = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._") var result = "" var remainingLength = length while remainingLength > 0 { let randoms: [UInt8] = (0 ..< 16).map { _ in var random: UInt8 = 0 let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random) if errorCode != errSecSuccess { fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)") } return random } randoms.forEach { random in guard remainingLength > 0 else { return } if random < charset.count { result.append(charset[Int(random)]) remainingLength -= 1 } } } return result } private func sha256(_ input: String) -> String { let inputData = Data(input.utf8) let hashedData = SHA256.hash(data: inputData) let hashString = hashedData.compactMap { String(format: "%02x", $0) }.joined() return hashString } }
重构关键点
- 原
SignInWithAppleDelegates的代理逻辑直接迁移到Coordinator中,减少额外的类依赖; - 所有可变状态(
currentNonce)由Coordinator持有,避免struct的不可变性限制; - 通过
context.coordinator访问协调器的方法,确保状态和逻辑的一致性。
内容的提问来源于stack exchange,提问作者David Jarrin
相关产品推荐
相关产品推荐

