swiftasync-awaitdispatchgroupserial-processingoperationqueue

Swift - Time Consuming Processing of an Array


I am trying to process over an array of simple elements, where on every element of the array a time-consuming process of updating the UI with a Timer will be started. The order of the elements in the array is important and matters. For simplicity sake I simplified the code down to this:

func processArray() {
    let signals = [0, 1, 0, 1, 1]
        
    for element in signals {
        //pause and process first element
        timeConsumingProcess(e: element)
            
        //after processing first element finished, continue to next element
    }
}
    
func timeConsumingProcess(e: Int) {
    Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { timer in
        // 2 seconds set for demonstration purposes, can take more or less depending on the element
        // doing something with e in UI
        timer.invalidate()
    }
}

How can I pause array iteration while the time consuming timeConsumingProcess(e: Int) is happening?
I tried using OperationQueues, DispatchGroups, DispatchSerialQueues, sync{} blocks, but to no avail. I'm guessing with the advancement of Swift concurrency and Async/Await there should be an elegant and efficient solution to it, but I just can't wrap my head around it. Sequentially processing time-consuming events has never been easy in Swift, but I'm entirely at a loss here, so any help would be greatly appreciated!


Solution

  • You can use async-await to achieve this. Something like this should work. Where the print is the place to make your UI changes. (you'll need to DispatchQueue.main.async any UI Changes)

    let array = [0,1,2,3,4,5]
    
    Task {
        for item in array {
            await doAThing(item)
        }
    }
    
    
    func doAThing(_ item: Int) async {
        print("\(item)")
        try? await Task.sleep(nanoseconds: UInt64(2 * Double(NSEC_PER_SEC)))
    }
    
    

    If you can't use async await due to lower api support, you can use DispatchQueue.asyncAfter like so:

    DispatchQueue.main.asyncAfter(deadline: .now() + (2 * arrayIndex)) {
        // Put your code which should be executed with a delay here
    }
    

    With this you won't use a for loop but instead you'll need a DispatchQueue.asyncAfter for each item in your array and it'll handle it's own logic.