Swift Package集成Apple登录时授权代理无回调问题
问题根因
该问题与是否传入UIViewController/UIWindow无直接关系,核心是ASAuthorizationController的生命周期管理特性:
ASAuthorizationController不会在调用performRequests()后被系统自动强持有,一旦实例引用计数归零被ARC回收,授权流程结束后将无法找到对应实例触发delegate回调- 主应用内相同代码可正常运行,是因为controller创建在UIViewController的事件方法(如按钮点击回调)中时,UIKit的运行时逻辑会让局部变量延迟释放,刚好撑到系统侧建立授权流程的临时持有链路;迁移到Swift Package后,方法执行结束立刻出作用域,局部创建的controller会被立即回收,此时用户还未完成授权操作,回调链路已经断开
- 观察到的「主应用实例化controller传入Swift Package即可正常运行」现象完全符合该逻辑:主应用侧创建的实例存活时间更长,刚好覆盖授权流程初始化的关键节点,后续系统会接管持有直到流程结束
可落地的SPM封装方案
不需要依赖主应用创建controller,所有逻辑可完全收敛在Swift Package内,仅需做对应调整即可:
- 强持有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 } }
- 显式实现展示上下文协议,指定弹窗锚点
实现ASAuthorizationControllerPresentationContextProviding协议,这是AuthenticationServices框架原生提供的指定展示上下文的接口,不需要依赖系统自动查找keyWindow,避免多Scene、SPM环境下弹窗位置异常:
extension SignInWithAppleViewModel: ASAuthorizationControllerPresentationContextProviding { public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { return targetWindow } }
- 回调中清理持有引用
在授权成功、失败的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
相关产品推荐
相关产品推荐

