取消完成块

Cancel a completion block

快速提问。

通常,我们进行网络调用并在获得响应时,我们return完成块中的数据如下

func someAPIcall(input: input, completion: (result: data) -> Void) {
    ...
    completion(data)
}

我使用下面的功能

someAPIcall(input) {
    (result: data) in
    print(result)
    // Update UI, etc
}

是否可以在以后的任何时间以某种方式取消完成块?比如说,如果我立即进行网络调用和 popViewController,但如果我取消 request,如果数据被 returned 到完成块,则执行完成任务。

是否有任何机制可以将 var 分配给闭包并稍后取消它?

我如何在需要时取消块的执行,比如 viewWillDisappear

我的猜测是您在完成块中强烈保留自我。如果你在捕获列表中传递一个对 self 的弱引用,你的操作将不会在视图控制器被释放时执行。

someAPIcall(input) { [weak self] result in
    guard let strongSelf = self else { return }

    strongSelf.label.text = ...
}

请注意,这仅在您在块中执行的任务是自行执行时才有效。 someAPIcall 仍然保持对完成块的引用,但是完成块对你的视图控制器有一个弱引用。 (从技术上讲,您可以使用 weak self 的值来检查是否执行其他任务)。

如果这还不够,您可以访问someAPIcall的实现,那么您可以添加一个cancel()方法(其他人有提到)将停止调用,并释放块。

你不一定要从存在中删除完成块,但由于它是你的完成块,你可以很容易地在它被调用时不做任何事情:

func someAPIcall(input: input, completion: (result: data) -> Void) {
    guard somethingOrOther else {return}
    // ...
    completion(data)
}

somethingOrOther 可能是 self 的 属性,或者(如您所知)您可能会检查 self 是否仍然存在。

这与 NSOperation 使用的机制没有太大区别,它可以在实际执行任何操作之前检查自己的 cancelled 属性。

有许多不同的方法可以做到这一点。正确答案取决于您的特定用例,以及您设计 API 交互的方式。 NSOperations 有一个很好的取消/依赖管理/完成工作流程,所以如果你能够将你的 API 交互放在 NSOperationQueue 中,这可能是最好的前进方式。我用于一些更简单的交互的另一种可能性是简单地保留对与视图控制器的特定视图交互相对应的 NSURLSessionTasks 的引用,并根据需要取消它们。例如:

//: Playground - noun: a place where people can play

import UIKit

class MyViewController: UIViewController, UISearchBarDelegate {

    var tasks = [NSURLSessionTask]()
    let client = MyAPIClient()

    deinit {
        cancelAllTasks()
    }

    func cancelAllTasks() {
        tasks.forEach { [=10=].cancel() }
    }

    func cancelAllSearchTasks() {
        tasks.filter({ [=10=].taskDescription == MyAPIClient.TaskDecription.search.rawValue }).forEach { [=10=].cancel() }
    }

    func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
        // Cancel previous search as user types a new search
        cancelAllSearchTasks()

        guard let text = searchBar.text else {
            return
        }

        tasks.append(client.search(text) { [weak self] results in
            // ...
        })
    }

}

class MyAPIClient {

    enum TaskDecription: String {
        case search
    }

    let session = NSURLSession()

    func search(text: String, completion: (result: [String]) -> Void) -> NSURLSessionTask {
        let components = NSURLComponents()
        components.scheme = "http"
        components.host = "myapi.com"
        components.queryItems = [NSURLQueryItem(name: "q", value: text)]
        guard let url = components.URL else {
            preconditionFailure("invalid search url")
        }

        let task = session.dataTaskWithURL(url) { (data, response, error) in
            // ...
            completion(result: ["results", "from", "api", "go", "here!"])
        }
        task.resume()
        task.taskDescription = TaskDecription.search.rawValue

        return task
    }
}

这里,当视图控制器被释放时,我们取消所有与这个控制器相关的NSURLSessionTasks。当用户在搜索栏中键入内容时,我们还会取消任何现有的 "search" api 任务,这样我们就不会进行 "stale" api 调用。

当然,这是一个相当简单的示例,但您明白了 - 明智地了解您的应用程序正在进行的网络调用数量并在不再需要时取消它们是件好事!

没有什么可以让您直接取消任何阻止。所以块在调用时执行。然而,该块当然可以执行代码来确定是否仍然需要它应该执行的任何操作。

通常你只会对块中的某个对象进行弱引用,如果该弱引用为 nil,则不会执行操作。在其他情况下,您会检查某个对象的某些 属性。一个总是有效的简单方法:创建一个简单的 class 和一个实例 属性 "cancelled"。创建并获取一个实例,让块引用它,当你想取消块时,将 "cancelled" 属性 设置为 true,然后回调检查该设置。 (同样,您可以将其设为弱引用,如果调用者不再感兴趣,他们可以放弃该实例)。