【问题标题】:Completion handler never called从未调用过完成处理程序
【发布时间】:2017-12-28 22:06:26
【问题描述】:

我有这样的功能。

class func getAccountBalances(completionHandler:@escaping ((_ balances:Any) -> Void)){
//Alamofire request and we get the result. But sometimes the result fails.
    switch response.result {
    case .success(let value):
        completionHandler(value)
    case .failure(let error):
        print ("error is: \(error)")
}

如果失败,我不会使用代码来处理结果。那是一件坏事?如果调用失败,我是否需要一个完成处理程序,以便该函数不会留在内存中等待调用该完成处理程序?最佳做法是什么?

【问题讨论】:

  • 你的 response.result 是什么类型的对象,它是否在测试中为 .success(response.result) 返回 true?
  • JSON 并且通常它会返回 true,但有时会失败。我正在使用可编码来解析它。
  • @NevinJethmalani 将第二个参数 (Error?) 添加到您的完成处理程序。那天你真的需要返回Any 吗?如果是这样,请将其更改为(Any?, Error?),否则最好返回正确的对象类型Balance?
  • 有两个考虑:如果存在强引用循环的风险(你将这个闭包存储为参数并且永远不会释放它),那么绝对使用[weak self]。但这对 Alamofire 来说不是问题,因为它们在完成后会释放关闭。 (查看 Alamofire 页面,他们很少/从不使用 [weak self] 模式,因为它根本不需要。)另一个问题更简单:如果您发出请求并碰巧在请求完成之前关闭视图控制器,您关心吗如果它挂在视图控制器上直到请求完成?
  • ... 通常你不在乎,所以你不用理会[weak self]。但是如果你这样做了(例如,如果视图控制器占用了大量内存或者请求可能非常慢),那么无论如何你都可以使用[weak self] 让视图控制器在它被解除后立即被释放。所以 Radu 是正确的,您可以使用[weak self] 模式,但在 Alamofire 的情况下,这通常不是问题。我当然不希望您担心在这种特殊情况下使用[weak self] 来防止泄漏。但是,如果您愿意,可以使用该模式。

标签: swift swift4


【解决方案1】:

一般来说,对每个案例都调用完成是一种很好的做法。这样做的原因是您通常希望让上层(业务逻辑层)决定是否应该将某些余额(例如)标记为已保存,或者在发生错误时显示一个对话框。这是一个很好的实践,认为一切都应该是一个模块。话虽如此,如果另一个模块想要在某个时候调用相同的函数,那么让该模块处理结果可能是一个好主意。这可以通过多种方式实现,我不会进入这里,这是你的决定。

但是,这不是必须的。如果一个块不会被调用,它应该被释放,然后一切都是好的内存。因此,在您的示例中,如果您不将块保留在其他地方(例如将其保存在进行 getAccountBalances 调用的类中的变量中),您应该没问题。

另一个重要的部分是当你调用函数时要小心,不要在块内保留self 的地方造成内存泄漏:

getAccountBalances() { _ in 
   self.updateUI()
}

这个块将创建一个保留给自己,如果调用一切正常,但用户离开了屏幕,你最终可能会使用被释放的变量并使应用程序崩溃。这里的一个好的做法是不要在回调中保留 self,而是在此之前使其变弱:

getAccountBalances() { [weak self] _ in 
   // This is not necessarily needed, and you could use self?.updateUI() instead. 
   // However, this is usually another thing i like to do and consider it a good practice
   guard let `self` = self else { return }
   self.updateUI()
}

【讨论】:

  • 这太棒了!谢谢!您能否详细说明内存泄漏部分?我想我的代码中可能有这个问题。这里的最佳做法是什么?我应该怎么做才能避免泄漏?
  • @NevinJethmalani 让我知道是否应该提供更多详细信息。如果您在某种情况下需要更多帮助,我建议您发布整个场景
  • 澄清一下:Radu,我怀疑你知道这一点,但为了@NevinJethmalani 的利益,如果你不将[weak self] 模式与Alamofire 结合使用,它不会“泄漏”本身,但可能只是在视图控制器被关闭后等待几秒钟以释放视图控制器,只是等待请求完成。有时您需要[weak self] 模式(如果您只是更新UI),但在其他情况下您可能正在更新模型等,在这种情况下,您不想使用[weak self] 模式。这取决于你在这个闭包中做了什么。
【解决方案2】:

你的标题:Completion handler never called

您的代码:如果出现错误,则不会向完成处理程序返回任何值。

如果出现故障,您希望如何看到竞争处理程序的结果?你想让应用程序崩溃吗?这种方式更好,因为它可以处理两种情况:

class func getAccountBalances(completionHandler:@escaping ((_ balances:Any?) -> Void)){ //make Any an optional
//Alamofire request and we get the result. But sometimes the result fails.
    switch response.result {
    case .success(let value):
        completionHandler(value)
    case .failure(let error):
        print ("error is: \(error)")
        completionHandler(nil)
}

您对该函数的新调用:

getAccountBalances() { value in 
guard let _value = value else { // anticipate on the error return }
// use _value, the balances are inside.
}

另一种方法不是让它为零,而是向下转换它内部的值。看起来像这样:

class func getAccountBalances(completionHandler:@escaping ((_ balances:Any) -> Void)){
//Alamofire request and we get the result. But sometimes the result fails.
    switch response.result {
    case .success(let value):
        completionHandler(value)
    case .failure(let error):
        print ("error is: \(error)")
        completionHandler(error) //notice this change
}

那么你的函数应该是这样的:

getAccountBalances() { value in 
if let error = value as? Error { //or whatever type your error is in the function
  //there is an error
}
}

【讨论】:

  • 如果我不包含completionHandler(error) 会怎样?该功能是否保留在应用程序的内存中并在以后引起问题,还是可以这样做?如果出现错误,我不需要在主 VC 中执行任何操作。反正我只是忽略了这个错误。
  • @NevinJethmalani 你为什么还要这样做?您的函数将等待返回,但该返回永远不会到来。我想它会留在记忆中,但我真的不知道。但是我没有理由在转义完成处理程序中不返回值。
  • 是的,这就是我想知道的。没有理由。只是偶尔我会忘记处理错误,所以我想知道这是否是一个大问题,或者我是否应该返回并添加那行代码来处理错误。
  • 你的意思是Error。您不能将其转换为自己的实例。 if let error = value as? error { 总是会失败
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-18
  • 1970-01-01
  • 2014-08-16
  • 1970-01-01
  • 1970-01-01
  • 2019-12-07
相关资源
最近更新 更多