Swift Task Continuation误用:authenticateUser多次恢复续体报错
问题原因
- GKLocalPlayer.authenticateHandler的全局持久特性:这个handler是全局单例
GKLocalPlayer的属性,赋值后会被GameKit保留。当App软关闭重启后,用户的认证状态可能仍有效,此时设置handler会立即同步触发一次回调。 - Continuation重复Resume:你的代码未限制continuation仅能被resume一次,一旦GameKit多次调用
authenticateHandler(比如状态变化、重复设置handler触发的回调),就会导致同一个continuation被多次resume,触发Swift并发的致命错误。 - 视图重建导致重复调用:当ContentView因App重启、状态变化等原因重建时,
.task会再次执行authenticateUser,重复设置handler,进一步增加多次回调的概率。
解决办法
1. 限制Continuation仅Resume一次
在authenticateHandler的闭包中添加标志位,确保continuation只被resume一次:
func authenticateUser() async -> Bool { return await withCheckedContinuation { continuation in var isResumed = false GKLocalPlayer.local.authenticateHandler = { vc, error in guard !isResumed else { return } isResumed = true if let error { continuation.resume(returning: false) } else { GKAccessPoint.shared.isActive = GKLocalPlayer.local.isAuthenticated continuation.resume(returning: GKLocalPlayer.local.isAuthenticated) } } } }
2. 封装全局单例管理认证逻辑
由于GKLocalPlayer的handler是全局属性,频繁设置会覆盖之前的逻辑且易触发多次回调。建议封装到单例类中,确保handler仅初始化一次:
class GameKitAuthManager { static let shared = GameKitAuthManager() private let authStream = AsyncStream<Bool> { continuation in GKLocalPlayer.local.authenticateHandler = { vc, error in let isAuthenticated = GKLocalPlayer.local.isAuthenticated GKAccessPoint.shared.isActive = isAuthenticated continuation.yield(isAuthenticated) continuation.finish() // 完成后结束流,避免多次回调 } } private init() {} func authenticateUser() async -> Bool { // 先同步检查当前认证状态,直接返回结果 if GKLocalPlayer.local.isAuthenticated { GKAccessPoint.shared.isActive = true return true } // 等待认证流的结果 for await result in authStream { return result } return false } } // 视图中使用 struct ContentView: View { var body: some View { Text("hello") .task { let success = await GameKitAuthManager.shared.authenticateUser() // 处理认证结果 } } }
3. 优先同步检查认证状态
在发起异步认证前,先同步检查当前是否已认证,减少不必要的异步操作:
func authenticateUser() async -> Bool { // 先同步检查认证状态 if GKLocalPlayer.local.isAuthenticated { GKAccessPoint.shared.isActive = true return true } return await withCheckedContinuation { continuation in var isResumed = false GKLocalPlayer.local.authenticateHandler = { vc, error in guard !isResumed else { return } isResumed = true let isAuthenticated = GKLocalPlayer.local.isAuthenticated GKAccessPoint.shared.isActive = isAuthenticated continuation.resume(returning: isAuthenticated) } } }
关键注意事项
- 避免频繁设置全局handler:
GKLocalPlayer的handler是全局属性,建议封装到单例类中,确保仅初始化一次。 - 严格控制Continuation的Resume次数:CheckedContinuation要求只能被resume一次,必须通过标志位或流的
finish()方法避免重复调用。 - 优先同步检查状态:发起异步认证前先确认当前状态,减少不必要的异步流程触发。
内容的提问来源于stack exchange,提问作者Evan
相关产品推荐
相关产品推荐

