能否安全将AVContentKeySession的makeStreamingContentKeyRequestDataForApp改为同步?
FairPlay DRM: 复用持久密钥并优化异步密钥请求处理
首先直接给结论:你可以用信号量强制makeStreamingContentKeyRequestDataForApp的调用同步,但这不是最优解,甚至可能引发死锁或性能问题。更合理的方案是通过缓存请求队列的方式,让同一密钥的多个请求复用同一次服务器调用,全程异步处理,既节省服务器成本又避免线程阻塞。
一、信号量同步的可行性与潜在风险
你的代码思路是可行的,但存在几个需要修正的问题和风险:
- 代码错误:你代码里的变量名不一致(
sema和semaphore),而且在ARC环境下不需要dispatch_release——系统会自动管理信号量的内存。 - 死锁风险:如果
makeStreamingContentKeyRequestDataForApp的completion block和dispatch_semaphore_wait在同一个线程执行,会直接导致死锁。因为等待操作阻塞了线程,completion block永远无法执行来发送信号。 - 性能损耗:阻塞线程会占用系统线程资源,尤其是你同时发起多个下载任务时,可能导致线程池耗尽,拖慢整个应用的响应速度。
如果一定要用信号量,修正后的代码需要确保异步调用在独立线程执行:
-(void)handleOnlineRequest:(AVContentKeyRequest *)req { NSData *appCert = [self _getAppCertData]; NSData *assetId = [self _kidFromRequest:req]; __block NSData *contentKeyRequestData = nil; __block NSError *requestError = nil; dispatch_semaphore_t sema = dispatch_semaphore_create(0); // 切换到全局并发队列执行异步调用,避免completion block与等待操作同线程 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [req makeStreamingContentKeyRequestDataForApp:appCert contentIdentifier:assetId options:nil completion:^(NSData *data, NSError *error) { contentKeyRequestData = data; requestError = error; dispatch_semaphore_signal(sema); }]; }); dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); if (requestError) { [req respondWithError:requestError]; return; } // 继续向服务器请求CKC、生成并保存持久密钥,最后响应请求 [self _fetchCKCFromServerAndRespond:req withSPC:contentKeyRequestData]; }
二、更优的异步复用方案:缓存请求队列
针对你的场景(多音轨/多下载任务复用同一密钥),更合理的做法是维护一个正在处理的密钥请求队列,同一Asset ID的请求会等待首个请求完成后复用结果,全程异步无阻塞。
实现步骤:
- 定义缓存容器和串行队列(保证线程安全)
- 处理请求时,先检查是否已有同Asset ID的请求在处理
- 若有,将当前请求加入等待队列;若无,发起新的密钥请求
- 首个请求完成后,将结果同步给所有等待的请求,并清理缓存
示例代码:
// 类属性定义 @property (nonatomic, strong) NSCache *pendingKeyRequestsCache; @property (nonatomic, strong) dispatch_queue_t keyRequestSerialQueue; // 初始化方法 - (instancetype)init { self = [super init]; if (self) { _pendingKeyRequestsCache = [[NSCache alloc] init]; // 串行队列保证缓存操作的线程安全 _keyRequestSerialQueue = dispatch_queue_create("com.yourdomain.fairplay.keyqueue", DISPATCH_QUEUE_SERIAL); } return self; } // 处理AVContentKeyRequest的核心方法 - (void)processContentKeyRequest:(AVContentKeyRequest *)request { NSData *assetId = [self _kidFromRequest:request]; dispatch_sync(self.keyRequestSerialQueue, ^{ // 检查是否已有同Asset ID的请求在处理 NSMutableArray *pendingRequests = [self.pendingKeyRequestsCache objectForKey:assetId]; if (pendingRequests) { // 加入等待队列,等待首个请求完成 [pendingRequests addObject:request]; return; } // 无正在处理的请求,创建新的等待队列 pendingRequests = [NSMutableArray arrayWithObject:request]; [self.pendingKeyRequestsCache setObject:pendingRequests forKey:assetId]; NSData *appCert = [self _getAppCertData]; // 发起SPC请求 [request makeStreamingContentKeyRequestDataForApp:appCert contentIdentifier:assetId options:nil completion:^(NSData *spcData, NSError *spcError) { if (spcError) { // 错误处理:给所有等待的请求返回错误 dispatch_sync(self.keyRequestSerialQueue, ^{ for (AVContentKeyRequest *pendingReq in pendingRequests) { [pendingReq respondByRequestingRetry]; // 或根据需求返回具体错误 } [self.pendingKeyRequestsCache removeObjectForKey:assetId]; }); return; } // 向密钥服务器请求CKC并生成持久密钥 [self _fetchCKCFromServerWithSPC:spcData assetId:assetId completion:^(NSData *persistentKey, NSError *ckcError) { dispatch_sync(self.keyRequestSerialQueue, ^{ if (ckcError) { for (AVContentKeyRequest *pendingReq in pendingRequests) { [pendingReq respondWithError:ckcError]; } } else { // 保存持久密钥 [self _savePersistentKey:persistentKey forAssetId:assetId]; // 给所有等待的请求返回密钥 AVContentKeyResponse *keyResponse = [AVContentKeyResponse contentKeyResponseWithSecureKey:persistentKey]; for (AVContentKeyRequest *pendingReq in pendingRequests) { [pendingReq respondWithContentKeyResponse:keyResponse]; } } // 清理缓存 [self.pendingKeyRequestsCache removeObjectForKey:assetId]; }); }]; }]; }); }
这个方案的优势:
- 无线程阻塞:全程异步处理,不会占用线程资源导致性能下降
- 避免重复请求:同一Asset ID的请求只会发起一次服务器调用,大幅降低密钥服务器的成本
- 线程安全:通过串行队列保证缓存操作的原子性,避免竞态条件
- 容错性强:错误发生时,所有等待的请求都会得到统一处理,不会出现部分成功部分失败的情况
总结
优先选择异步缓存请求队列的方案,这是FairPlay密钥复用场景下的标准实践。如果因为特殊需求必须使用信号量,一定要注意线程切换,避免死锁风险。
内容的提问来源于stack exchange,提问作者Tom Hamming
相关产品推荐
相关产品推荐

