1

Trying to make my for loop behave synchronously when I am making an asynchronous call in each iteration of the loop. I have a feeling I will need to use Grand Central Dispatch in some way but not sure.

func test(strings: [String], completion: @escaping ((_ value: [String]) -> Void)) {
    var results: [String] = []
    for string in strings {
        Service.shared.fetch(with: string, completion: { (result) in
            results.append(result)
        })
    }
    // this will run before asynchronous method in for-loop runs n times.
    completion(results)
}
  • Do you want each iteration to run one at a time, waiting for each to finish before doing the next, or do you just want to ensure `completion` isn't called until all of the fetches are complete? – rmaddy Jan 14 '18 at 21:00
  • @rmaddy the ladder –  Jan 14 '18 at 21:30
  • FYI - you mean "latter". Your friendly English lesson for the day. :) – rmaddy Jan 14 '18 at 21:32
  • Its not everyday I use the word latter.. But i really have no excuse lol. –  Jan 14 '18 at 21:34
  • Wow, I found a duplicate: https://stackoverflow.com/questions/29803458/is-there-any-way-of-locking-an-object-in-swift-like-in-c-sharp – kelin Jan 14 '18 at 21:35

1 Answers1

0

You don't need to make this loop synchronous. What you really want is to call completion when you will get all results. You can use this solution (for free):

func test(strings: [String], completion: @escaping ((_ value: [String]) -> Void)) {
    var results: [String] = []  

    for string in strings {
        Service.shared.fetch(with: string, completion: { (result) in
            DispatchQueue.main.async { 
                results.append(result)
                if results.count >= strings.count {
                    completion(results)
                }
            }
        )
    }
}
kelin
  • 11,323
  • 6
  • 67
  • 104
  • 1
    This runs the risk of `results` being updated on two background threads at the same time. This code is not safe. – rmaddy Jan 14 '18 at 21:30
  • I was thinking this isn't safe because asynchronous function could fail for one of the iterations since its a request over the network. This means `results.count` would never be greater or equal to `strings.count` if one request failed. –  Jan 14 '18 at 21:32
  • @AnonProgrammer, then it should return optional result or error. – kelin Jan 14 '18 at 21:33
  • if one request to fetch an object fails, I'd just like to ignore it and keep all that succeed, no throw an error and end execution. –  Jan 14 '18 at 21:36
  • @AnonProgrammer, it's very bad coding style. That's why you have that problem in question. – kelin Jan 14 '18 at 21:38
  • @rmaddy, I don't know how to lock variable in Swift, so I just put everything into main queue. The thread-safety issue solved. – kelin Jan 14 '18 at 21:43
  • Yes, that does work. Optionally, you could create a background serial queue instead of using the main queue. – rmaddy Jan 14 '18 at 21:44
  • I must notice that we ignore Jonas W answer telling us about `DispatchGroups`, which are very useful. – kelin Jan 14 '18 at 21:45