Marcus Leon
Marcus Leon

Reputation: 56699

Swift wait for async task

In a background thread we have:

defer {
    cleanup()
}

loadData()

if error {
    return
}

processData()

DispatchQueue.main.asyncAfter(deadline: delay) {  //Delay = now + 0-2 seconds
     updateUI()
}

The problem is we want to ensure that the defer cleanUp() code runs after updateUI(). And as it stands this won't happen as updateUI() runs async.

My thought was to sleep/block for that delay period instead of running asynchronously. This would have the defer cleanUp() run once updateUI() is done.

How can you do this? Or is there a better way?

Upvotes: 1

Views: 7451

Answers (3)

Lapinou
Lapinou

Reputation: 1477

An other alternative is using DispatchGroup():

func doWork() {

   let group = DispatchGroup()

   group.enter() //Enter #1
   loadData { result in

      switch (result) {
      case .success(_):
           group.enter()//Enter #2
           processData { group.leave()//Leave #2 }
      case .failure(let error):
           //Do something nice with the error
           print(error)
      }

      group.leave()//Leave #1
    }

    //All the code inside this block will be executed on the mainThread when all tasks will be finished.
     group.notify(queue: .main) { [weak self] in

         guard let strongSelf = self else { return }

         strongSelf.updateUI()
         strongSelf.cleanup()
     }
}

private func updateUI() {
    //All your stuff
}

private func cleanup() {
    //All your stuff
}

private func loadData(completion: (Result<(), Error>) -> ()) {
    //All your stuff
    if error {
       completion(.failure(error))
    }
    else {
       completion(.success(()))
    }
}

private func processData(completion: () -> ()) {
    //All your stuff
    completion()
}

Upvotes: 0

Marcus Leon
Marcus Leon

Reputation: 56699

Realized I could just change the structure of the code:

loadData()

if error {
    log.error("Error")
} else {
   processData()
}

DispatchQueue.main.asyncAfter(deadline: delay) {  //Delay = now + 0-2 seconds
  updateUI()
  cleanup()
}

Upvotes: 0

Code Different
Code Different

Reputation: 93191

You can use a semaphore to tell the cleanup task to wait until updateUI has completed:

let semaphore = DispatchSemaphore(value: 1)
defer {
    semaphore.wait()
    cleanup()
}

loadData()

if error {
    // If we exit here, the semaphore would have never been used
    // and cleanup will run immediately
    return
}

processData()
semaphore.wait() // here, we claim the semaphore, making cleanup
                 // wait until updateUI is done
DispatchQueue.main.asyncAfter(deadline: delay) {
     updateUI()
     semaphore.signal()
}

Upvotes: 5

Related Questions