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

Swift中取消URLSession任务后重启请求时获取到nil数据的问题排查

问题分析与解决方案

首先看你遇到的NSURLErrorDomain Code=-999 "cancelled"错误,这说明第二次请求被主动取消了。追踪代码逻辑后,问题的根源很明确:

问题出在哪?

看IAPResponseCheck里这段代码:

IAPStatusCheck(iapReceiptValidationFrom: iapReceiptValidationFrom) { isSuccessful in
    if isSuccessful == true {
        self.IAPTimer.invalidate()
        self.IAPTask?.cancel() // 这行代码是罪魁祸首!
        // ...其他弹窗逻辑
    }
}

再看IAPStatusCheck的实现,不管请求成功还是失败,它都会调用complition(true):

receiptValidation() { isPurchaseSchemeActive, error in
    if let err = error {
        self.onBuyProductHandler?(.failure(err))
    } else {
        self.onBuyProductHandler?(.success(isPurchaseSchemeActive))
    }
    complition(true) // 无论结果如何,都返回true
}

这就导致:当你点击重试发起新请求后,只要receiptValidation的回调触发(哪怕是请求刚完成就出错),IAPResponseCheck就会立刻取消当前的IAPTask——也就是你刚发起的重试请求,所以才会拿到cancelled错误,并且data为nil。


修复步骤

1. 修正IAPStatusCheck的回调逻辑

只有当请求无错误且验证完成时才返回true,出错时返回false:

func IAPStatusCheck(iapReceiptValidationFrom: IAPReceiptValidationFrom, complition: @escaping (_ isSuccessful: Bool)->()) {
    receiptValidation() { isPurchaseSchemeActive, error in
        if let err = error {
            self.onBuyProductHandler?(.failure(err))
            // 区分主动取消的错误,避免误处理
            if (err as NSError).code != NSURLErrorCancelled {
                // 可在这里处理其他非取消类错误
            }
            complition(false) // 出错时返回false
        } else {
            self.onBuyProductHandler?(.success(isPurchaseSchemeActive))
            complition(true) // 验证成功时返回true
        }
    }
}

2. 移除IAPResponseCheck中不必要的取消逻辑

任务完成后会自动释放,不需要手动取消,只有超时场景才需要取消任务:

func IAPResponseCheck(iapReceiptValidationFrom: IAPReceiptValidationFrom) {
    let infoDic: [String : String] = ["IAPReceiptValidationFrom" : iapReceiptValidationFrom.rawValue]
    self.IAPTimer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(self.IAPTimerAction), userInfo: infoDic, repeats: false)
    IAPStatusCheck(iapReceiptValidationFrom: iapReceiptValidationFrom) { isSuccessful in
        if isSuccessful == true {
            self.IAPTimer.invalidate()
            // 移除这行错误的取消代码!
            // self.IAPTask?.cancel()
            self.getTopVisibleViewController { topViewController in
                if let viewController = topViewController {
                    viewController.dismiss(animated: true, completion: nil)
                    self.hideActivityIndicator()
                }
            }
        } else {
            // 请求失败时清理定时器
            self.IAPTimer.invalidate()
        }
    }
}

3. 额外优化:用原生超时替代Timer(可选)

其实URLRequest本身支持设置超时时间,比手动管理Timer更精准,还能减少代码复杂度:

var storeRequest = URLRequest(url: storeURL)
storeRequest.httpMethod = "POST"
storeRequest.httpBody = requestData
storeRequest.timeoutInterval = 10.0 // 直接设置请求超时时间

之后在receiptValidation的回调里判断错误码:

if let err = error {
    if (err as NSError).code == NSURLErrorTimedOut {
        // 触发超时重试弹窗
        self.showAlertForRetryIAP(...)
    }
    // ...其他错误处理
}

这样就可以完全去掉Timer相关的代码了。


验证修复效果

修改后,点击重试发起的新请求不会被无故取消,正常请求会走完验证流程,超时则会正确触发重试弹窗,整个流程就能正常工作了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 13:59:08