swiftasynchronouscompletionhandler

In a loop, call a function having a completion handler, order the result


I have the following problem, maybe there exists a solution. I need to call a given function with a completion handler in a loop from synchronous code. The problem is that the function calls might not complete in order, and I need to find a way to be able to know the order of the returned values.


func findNextWord(completionHandler: @escaping (String) -> Void) // Given

class A {
    var words = [String]()

    func retrieveAllWords() {
        for _ in 0...20 {
            findNextWord { [weak self] word in 
            self.words.append(word)  // I want to be able to get the correct ordering in my array
        }
    }
}

What is the best way to do that? Thanks in advance.


Experimentations

Note: I tested a few things and I noticed that the closure displays variable a with its value at the time the function was called. Maybe there is something to do with that.

import Foundation

class A {
    
    let queue = DispatchQueue(label: "test")
    
    var a = 0
    
    func doInLoop() {
        for _ in 0...5 {
            doSomething()
        }
    }
    
    func doSomething() {
        a += 1
        let a_not_self = a
        print("outside a: \(a_not_self)")
        orderPizza { [weak self] in
            print("inside a: \(a_not_self)")
            print("inside a (but self): \(self?.a)\n")
        }
    }
    
    func orderPizza(completionHandler : @escaping () -> Void) {
        queue.async {
            sleep(5)
            completionHandler()
        }
    }
}

var a = A()
a.doInLoop()

prints:

outside a: 1
outside a: 2
outside a: 3
outside a: 4
outside a: 5
outside a: 6
inside a: 1
inside a (but self): Optional(6)

inside a: 2
inside a (but self): Optional(6)

inside a: 3
inside a (but self): Optional(6)

inside a: 4
inside a (but self): Optional(6)

inside a: 5
inside a (but self): Optional(6)

inside a: 6
inside a (but self): Optional(6)

Solution

  • You could use a dictionary instead of an array (or both)

    class A {
        var words = [Int: String]()
    
        func retrieveAllWords() {
            for index in 0...20 {
                findNextWord { [weak self] word in 
                self.words[index] = word
            }
        }
    }