You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xcode更新后模拟器报错:UIViewControllerRepresentables需为值类型

Xcode更新后模拟器报错,物理设备正常的问题解析

问题详情

Xcode更新后,在新模拟器中运行App时抛出错误:
Thread 1: Fatal error: UIViewControllerRepresentables must be value types: SignInWithAppleToFirebase
但在物理设备上运行完全正常。需要明确两个核心问题:

  1. 物理设备能正常运行的原因是什么?
  2. 是否必须将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 11:05:29