0

I have this function which has a completion block, the function returns asynchronously without waiting for the completion.

I want my function to delay its return till its completion block is finished. I changed my function for callbacks, it doesn't work, I am new to swift so have little idea of GCD, also their API has also changed a lot.

 func authenticateCredentials(usernameString: String, passwordString: String, completion: @escaping (Bool)-> Void)  {


            var boolVal: Bool = false
            client.invokeAPI("AuthenticateAndFetchData",
                             body: nil,
                             httpMethod: "GET",
                             parameters: ["userid": usernameString,"password":passwordString],
                             headers: nil)
            {
                (result, response, error) -> Void in
                if let err = error {
                    print("ERROR ", err)

                } else if let res = result {
                    let jsonArray = res as! NSArray
                    // print(jsonArray)
                    for value in jsonArray[0] as! NSDictionary {
                        let jsonstring = value.value as! String
                        NetworkService.jsonResponse = jsonstring
                        if let data = jsonstring.data(using: .utf8) {
                            if let content = try? JSONSerialization.jsonObject(with: data, options: []),
                                let array = content as? [[String: Any]]
                            {
                                for jsondict in array {

                                    let auth = jsondict["IsAuth"]! as! String
                                    print(type(of: auth))
                                    print(auth)
                                    boolVal = (auth == "true") ? true : false
                                    self.isAuth = boolVal
                                    print(boolVal)

                                    completion(boolVal) // callback here
                                }
                            }
                        }
                    }

                }
            }

      }

I am calling this function from my viewController on login action like this.

 // After login button is pressed
    @IBAction func loginButtonTapped(_ sender: UIButton) {

        let userid = userIDtxt.text
        let password = userpasswordtxt.text
        //Display an alert if no text is entered 
        if (userid?.isEmpty)! || (password?.isEmpty)! {
            displayAlertMessage("Please enter userid/password")
        }
            // take this userid and password and authenticate from sql server and move to 

        else {
            // Run the spinner to show the task is in progress
            print("\(userid)" + " " + "\(password)")
            nw.authenticateCredentials(usernameString: userid!, passwordString: password!){
                (boolVal) in

                    self.nw.isAuth = boolVal


            }

            showActivityIndicatory(uiview: view)

            if nw.isAuth == true {


            // User authenticated

            }
            else {

            // wrong user
            }

        }


    }

So here, the authenticateCredentials function returns beforehand, and it returns false, so when I click my login button first time, I receive false, when I press it second time, I receive true. I want to receive true and authenticate user on first click.

ash007
  • 311
  • 4
  • 24
  • 1
    Short answer: You can't, and shouldn't, do that. You need to understand how async code and completion handlers work. – Duncan C Jan 04 '17 at 01:37

1 Answers1

1

This should work - update ur UI after callback

nw.authenticateCredentials(usernameString: userid!, passwordString: password!){ [weak self] (boolVal) in
        guard let `self` = self else {
             return
        } //to avoid memory leak
        self.nw.isAuth = boolVal

        showActivityIndicatory(uiview: view)

        if nw.isAuth == true {


            let userData = User.getUserData()
            print("FirstName: \(userData.LastName)")
            showActivityIndicatory(uiview: view)

            displayAlertMessage("User authenticated.")
            hideActivityIndicator(uiView: view)
            self.saveData(userData: userData)

            print("Welcome123 \(userid)")

        }
        else {

            displayAlertMessage("Wrong Username/Password. Please try again.")
            hideActivityIndicator(uiView: view)
        }
    }

Basic idea:

func asyncA(handlerA: () -> ()) {
   asyncB({
       handlerA()
   })
}

func asyncB(handlerB: () -> ()) {
    handlerB()
}

func uiviewcontrollerfunc() {
    asyncA({ [weak self] in
       //update ui
  })
}
JuicyFruit
  • 2,638
  • 2
  • 18
  • 35
  • As of now, i am just popping up an alert, and printing on console, where should I put the update UI statement as of now? Because the moment i click on login for first time, "Wrong username/password" message pops up, when I click login twice, it authenticates. – ash007 Jan 04 '17 at 00:59
  • I have similar requests structure and the idea here is: your `UIViewController` calls `funcA(handler: someHandlerA)`, which calls `funcB(handler: someHandlerB)`, inside `funcB` there is async server request , which has it's own `serverDataHandler`. you should call `someHandlerB` inside `serverDataHandler ` and then call `someHandlerA` inside `someHandlerB` and put all UI update inside `someHandlerA `. This will cause UI update only after all functions executed, if I understand, what are you trying to achieve right. – JuicyFruit Jan 04 '17 at 01:04
  • where is `someHanlerA` in my case? my UIViewController calls `loginButtonTapped` function which calls `AuthenticateCredentials` that has an async server request, this has its own `serverDataHandler`. what i can understand is that you want me to callback the `AuthenticateCredentials` handler. But what is `someHandlerA` in my case? Its little confusing, thanks for help. – ash007 Jan 04 '17 at 01:19
  • In your case, I assume, your `UIViewController` calls `AuthenticateCredentials`, which has its handler, then it calls server, gets response, in `serverHandler` you call `AuthenticateCredentialsHanler`, in which then update your UI, cause your server had responded and you have updated your model. By the way, I also recommend you to read about `Alamofire` and `SwiftyJSON`, they would make your work much easier. – JuicyFruit Jan 04 '17 at 01:22
  • completion(boolVal) I called this completionHandler of authenticateCredentials inside it. How do I call this in UIViewController. – ash007 Jan 04 '17 at 01:29
  • can dispatch.sync(execute:) be of any help here? I tried it but I don't know if I used it correctly? – ash007 Jan 04 '17 at 01:30
  • can you tell me if my code does not work? it actually should. – JuicyFruit Jan 04 '17 at 01:32
  • you code runs without error,but result is still the same. I am not able to understand what you said that I should call `AuthenticateCredentialsHandler` in `serverHandler`, i am already doing that, I am calling `callback(boolVal)` inside `serverHandler`. – ash007 Jan 04 '17 at 01:38
  • yeah, and that's right, but in your code then inside `loginButtonTapped` you assign `self.nw.isAuth = boolVal` inside `handler`, but `if nw.isAuth == true { etc` not inside it, so basically you do check `if nw.isAuth == true` before getting the response, which causes 1st attempt to fail. – JuicyFruit Jan 04 '17 at 01:41
  • Thanks man, it worked. Thanks for your time. – ash007 Jan 04 '17 at 02:14