How to properly use DispatchQueue.main.async?

6.2k Views Asked by At

I have a loop in my app that can take a few seconds to process.

I want the screen to dismiss immediately while the loop runs in the background.

Is this possible?

I've tried adding only the loop:


DispatchQueue.main.async {
                for _ in 1...self.numberOfTransactionsToAdd {
                    
                    let newTransaction = Transaction()
                    
                    var timeAdded = 1.months
                    newTransaction.transactionAmount = self.amountTextField.text!.toDouble()
                    newTransaction.transactionDescription = self.descriptionTextField.text
                    newTransaction.transactionDate = self.datePicked
                    newTransaction.transactionCategory = self.categoryPicked
                    newTransaction.repeatInterval = self.repeatInterval
                    newTransaction.transactionSubCategory = self.subcategoryPicked
                    newTransaction.subCategoryName = self.subcategoryPicked!.subCategoryName
                    
                  
                    try! self.realm.write {
                        self.realm.add(newTransaction)
                    }
                    
                    self.datePicked = self.datePicked + timeAdded
                }
            }
                
            }

I've tried adding the whole function:

    @IBAction func doneButtonPressed(_ sender: UIButton) {
        
        self.dismiss(animated: true, completion: nil)

        DispatchQueue.main.async {

            self.saveTransaction()

        }
        
    }

But the screen still hangs before the view is dismissed.

This is my first time using DispatchQueue. I've read Apple's documentation on it, but it is extremely vague.

Is this even the right way to go about this?

Can someone help guide a newbie to the correct way to do this?

2

There are 2 best solutions below

0
On

The problem is the asynchronous call to the the main queue itself for the non-ui work(background work). Replace DispatchQueue.main.async with DispatchQueue.global().async. The reason being all the non-ui tasks should run asynchronously off the main queue.

After doing the above fix, you will still have another issue which will be accessing the UI elements from the non-main queue(as now our code will be running on global queue). The reason being you will be fetching the UI elements data(like amountTextField's text etc) from the global queue. Store the UI's data in local variable and use the local variables in your loop code instead of calling amountTextField and other textfields as well.

var amountData: Double
var description: String

@IBAction func doneButtonPressed(_ sender: UIButton) {
    amountData = amountTextField.text!.toDouble()
    descriptionTextField = descriptionTextField.text
    DispatchQueue.global().async {
        self.saveTransaction()
    }
    self.dismiss(animated: true, completion: nil)
}

func saveTransaction() {
    for _ in 1...self.numberOfTransactionsToAdd {
        .
        .
        newTransaction.transactionAmount = self.amountData
        newTransaction.transactionDescription = self.descriptionTextField
        .
        .
        .
    }
    
}
2
On

Seems like your problem is about GCD:

@IBAction func doneButtonPressed(_ sender: UIButton) {
    DispatchQueue.global(qos: .background).async {
//  do here your loading, fetching the data etc
        self.saveTransaction()
    DispatchQueue.main.async {
//  do here ui works, because user doesn't want to wait for touching etc so it has to be asp
        self.dismiss(animated: true, completion: nil)
    }
  }
}