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

使用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仍会持续运行,占用内存资源。

解决方案

  1. 在对象销毁时取消Task
    给持有listenForStatus返回Task的类添加存储属性,并在deinit中取消任务:

    private var statusListenerTask: Task<Void, Error>?
    
    deinit {
        statusListenerTask?.cancel()
    }
    

    调用cancel()后,for await _ in Status.updates的循环会终止,序列订阅资源会被释放。

  2. 优化闭包内的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))
                }
            }
        }
    }
    
  3. 检查辅助方法的引用关系
    确认checkVerified和getPurchasedStatusDescription方法内部没有持有self或transaction的强引用,避免不必要的资源滞留。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 21:18:14