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

Swift Package集成Apple登录时授权代理无回调问题

问题根因

该问题与是否传入UIViewController/UIWindow无直接关系,核心是ASAuthorizationController的生命周期管理特性:

  • ASAuthorizationController 不会在调用performRequests()后被系统自动强持有,一旦实例引用计数归零被ARC回收,授权流程结束后将无法找到对应实例触发delegate回调
  • 主应用内相同代码可正常运行,是因为controller创建在UIViewController的事件方法(如按钮点击回调)中时,UIKit的运行时逻辑会让局部变量延迟释放,刚好撑到系统侧建立授权流程的临时持有链路;迁移到Swift Package后,方法执行结束立刻出作用域,局部创建的controller会被立即回收,此时用户还未完成授权操作,回调链路已经断开
  • 观察到的「主应用实例化controller传入Swift Package即可正常运行」现象完全符合该逻辑:主应用侧创建的实例存活时间更长,刚好覆盖授权流程初始化的关键节点,后续系统会接管持有直到流程结束
可落地的SPM封装方案

不需要依赖主应用创建controller,所有逻辑可完全收敛在Swift Package内,仅需做对应调整即可:

  1. 强持有controller实例直到流程结束
    在负责处理Apple登录的类(如示例中的SignInWithAppleViewModel)中新增强引用属性存储controller,避免提前释放:
public final class SignInWithAppleViewModel: NSObject, ASAuthorizationControllerDelegate {
    // 强引用持有授权控制器,流程结束后置空释放
    private var authController: ASAuthorizationController?
    // 存储外部传入的锚点窗口
    private let targetWindow: UIWindow
    
    // 初始化时传入需要展示授权弹窗的窗口,和GoogleSignIn的调用习惯一致
    public init(targetWindow: UIWindow) {
        self.targetWindow = targetWindow
        super.init()
    }
    
    // 触发登录的方法
    public func startSignInFlow() {
        let provider = ASAuthorizationAppleIDProvider()
        let request = provider.createRequest()
        request.requestedScopes = [.fullName, .email]
        
        let controller = ASAuthorizationController(authorizationRequests: [request])
        controller.delegate = self
        controller.presentationContextProvider = self
        // 强持有实例,避免被提前回收
        self.authController = controller
        controller.performRequests()
    }
    
    deinit {
        authController = nil
    }
}
  1. 显式实现展示上下文协议,指定弹窗锚点
    实现ASAuthorizationControllerPresentationContextProviding协议,这是AuthenticationServices框架原生提供的指定展示上下文的接口,不需要依赖系统自动查找keyWindow,避免多Scene、SPM环境下弹窗位置异常:
extension SignInWithAppleViewModel: ASAuthorizationControllerPresentationContextProviding {
    public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        return targetWindow
    }
}
  1. 回调中清理持有引用
    在授权成功、失败的delegate回调中,处理完业务逻辑后将authController置空释放内存:
extension SignInWithAppleViewModel {
    public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        defer {
            // 无论凭证处理是否成功,流程结束后释放controller
            self.authController = nil
        }
        guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else {
            // 处理凭证类型不匹配错误
            return
        }
        // 处理credential中的userID、email、fullName、identityToken等业务逻辑
    }
    
    public func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        defer {
            self.authController = nil
        }
        // 处理授权错误,如用户取消、网络异常等
    }
}
避坑说明
  • 不要用局部变量存储ASAuthorizationController,无论在主应用还是SPM中,这种写法都依赖系统的延迟回收逻辑,在不同iOS版本、不同运行场景下会偶现不回调的问题
  • 不要尝试通过runtime、私有API绑定UIViewController,官方提供的ASAuthorizationControllerPresentationContextProviding已经完全满足自定义展示上下文的需求
  • 封装完成后,主应用侧只需要在调用时传入当前的keyWindow即可,所有授权逻辑完全在SPM内部闭环,和GoogleSignIn的使用体验无差异

内容的提问来源于stack exchange,提问作者Wesley

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 07:09:18