swift 等待 urlsession 完成
swift wait until the urlsession finished
每次我调用这个 api https://foodish-api.herokuapp.com/api/ 我都会得到一张图片。我不想要一张图片,我需要其中的 11 张,所以我循环获取 11 张图片。
但是我不能做的是在循环结束后重新加载集合视图。
func loadImages() {
DispatchQueue.main.async {
for _ in 1...11{
let url = URL(string: "https://foodish-api.herokuapp.com/api/")!
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String : String]
print(json!["image"]!)
self.namesOfimages.append(json!["image"]!)
} catch {
print("JSON error: \(error.localizedDescription)")
}
}.resume()
}
}
self.collectionV.reloadData()
print("after resume")
}
如果你想在加载完所有 11 张图片后重新加载一张,你需要使用 DispatchGroup。添加一个 属性 创建一个组:
private let group = DispatchGroup()
然后修改你的 loadImages() 函数:
func loadImages() {
for _ in 1...11 {
let url = URL(string: "https://foodish-api.herokuapp.com/api/")!
group.enter()
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard let self = self else { return }
self.group.leave()
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String : String]
print(json!["image"]!)
self.namesOfimages.append(json!["image"]!)
} catch {
print("JSON error: \(error.localizedDescription)")
}
}.resume()
}
group.notify(queue: .main) { [weak self] in
self?.collectionV.reloadData()
}
}
一些描述:
- 在方法调用上 group.enter() 将被调用 11 次
- 每次完成图片下载后 group.leave() 将被调用
- 当 group.leave() 被调用的次数与 group.enter() 相同时,将调用您在 group.notify()
中定义的块
更多关于DispatchGroup
请注意,如果您需要同时下载不同组的图像,则需要处理创建和存储不同的 DispatchGroup 对象。
通常,当我们想知道一系列并发任务(例如这些网络请求)何时完成时,我们会使用 DispatchGroup
。在网络请求之前调用enter
,在completion handler中调用leave
,并指定一个notify
块,例如
/// Load images
///
/// - Parameter completion: Completion handler to return array of URLs. Called on main queue
func loadImages(completion: @escaping ([URL]) -> Void) {
var imageURLs: [Int: URL] = [:] // note, storing results in local variable, avoiding need to synchronize with property
let group = DispatchGroup()
let count = 11
for index in 0..<count {
let url = URL(string: "https://foodish-api.herokuapp.com/api/")!
group.enter()
URLSession.shared.dataTask(with: url) { data, response, error in
defer { group.leave() }
guard let data = data else { return }
do {
let foodImage = try JSONDecoder().decode(FoodImage.self, from: data)
imageURLs[index] = foodImage.url
} catch {
print("JSON error: \(error.localizedDescription)")
}
}.resume()
}
group.notify(queue: .main) {
let sortedURLs = (0..<count).compactMap { imageURLs[[=10=]] }
completion(sortedURLs)
}
}
就我个人而言,我使用 JSONDecoder
和 Decodable
类型来解析 JSON 响应,而不是 JSONSerialization
。 (此外,我发现键名 image
有点误导,所以我将其重命名为 url
以避免混淆,以明确它是图像的 URL ,而不是图像本身。)因此:
struct FoodImage: Decodable {
let url: URL
enum CodingKeys: String, CodingKey {
case url = "image"
}
}
另请注意,以上内容并未更新属性或重新加载集合视图。执行网络请求的例程不应同时更新模型或 UI。我会把它留给调用者,例如,
var imageURLs: [URL]?
override func viewDidLoad() {
super.viewDidLoad()
// caller will update model and UI
loadImages { [weak self] imageURLs in
self?.imageURLs = imageURLs
self?.collectionView.reloadData()
}
}
注:
不需要DispatchQueue.main.async
。这些请求已经 运行 异步。
将临时结果存储在局部变量中。 (而且因为 URLSession
使用串行队列,我们不必担心进一步同步。)
dispatch group notify
block, though, uses the .main
queue, so the caller can conveniently update properties and UI direct.
可能很明显,但我直接解析 URL
,而不是解析字符串并将其转换为 URL
.
同时获取结果时,您无法保证它们的完成顺序。因此,人们通常会在一些与顺序无关的结构(例如字典)中捕获结果,然后在将结果传回之前对结果进行排序。
在这种特殊情况下,顺序并不严格,但我在上面的示例中包含了这种先排序 return 模式,因为这通常是所需的行为。
无论如何,结果是:
每次我调用这个 api https://foodish-api.herokuapp.com/api/ 我都会得到一张图片。我不想要一张图片,我需要其中的 11 张,所以我循环获取 11 张图片。 但是我不能做的是在循环结束后重新加载集合视图。
func loadImages() {
DispatchQueue.main.async {
for _ in 1...11{
let url = URL(string: "https://foodish-api.herokuapp.com/api/")!
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String : String]
print(json!["image"]!)
self.namesOfimages.append(json!["image"]!)
} catch {
print("JSON error: \(error.localizedDescription)")
}
}.resume()
}
}
self.collectionV.reloadData()
print("after resume")
}
如果你想在加载完所有 11 张图片后重新加载一张,你需要使用 DispatchGroup。添加一个 属性 创建一个组:
private let group = DispatchGroup()
然后修改你的 loadImages() 函数:
func loadImages() {
for _ in 1...11 {
let url = URL(string: "https://foodish-api.herokuapp.com/api/")!
group.enter()
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard let self = self else { return }
self.group.leave()
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String : String]
print(json!["image"]!)
self.namesOfimages.append(json!["image"]!)
} catch {
print("JSON error: \(error.localizedDescription)")
}
}.resume()
}
group.notify(queue: .main) { [weak self] in
self?.collectionV.reloadData()
}
}
一些描述:
- 在方法调用上 group.enter() 将被调用 11 次
- 每次完成图片下载后 group.leave() 将被调用
- 当 group.leave() 被调用的次数与 group.enter() 相同时,将调用您在 group.notify() 中定义的块
更多关于DispatchGroup
请注意,如果您需要同时下载不同组的图像,则需要处理创建和存储不同的 DispatchGroup 对象。
通常,当我们想知道一系列并发任务(例如这些网络请求)何时完成时,我们会使用 DispatchGroup
。在网络请求之前调用enter
,在completion handler中调用leave
,并指定一个notify
块,例如
/// Load images
///
/// - Parameter completion: Completion handler to return array of URLs. Called on main queue
func loadImages(completion: @escaping ([URL]) -> Void) {
var imageURLs: [Int: URL] = [:] // note, storing results in local variable, avoiding need to synchronize with property
let group = DispatchGroup()
let count = 11
for index in 0..<count {
let url = URL(string: "https://foodish-api.herokuapp.com/api/")!
group.enter()
URLSession.shared.dataTask(with: url) { data, response, error in
defer { group.leave() }
guard let data = data else { return }
do {
let foodImage = try JSONDecoder().decode(FoodImage.self, from: data)
imageURLs[index] = foodImage.url
} catch {
print("JSON error: \(error.localizedDescription)")
}
}.resume()
}
group.notify(queue: .main) {
let sortedURLs = (0..<count).compactMap { imageURLs[[=10=]] }
completion(sortedURLs)
}
}
就我个人而言,我使用 JSONDecoder
和 Decodable
类型来解析 JSON 响应,而不是 JSONSerialization
。 (此外,我发现键名 image
有点误导,所以我将其重命名为 url
以避免混淆,以明确它是图像的 URL ,而不是图像本身。)因此:
struct FoodImage: Decodable {
let url: URL
enum CodingKeys: String, CodingKey {
case url = "image"
}
}
另请注意,以上内容并未更新属性或重新加载集合视图。执行网络请求的例程不应同时更新模型或 UI。我会把它留给调用者,例如,
var imageURLs: [URL]?
override func viewDidLoad() {
super.viewDidLoad()
// caller will update model and UI
loadImages { [weak self] imageURLs in
self?.imageURLs = imageURLs
self?.collectionView.reloadData()
}
}
注:
不需要
DispatchQueue.main.async
。这些请求已经 运行 异步。将临时结果存储在局部变量中。 (而且因为
URLSession
使用串行队列,我们不必担心进一步同步。)dispatch group
notify
block, though, uses the.main
queue, so the caller can conveniently update properties and UI direct.可能很明显,但我直接解析
URL
,而不是解析字符串并将其转换为URL
.同时获取结果时,您无法保证它们的完成顺序。因此,人们通常会在一些与顺序无关的结构(例如字典)中捕获结果,然后在将结果传回之前对结果进行排序。
在这种特殊情况下,顺序并不严格,但我在上面的示例中包含了这种先排序 return 模式,因为这通常是所需的行为。
无论如何,结果是: