等到swift for循环,异步网络请求完成执行

Jos*_*osh 138 asynchronous nsoperation grand-central-dispatch swift

我希望for循环向firebase发送一堆网络请求,然后在方法完成执行后将数据传递给新的视图控制器.这是我的代码:

var datesArray = [String: AnyObject]()

for key in locationsArray {       
    let ref = Firebase(url: "http://myfirebase.com/" + "\(key.0)")
    ref.observeSingleEventOfType(.Value, withBlock: { snapshot in

        datesArray["\(key.0)"] = snapshot.value
    })
}
// Segue to new view controller here and pass datesArray once it is complete 
Run Code Online (Sandbox Code Playgroud)

我有几个问题.首先,我如何等待for循环完成并且所有网络请求都已完成?我无法修改observeSingleEventOfType函数,它是firebase SDK的一部分.另外,我是否会通过尝试从for循环的不同迭代中访问datesArray来创建某种竞争条件(希望这是有道理的)?我一直在阅读有关GCD和NSOperation的内容,但我有点失落,因为这是我构建的第一个应用程序.

注意:Locations数组是一个包含我需要在firebase中访问的键的数组.此外,重要的是异步触发网络请求.我只想等到所有异步请求完成后再将datesArray传递给下一个视图控制器.

pau*_*lvs 290

您可以使用调度组在所有请求完成时触发异步回调.

这是Swift 4.1中的一个示例(也适用于Swift 3),当多个网络请求全部完成时,使用调度组异步执行回调.

override func viewDidLoad() {
    super.viewDidLoad()

    let myGroup = DispatchGroup()

    for i in 0 ..< 5 {
        myGroup.enter()

        Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
            print("Finished request \(i)")
            myGroup.leave()
        }
    }

    myGroup.notify(queue: .main) {
        print("Finished all requests.")
    }
}
Run Code Online (Sandbox Code Playgroud)

产量

Finished request 1
Finished request 0
Finished request 2
Finished request 3
Finished request 4
Finished all requests.
Run Code Online (Sandbox Code Playgroud)

对于使用旧版Swift 2.3的用户,这里有一个使用其语法的示例:

override func viewDidLoad() {
    super.viewDidLoad()

    let myGroup = dispatch_group_create()

    for i in 0 ..< 5 {
        dispatch_group_enter(myGroup)
        Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
            print("Finished request \(i)")
            dispatch_group_leave(self.myGroup)
        }
    }

    dispatch_group_notify(myGroup, dispatch_get_main_queue(), {
        print("Finished all requests.")
    })
}
Run Code Online (Sandbox Code Playgroud)

  • @Josh关于竞争条件:如果从不同线程访问相同的内存位置,则发生竞争条件,其中至少一次访问是写入 - 不使用同步。不过,同一串行调度队列中的所有访问都是同步的。调度队列 A 上发生的内存操作也会发生同步,调度队列 A 会提交到另一个调度队列 B。然后,队列 A 中的所有操作都会在队列 B 中同步。因此,如果您查看解决方案,就会发现并不能自动保证访问同步。;) (2认同)
  • 这个答案在2018年5月对我有用:) (2认同)

Cha*_*nel 40

Xcode 8.3.1 - Swift 3

这是paulvs的公认答案,转换为Swift 3:

let myGroup = DispatchGroup()

override func viewDidLoad() {
    super.viewDidLoad()

    for i in 0 ..< 5 {
        myGroup.enter()
        Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
            print("Finished request \(i)")
            myGroup.leave()
        }
    }

    myGroup.notify(queue: DispatchQueue.main, execute: {
        print("Finished all requests.")
    })
}
Run Code Online (Sandbox Code Playgroud)


Tim*_*ess 30

斯威夫特3或4

如果您关心订单,请使用@ paulvs的答案,它完美无缺.

否则,如果有人想要按顺序获得结果而不是同时触发它们,这里是代码.

let dispatchGroup = DispatchGroup()
let dispatchQueue = DispatchQueue(label: "any-label-name")
let dispatchSemaphore = DispatchSemaphore(value: 0)

dispatchQueue.async {

    // use array categories as an example.
    for c in self.categories {

        if let id = c.categoryId {

            dispatchGroup.enter()

            self.downloadProductsByCategory(categoryId: id) { success, data in

                if success, let products = data {

                    self.products.append(products)
                }

                dispatchSemaphore.signal()
                dispatchGroup.leave()
            }

            dispatchSemaphore.wait()
        }
    }
}

