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()
    }
}

一些描述:

  1. 在方法调用上 group.enter() 将被调用 11 次
  2. 每次完成图片下载后 group.leave() 将被调用
  3. 当 group.leave() 被调用的次数与 group.enter() 相同时,将调用您在 group.notify()
  4. 中定义的块

更多关于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)
    }
}

就我个人而言,我使用 JSONDecoderDecodable 类型来解析 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()
    }
}

注:

  1. 不需要DispatchQueue.main.async。这些请求已经 运行 异步。

  2. 将临时结果存储在局部变量中。 (而且因为 URLSession 使用串行队列,我们​​不必担心进一步同步。)

  3. dispatch group notify block, though, uses the .main queue, so the caller can conveniently update properties and UI direct.

  4. 可能很明显,但我直接解析 URL,而不是解析字符串并将其转换为 URL.

  5. 同时获取结果时,您无法保证它们的完成顺序。因此,人们通常会在一些与顺序无关的结构(例如字典)中捕获结果,然后在将结果传回之前对结果进行排序。

    在这种特殊情况下,顺序并不严格,但我在上面的示例中包含了这种先排序 return 模式,因为这通常是所需的行为。

无论如何,结果是: