Swift:具有未调用的完成处理程序的递归异步函数

Swift: Recursive async func with completion handler that does not get called

我需要以异步方式从端点获取大量数据。 API 端点一次提供预定义数量的数据。在第一个请求之后,我必须检查我是否从响应中得到 "next" url 并访问 link 以便继续下载。这种递归行为一直持续到所有可用数据都已提供为止,换句话说,就是分页功能 (HAL links)。此时我已经实现了一个递归下载的函数,但是:问题是最终的完成处理程序似乎没有被调用。

演示代码: ThingsApi 是一个 class,它封装了实际的 API 调用。重要的是这个 class 有一个初始的 url 并且在递归期间将获得特定的 url 以异步访问。我调用 downloadThings() 函数,需要在它完成时得到通知。如果我将递归排除在等式之外,它就会起作用。但是当递归在起作用时,什么都没有!

我创建了一个简化版本的代码来说明逻辑,可以直接粘贴到 Playground 中。 currentPage 和页面变量只是为了演示流程。最后的 print() 语句不会被调用。留下currentPage += 1遇到问题,设置currentPage += 6避免递归。显然,我在这里遗漏了一些基本概念。任何人?

import UIKit

let pages = 5
var currentPage = 0

class ThingsApi {
    var url: URL?
    var next: URL?

    init(from url: URL) {
        self.url = url
    }

    init() {
        self.url = URL(string: "https://whatever.org")
    }

    func get(completion: @escaping (Data?, HTTPURLResponse?, Error?) -> Void) {
        // *** Greatly simplified
        // Essentially: use URLSession.shared.dataTask and download data async.
        // When done, call the completion handler.

        // Simulate that the download will take 1 second.
        sleep(1)

        completion(nil, nil, nil)
    }
}

func downloadThings(url: URL? = nil, completion: @escaping (Bool, Error?, String?) -> Void) {
    var thingsApi: ThingsApi

    if let url = url {
        // The ThingsApi will use the next url (retrieved from previous call).
        thingsApi = ThingsApi(from: url)
    } else {
        // The ThingsApi will use the default url.
        thingsApi = ThingsApi()
    }

    thingsApi.get(completion: { (data, response, error) in
        if let error = error {
            completion(false, error, "We have nothing")
        } else {

            // *** Greatly simplified
            // Parse the data and save to db.

            // Simulate that the thingsApi.next will have a value 5 times.
            currentPage += 1
            if currentPage <= pages {
                thingsApi.next = URL(string: "https://whatever.org?page=\(currentPage)")
            }

            if let next = thingsApi.next {

                // Continue downloading things recursivly.
                downloadThings(url: next) { (success, error, feedback) in
                    guard success else {
                        completion(false, error, "failed")
                        return
                    }
                }

            } else {
                print("We are done")
                completion(true, nil, "done")
                print("I am sure of it")
            }

        }
    })
}

downloadThings { (success, error, feedback) in
    guard success else {
        print("downloadThings() failed")
        return
    }

    // THIS DOES NOT GET EXECUTED!
    print("All your things have been downloaded")
}

这似乎只是 "you forgot to call it yourself" 的一个例子:)

在此 if 语句中:

if let next = thingsApi.next {

    // Continue downloading things recursivly.
    downloadThings(url: next) { (success, error, feedback) in
        guard success else {
            completion(false, error, "failed")
            return
        }
    }

} else {
    print("We are done")
    completion(true, nil, "done")
    print("I am sure of it")
}

想想最外层调用downloadThings会发生什么,执行到if分支,下载成功。 completion 从未被调用!

您应该在 guard 语句之后调用 completion