dispatchGroup.notify(queue: dispatchQueue) {

    DispatchQueue.main.async {

        self.refreshOrderTable { _ in

            self.productCollectionView.reloadData()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Pra*_*tti 16

iOS 15+ 更新 (Swift 5.5)

我为 Swift 5.5 和 iOS 15+ 添加了一个更现代的解决方案,因为该工具链包括主要的URLSessionAPI 改进,这些改进并非特定于 Firebase 或 Alamofire。该代码使用async / awaitie Structured Concurrency。这是 Apple 在最新 iOS 版本 (iOS 13.0+) 上针对并发请求的建议。

DispatchGroup现在,我们用更少的代码行和更多的定制实现了与 s 相同的结果。这个答案将帮助那些曾经对URLSession请求进行排队并等待这些请求完成的用户。

任务组示例代码

TaskGroup如果我们有动态数量的请求(可变大小的数组),那么正确的工具是 a 。

func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] {
  var thumbnails: [String: UIImage] = [:]
  try await withThrowingTaskGroup(of: (String, UIImage).self) { group in
    for id in ids {
      group.addTask {
        return (id, try await fetchOneThumbnail(withID: id))
      }
    }
    for try await (id, thumbnail) in group {
      thumbnails[id] = thumbnail
    }
  }
  return thumbnails
}


func fetchOneThumbnail(withID id: String) async throws -> UIImage {
    // Just for demo purpose. In PROD, we may use dynamic URLs for each ID.
    guard let url = URL(string: "http://placekitten.com/200/300") else {
        throw ThumbnailError.invalidURL
    }
    // I have used `data(from: URL, delegate: URLSessionTaskDelegate? = nil)`
    // but we can also use `data(for: URLRequest, delegate: URLSessionTaskDelegate? = nil)`)`.
    // If we want to observe the delegate changes like when the
    // request fails, completes, or redirects, use the delegate param.
    // e.g. try await URLSession.shared.data(from: url, delegate: delegate)
    let result: (data: Data, response: URLResponse) = try await URLSession.shared.data(from: url)
    guard let image = UIImage(data: result.data) else {
        throw ThumbnailError.missingImageData
    }
    return image
}

enum ThumbnailError: Error {
    case invalidURL
    case missingImageData
}

Task {
    let images = try await fetchThumbnails(for: ["1", "2", "3"])
    // Show thumbnails in UI.
}
Run Code Online (Sandbox Code Playgroud)

这也使用for await循环(AsyncSequence)来等待任务完成。for try await是一个投掷的例子AsyncSequence。抛出语法是因为新的异步URLSession.data(for:)方法系列是抛出函数。

async let示例代码

async let语法适用于固定数量的请求。

let reqOne = urlRequest(for: keyOne) // Function that returns a unique URLRequest object for this key. i.e. different URLs or format.
async let (dataOne, _) = URLSession.shared.data(for: reqOne)

let reqTwo = urlRequest(for: keyTwo)
async let (dataTwo, _) = URLSession.shared.data(for: reqTwo)

guard let parsedData = parseInformation(from: try? await dataOne) else {
    // Call function to parse image, text or content from data.
      continue 
}
// Act on parsed data if needed.

guard let parsedDataTwo = parseInformation(from: try? await dataTwo) else {
    // Call function to parse image, text or content from data.
      continue 
}
// Act on the second requests parsed data if needed.

// Here, we know that the queued requests have all completed.
Run Code Online (Sandbox Code Playgroud)

await我不让请求立即完成的语法称为async let

此代码示例可以适用于可变大小的数组,但 Apple 不推荐。这是因为async let并不总是允许请求一到达就立即处理。

这种方法的好处是代码更清晰、更容易编写、更安全,并且可以避免死锁/线程问题。

笔记

TaskGroupand的确切语法async let将来可能会发生变化。目前,结构化并发在其早期版本中已经有了很大的改进,并且现在可以稳定用于生产。

Apple 已澄清,分组任务和异步任务的底层机制已基本确定(在 Swift Evolution 中得到批准)。一些语法更改的示例已经包括替换async {with Task {


Vas*_*huk 15

细节

Xcode 9.2,Swift 4

import Foundation

class SimultaneousOperationsQueue {
    typealias CompleteClosure = ()->()

    private let dispatchQueue: DispatchQueue
    private lazy var tasksCompletionQueue = DispatchQueue.main
    private let semaphore: DispatchSemaphore
    var whenCompleteAll: (()->())?
    private lazy var numberOfPendingActionsSemaphore = DispatchSemaphore(value: 1)
    private lazy var _numberOfPendingActions = 0

    var numberOfPendingTasks: Int {
        get {
            numberOfPendingActionsSemaphore.wait()
            defer { numberOfPendingActionsSemaphore.signal() }
            return _numberOfPendingActions
        }
        set(value) {
            numberOfPendingActionsSemaphore.wait()
            defer { numberOfPendingActionsSemaphore.signal() }
            _numberOfPendingActions = value
        }
    }

    init(numberOfSimultaneousActions: Int, dispatchQueueLabel: String) {
        dispatchQueue = DispatchQueue(label: dispatchQueueLabel)
        semaphore = DispatchSemaphore(value: numberOfSimultaneousActions)
    }

    func run(closure: ((@escaping CompleteClosure) -> Void)?) {
        numberOfPendingTasks += 1
        dispatchQueue.async { [weak self] in
            guard   let self = self,
                    let closure = closure else { return }
            self.semaphore.wait()
            closure {
                defer { self.semaphore.signal() }
                self.numberOfPendingTasks -= 1
                if self.numberOfPendingTasks == 0, let closure = self.whenCompleteAll {
                    self.tasksCompletionQueue.async { closure() }
                }
            }
        }
    }

    func run(closure: (() -> Void)?) {
        numberOfPendingTasks += 1
        dispatchQueue.async { [weak self] in
            guard   let self = self,
                    let closure = closure else { return }
            self.semaphore.wait(); defer { self.semaphore.signal() }
            closure()
            self.numberOfPendingTasks -= 1
            if self.numberOfPendingTasks == 0, let closure = self.whenCompleteAll {
                self.tasksCompletionQueue.async { closure() }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法

let queue = SimultaneousOperationsQueue(numberOfSimultaneousActions: 1, dispatchQueueLabel: "AnyString")
queue.whenCompleteAll = { print("All Done") }

 // add task with sync/async code
queue.run { completeClosure in
    // your code here...

    // Make signal that this closure finished
    completeClosure()
}

 // add task only with sync code
queue.run {
    // your code here...
}
Run Code Online (Sandbox Code Playgroud)

完整样本

import UIKit

class ViewController: UIViewController {

    private lazy var queue = { SimultaneousOperationsQueue(numberOfSimultaneousActions: 1,
                                                           dispatchQueueLabel: "AnyString") }()
    private weak var button: UIButton!
    private weak var label: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton(frame: CGRect(x: 50, y: 80, width: 100, height: 100))
        button.setTitleColor(.blue, for: .normal)
        button.titleLabel?.numberOfLines = 0
        view.addSubview(button)
        self.button = button

        let label = UILabel(frame: CGRect(x: 180, y: 50, width: 100, height: 100))
        label.text = ""
        label.numberOfLines = 0
        label.textAlignment = .natural
        view.addSubview(label)
        self.label = label

        queue.whenCompleteAll = { [weak self] in self?.label.text = "All tasks completed" }

        //sample1()
        sample2()
    }

    func sample1() {
        button.setTitle("Run 2 task", for: .normal)
        button.addTarget(self, action: #selector(sample1Action), for: .touchUpInside)
    }

    func sample2() {
        button.setTitle("Run 10 tasks", for: .normal)
        button.addTarget(self, action: #selector(sample2Action), for: .touchUpInside)
    }

    private func add2Tasks() {
        queue.run { completeTask in
            DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + .seconds(1)) {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    self.label.text = "pending tasks \(self.queue.numberOfPendingTasks)"
                }
                completeTask()
            }
        }
        queue.run {
            sleep(1)
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                self.label.text = "pending tasks \(self.queue.numberOfPendingTasks)"
            }
        }
    }

    @objc func sample1Action() {
        label.text = "pending tasks \(queue.numberOfPendingTasks)"
        add2Tasks()
    }

    @objc func sample2Action() {
        label.text = "pending tasks \(queue.numberOfPendingTasks)"
        for _ in 0..<5 { add2Tasks() }
    }
}
Run Code Online (Sandbox Code Playgroud)

结果

样品1

在此输入图像描述

样本2

在此输入图像描述


Shr*_*ada 5

您将需要使用信号量来实现此目的.

 //Create the semaphore with count equal to the number of requests that will be made.
let semaphore = dispatch_semaphore_create(locationsArray.count)

        for key in locationsArray {       
            let ref = Firebase(url: "http://myfirebase.com/" + "\(key.0)")
            ref.observeSingleEventOfType(.Value, withBlock: { snapshot in

                datesArray["\(key.0)"] = snapshot.value

               //For each request completed, signal the semaphore
               dispatch_semaphore_signal(semaphore)


            })
        }

       //Wait on the semaphore until all requests are completed
      let timeoutLengthInNanoSeconds: Int64 = 10000000000  //Adjust the timeout to suit your case
      let timeout = dispatch_time(DISPATCH_TIME_NOW, timeoutLengthInNanoSeconds)

      dispatch_semaphore_wait(semaphore, timeout)

     //When you reach here all request would have been completed or timeout would have occurred.
Run Code Online (Sandbox Code Playgroud)


Dee*_*eep 5

我们可以通过递归来做到这一点。从下面的代码中得到想法:

var count = 0

func uploadImages(){

    if count < viewModel.uploadImageModelArray.count {
        let item = viewModel.uploadImageModelArray[count]
        self.viewModel.uploadImageExpense(filePath: item.imagePath, docType: "image/png", fileName: item.fileName ?? "", title: item.imageName ?? "", notes: item.notes ?? "", location: item.location ?? "") { (status) in

            if status ?? false {
                // successfully uploaded
            }else{
                // failed
            }
            self.count += 1
            self.uploadImages()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)