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

如何序列化访问并发调用会失效的Swift异步API?

Apple DCAppAttestService generateAssertion 并发缺陷及优化方案

问题描述

Apple 的 DCAppAttestService 中 generateAssertion(_:clientDataHash:completionHandler:) 方法存在并发安全缺陷:当多个异步任务同时调用该方法时,绝大多数情况下会返回 invalidKey(对应错误码3)的错误;但改为串行调用时,所有请求都能成功执行。

测试代码

func testConcurrentGenerateAssertionIsUnsafe() async throws {
    let service = DCAppAttestService.shared
    let attestationKey = try await service.generateKey()
    try await service.attestKey(attestationKey, clientDataHash: Data(SHA256.hash(data: Data())))
    
    let calls = 5
    let concurrentAssertResults = try await withThrowingTaskGroup(of: Data.self) { taskGroup -> [Data] in
        for i in 1...calls {
            taskGroup.addTask {
                do {
                    let result = try await service.generateAssertion(attestationKey, clientDataHash: Data())
                    print("Assertion result \(i) \(result)")
                    return result
                } catch {
                    print("Throwing error \(i) \(error)")
                    throw error
                }
            }
        }
        var assertResult = [Data]()
        for try await fetchKeyResult in taskGroup { assertResult.append(fetchKeyResult) }
        return assertResult
    }
}

典型错误输出

Throwing error 5 Error Domain=com.apple.devicecheck.error Code=3 "(null)"
Assertion result 4 140 bytes
Assertion result 3 142 bytes
Throwing error 1 Error Domain=com.apple.devicecheck.error Code=3 "(null)"
Throwing error 2 Error Domain=com.apple.devicecheck.error Code=3 "(null)"

优化解决方案

方案1:用 Actor 封装并发安全调用

Swift 的 Actor 是原生并发安全原语,能自动保证内部方法的串行执行,完美适配这类非线程安全的API:

actor AppAttestManager {
    private let service = DCAppAttestService.shared
    
    // 封装 generateAssertion,确保串行调用
    func generateSafeAssertion(_ keyId: String, clientDataHash: Data) async throws -> Data {
        return try await service.generateAssertion(keyId, clientDataHash: clientDataHash)
    }
    
    // 可按需封装其他相关方法
    func generateKey() async throws -> String {
        return try await service.generateKey()
    }
    
    func attestKey(_ keyId: String, clientDataHash: Data) async throws -> Data {
        return try await service.attestKey(keyId, clientDataHash: clientDataHash)
    }
}

使用示例:

func testSafeConcurrentCalls() async throws {
    let manager = AppAttestManager()
    let attestationKey = try await manager.generateKey()
    try await manager.attestKey(attestationKey, clientDataHash: Data(SHA256.hash(data: Data())))
    
    let calls = 5
    let results = try await withThrowingTaskGroup(of: Data.self) { group in
        for i in 1...calls {
            group.addTask {
                let result = try await manager.generateSafeAssertion(attestationKey, clientDataHash: Data())
                print("Assertion result \(i) \(result)")
                return result
            }
        }
        
        var assertResults = [Data]()
        for try await result in group {
            assertResults.append(result)
        }
        return assertResults
    }
    
    print("All \(results.count) calls succeeded")
}

方案2:用串行 DispatchQueue 包装调用

如果需要兼容更早的Swift版本,可使用GCD串行队列手动控制调用顺序:

class AppAttestQueueWrapper {
    private let service = DCAppAttestService.shared
    private let serialQueue = DispatchQueue(label: "com.yourdomain.appattest.serial")
    
    func generateAssertion(_ keyId: String, clientDataHash: Data) async throws -> Data {
        return try await withCheckedThrowingContinuation { continuation in
            serialQueue.async {
                Task {
                    do {
                        let result = try await self.service.generateAssertion(keyId, clientDataHash: clientDataHash)
                        continuation.resume(returning: result)
                    } catch {
                        continuation.resume(throwing: error)
                    }
                }
            }
        }
    }
}

方案优势对比

  • Actor方案:符合Swift现代并发编程范式,代码简洁、安全,无需手动管理队列调度,是最优选择。
  • 串行队列方案:兼容Swift 5.5之前的版本,但代码相对繁琐,需要手动处理异步续体。
  • 两种方案均比手动维护任务数组逐个执行更优雅,底层自动处理串行逻辑,减少出错概率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 10:55:14