使用StoreKit 2监听交易与订阅状态时是否存在内存泄漏?
StoreKit 2 内购内存泄漏问题排查与解决
初始问题
项目使用StoreKit 2实现内购功能,按照官方指引通过Task监听后台交易,代码如下:
private func listenForTransactions() -> Task<Void, Error> { Task.detached(priority: .background) { [weak self] in for await result in Transaction.updates { do { let transaction = try self?.checkVerified(result) await self?.updateCustomerProductStatus() await transaction?.finish() } catch { print("Failed transaction verification") } } } } private var updateListenerTask: Task<Void, Error>? = nil init() { updateListenerTask = listenForTransactions() //... }
调试器显示内存占用持续缓慢增长,即使应用处于后台也是如此。请问该现象是否正常?是否会引发内存问题?
更新:定位泄漏代码
通过Leaks Instrument工具排查后,确认存在内存泄漏,但问题出在另一段用于实时更新订阅状态的代码:
private func listenForStatus() -> Task<Void, Error> { Task.detached(priority: .background) { [weak self] in for await _ in Status.updates { do { print("Iterate Status") guard let product = self?.purchasedProduct, let currentEntitlement = await product.currentEntitlement else { break } let transaction = try self?.checkVerified(currentEntitlement) self?.purchasedStatusDescription = await self?.getPurchasedStatusDescription(of: transaction) await transaction?.finish() } catch { self?.result = .failed(.system(error)) } } } }
这段代码用于实时更新订阅状态描述文本并展示在视图上,尝试多种方法后,只有通过Status.updates才能及时获取状态信息,否则需重启应用才能刷新。现咨询该内存泄漏的成因及解决方案。
解答
泄漏成因分析
- 异步序列未终止:
Status.updates是无限异步序列,只要订阅存在就会持续发送更新。如果持有该Task的对象销毁时未取消任务,序列订阅会一直持有资源,导致相关对象无法释放。 - 隐式强引用:闭包内分散使用
self?.调用方法/属性时,可能因异步上下文捕获导致self被意外强引用,形成循环引用。 - 任务未主动检查取消状态:循环内未判断Task是否已取消,即使对象销毁,Task仍会持续运行,占用内存资源。
解决方案
在对象销毁时取消Task
给持有listenForStatus返回Task的类添加存储属性,并在deinit中取消任务:private var statusListenerTask: Task<Void, Error>? deinit { statusListenerTask?.cancel() }调用
cancel()后,for await _ in Status.updates的循环会终止,序列订阅资源会被释放。优化闭包内的
self引用
统一捕获weak self为strong self,避免分散调用导致的隐式强引用,同时在循环内检查任务取消状态:private func listenForStatus() -> Task<Void, Error> { Task.detached(priority: .background) { [weak self] in guard let self = self else { return } for await _ in Status.updates { // 检查任务是否取消,及时退出循环 guard !Task.isCancelled else { break } do { print("Iterate Status") guard let product = self.purchasedProduct, let currentEntitlement = await product.currentEntitlement else { break } let transaction = try self.checkVerified(currentEntitlement) self.purchasedStatusDescription = await self.getPurchasedStatusDescription(of: transaction) await transaction.finish() } catch { self.result = .failed(.system(error)) } } } }检查辅助方法的引用关系
确认checkVerified和getPurchasedStatusDescription方法内部没有持有self或transaction的强引用,避免不必要的资源滞留。
内容的提问来源于stack exchange,提问作者Land Fish
相关产品推荐
相关产品推荐

