refresh UI waits for core data task finish

I have a login screen that on a succesfull login, update core data from other database data, but i want to show an activity indicator view until this update finish.

When it finish, this screen had to move to another screen.

I read a lot of private and main concurrence queue managed object context, perform blocks, main and background threads, asynchronous calls........ but i don't know where to begin, if someone could bring me some light on this.


i know there is not a specific question, if you need more information just ask me.

I suppose login occurs through an alert or something similar.

You usually define the action in a completion handler which sets some variable (like login is OK)

The usual error is to test the value of the var (login) after the call to the presentation of the alert and expect the var to be set in the completion handler :


This will not work :


let alert = UIAlertController()

let yesAction = UIAlertAction(title: "OK", style: .Default, handler: { (action) -> Void in

test the value entered and set okLogin

}


if OKLogin { authorizeSomething }


The problem is that the completion handler is sent to another thread and the main code continues to execute without waiting result.

Hence, when testing OKLogin, it's not yet set.


So, the right way to do this is to call authorizeSomething from within the handler :



        let alert = UIAlertController(title: "Autoriser l'envoi", message: "ogin", preferredStyle: .Alert)    

        let yesAction = UIAlertAction(title: "OK", style: .Default, handler: { (action) -> Void in
            let textField = alert.textFields![0] /
            inputCode = textField.text!
            if inputCode is OK { 
               self.authorizeSomething           // self needed inside the handler
            } 
        })
        alert.addAction(yesAction)
        self.presentViewController(alert, animated: true, completion: nil)
    }

Thanks or your answer Claude:


The idea is very similar to this post: https://forums.developer.apple.com/thread/4333


i'm not login from a uiAlert, is a view controller

i try to detail it as much as i can,


i have an external database, and use too core data.


the login view controller, communicattes with that external data base, validates user and password(when the user push the button, the app shows a indicator activity view ), and fire some request to the external database to update local core data, i understand all communications must to be at background, cause i don't want to freeze the UI, and finally when that updates finish, the view controller push the next view controller.


i have in my app some similar problem, i want to code a table(populate with a NSFetchedResultController) with a pull and refresh behaviour:


The idea in that case is when user drag down the table, it show an activity indicator, call the external database, update the internal coredata , refresh the tableView and finally hide the activity indicator.


i really appreciate your help.

You have no UIAlert, but the use probably taps a button ?


If so, the button has a completion handler, and similar process can be applied.


Please tell if that's not clear.

Sorry, i don't understand how to code that,


for now i got this


@IBAction func login(_ sender: UIButton) {
DispatchQueue.main.async {
//call to validation code
     someActivityIndicatorView.isHidden = false
     someActivityIndicatorView.startAnimating()
     if result == ok

                    moc.performAndWait {
                        moc.performAndWait {
                            moc.performAndWait {
                                loadAndSaveExternalDatabase()           
                               }
                            loadAndSaveOtherExternalDatabase()
                  
                        }
              
                        self.someActivityIndicatorView.stopAnimating()
                        let principalScreen = self.storyboard?.instantiateViewController(withIdentifier: "principalScreen")
              
              
                        self.addChildViewController(principalScreen!)
                        self.view.addSubview(principalScreen!.view)
              
                        principalScreen?.didMove(toParentViewController: self)
              
                    }
     if result != ok
          show alert with error
          self.someActivityIndicatorView.stopAnimating()
          self.someActivityIndicatorView.isHidden = true
     }
}

Where is result set to true or false ?


Why do you call moc.performAndWait 3 times ?


What do you get when you run this code now ?

the result comes from a request to database

With that 3 calls i try (with no success), run that blocks sequencially


i try run it in a lot different ways.


what i get know, is the activiity indicator rounding, and items be saved in background, but it changes to the new viewcontroller before this load and save finnish

When I ask where, it is where in your code is result set to true or false ?


Can you show moc.performAndWait ? But running 3 times the same code is strange.

is in other class, i call it with the parameters of connection, it returns a JSON and a flag, i write the code in that way to simplify the idea.


for every call to external database i use the same class, but i change the parameters to made the request to a table or another


let task = session.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in


and i use a globar var if the request is finished.


the 3 calls to moc.performAndWait, is just a proof, but the more close to my objetive

OK, let's assume the logic of your design is OK.


You call DispatchQueue.main.async : that probably means that other tasks as validation code run in another thread. EXACT ?


If so,

- you call the validation code in another thread

- then, the code in main thread continues immediately

- hence, result is not set correctly.


One way to handle this is to use some type of semaphore (in very simple way)

- define a global var semaphore that must be visible in IBAction and in validation code (you can define it out of a class, to have global scope)

- set semaphore to false before calling validation code

- in validation code, you will set semaphore to true when the validation has been checked (not depending on check OK or not OK)

- wait for semaphore before testing result

- of course that will block everything until user has entererd its code (you could also release semaphore after some time out, but that can be done in a second stage)


PS :

- I do not see either how you configure moc.performAndWait, but that should not change the logic

- what do you wait in moc.performAndWait ?


var semaphore = false  // Global scope, must be visible in validation code

@IBAction func login(_ sender: UIButton) {
DispatchQueue.main.async {

semaphore = false

//call to validation code
     someActivityIndicatorView.isHidden = false
     someActivityIndicatorView.startAnimating()

     repeat {//wait till semaphore is set true in validation code
     } while !semaphore

     if result == ok

                    moc.performAndWait {
                        moc.performAndWait {
                            moc.performAndWait {
                                loadAndSaveExternalDatabase()   
                               }
                            loadAndSaveOtherExternalDatabase()

                        }

                        self.someActivityIndicatorView.stopAnimating()
                        let principalScreen = self.storyboard?.instantiateViewController(withIdentifier: "principalScreen")


                        self.addChildViewController(principalScreen!)
                        self.view.addSubview(principalScreen!.view)

                        principalScreen?.didMove(toParentViewController: self)

                    }
     if result != ok
          show alert with error
          self.someActivityIndicatorView.stopAnimating()
          self.someActivityIndicatorView.isHidden = true
     }
}

I think is better to forget all about my design and logic, because i don't have clear some things.


First, i don't sure if my main MOC had to be run on main or private queue concurrence (or either if it doesn't matter).


All my communications with external database are performed on a dataTask with url, and i don't know if it is running on background.


After all single communication i want to save all data on local Core Data, i want to know if that save could to be done on a child moc, at the same moc, and also with what concurrency type.


I try to do performAndWait, cause it seems to be the way to wait that single communication ends and continue sequencially to the next, but i don't know if is the right way.


i show a screen with my desire screen, activity indicator is spinning and data are saved before that screen changes to main screen of app.




https://postimg.org/image/t1lgzgu17/

refresh UI waits for core data task finish
 
 
Q