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
!
我需要以异步方式从端点获取大量数据。 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
!