1

I'm new in swift and I've been probably for more than an hour around this.

I make a request to a webservice, and now i want to act according to the response code (200 Ok) or other but I can't understand the syntax for returning a value and throwing the exception.

typealias  ThrowableCallBack = () throws -> Bool


func authenticate()
{
    let services = ServiceManager()


    do {
         try services.login(username: emailField.text!, password: passwordField.text!, authenticated: { auth in
            self.loadingDialog.dismiss(animated: false, completion: {
                if (auth) // '() throws -> Bool' is not convertible to 'Bool'
                {
                    self.performSegue(withIdentifier: "LoginSegue", sender: self)
                }
            })
        } )
    }
    catch RequestError.invalidRequest {
        showLoginFailedAlert()
    }
    catch {
        showLoginFailedAlert()
    }
}

Then on services

func login(username : String, password : String, authenticated: @escaping (_ inner: ThrowableCallBack) -> Void )
{
    let parameters = [
        "_username" : username,
        "_password" : password
    ]

    let request = makePostCall(request: "login", parameters: parameters, completion: {
        response in

        let statusCode = String(describing: response["statusCode"]!)
        if (statusCode != "200")
        {
            authenticated( { throw RequestError.invalidRequest })
        }
        else
        {
            self.jwt = String(describing: response["jwt"]!)
            authenticated( { return true })
        }
    } )

}

How should I fix the auth '() throws -> Bool' is not convertible to 'Bool' to be able to both catch the error or succeed ? Is my alias correct?

Thank you in advance

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Got two errors now Closure use of non-escaping parameter 'auth' may allow it to escape and Cannot convert value of type 'Void' to closure result type 'Bool' – João Serra Apr 06 '18 at 10:20
  • You seem to be going to a lot of trouble to adopt a throw/catch paradigm when the standard pattern in this case would be a closure that accepts an `Error?` and if the error is `nil` all was good. – Paulw11 Apr 06 '18 at 10:23
  • @Paulw11 thanks for the comment, I will try to check if I can do it that way in the future. seems lot more simple. – João Serra Apr 06 '18 at 11:13

2 Answers2

1

In your login method, the type of the parameter authenticated is @escaping (_ inner: ThrowableCallBack) -> Void.

So, the closure passed to authenticated takes one parameter of type ThrowableCallBack, which means the parameter auth in your closure is of type ThrowableCallBack, not Bool.

You need to use auth as if try auth() as ThrowableCallBack takes one parameter of type () and throws.

func authenticate() {
    let services = ServiceManager()

    services.login(username: emailField.text!, password: passwordField.text!, authenticated: { auth in
        self.loadingDialog.dismiss(animated: false, completion: {
            do {
                if try auth() {
                    self.performSegue(withIdentifier: "LoginSegue", sender: self)
                }
            } catch RequestError.invalidRequest {
                self.showLoginFailedAlert()
            } catch {
                self.showLoginFailedAlert()
            }
        })
    } )
}

To make this work, you may need to modify the type of authenticated as follows:

func login(username : String, password : String,
           authenticated: @escaping (_ inner: @escaping ThrowableCallBack) -> Void ) {
    //...
}

Anyway, closure type taking a parameter of another closure type, is very confusing and you should better re-consider your design.

(ADDITION)

When you get runtime errors saying some main thread things, you may need to use DispatchQueue.main as found in many async examples:

func authenticate() {
    let services = ServiceManager()

    services.login(username: emailField.text!, password: passwordField.text!, authenticated: { auth in
        DispatchQueue.main.async {
            self.loadingDialog.dismiss(animated: false, completion: {
                do {
                    if try auth() {
                        self.performSegue(withIdentifier: "LoginSegue", sender: self)
                    }
                } catch RequestError.invalidRequest {
                    self.showLoginFailedAlert()
                } catch {
                    self.showLoginFailedAlert()
                }
            })
        }
    } )
}
OOPer
  • 47,149
  • 6
  • 107
  • 142
  • Thanks, im trying it now but im still getting some sort of exception after authenticated( { throw RequestError.invalidRequest }) . Anyway what approach would you suggest? – João Serra Apr 06 '18 at 10:47
  • Please explain _some sort of exception_. It is not clear enough to find any solutions or suggestion. – OOPer Apr 06 '18 at 10:51
  • Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'accessing _cachedSystemAnimationFence requires the main thread' – João Serra Apr 06 '18 at 10:55
  • Seems your `makePostCall` does not execute `completion` in a main thread. You may need `DispatchQueue.main`, I will update my answer. – OOPer Apr 06 '18 at 11:00
  • yes, that makes, since the dialog is part of the interface. Thank you, its working now. But i still wonder how i could have do it more simple. – João Serra Apr 06 '18 at 11:08
  • The answer of Carien van Zyl is a nice hint for you. Good luck. – OOPer Apr 06 '18 at 11:09
0

You can have a look at this thread

We make use of a Completion enum to accomplish this

public enum Completion<E:Error> {
    case success
    case failure(E)
}

Implement it in your function as follows:

func login(username : String, password : String, authenticated: @escaping (Completion<ServiceError> )
{
    ...
    if (statusCode != "200") {
        authenticated(.failure(ServiceError.accessDenied))
    } else {
        self.jwt = String(describing: response["jwt"]!)
        authenticated(.success)
    }
    ...
}

Test it as follows:

try services.login(username: emailField.text!, password: passwordField.text!, authenticated: { auth in
    switch auth {
    case .success:  //handle success
    case .failure(let error): print(error)
    }
}

Note ServiceError is an enum that implements Error and holds all the different kinds of service errors that we potentially get.

We use a similar enum in the case where we want to return a result.

public enum Result<Value, E:Error> {
    case success(Value)
    case failure(E)
}
Carien van Zyl
  • 2,853
  • 22
  • 30
  • Thanks for your answer. I was thinking about Paulw11 answer and wondering if you would comment, why not to use a closure an "Error? and if the error is nil all was good." seems allot more simple – João Serra Apr 06 '18 at 11:24
  • @JoãoSerra I prefer the enum way because it forces the result to either be a success or failure result and it needs to be one of them. When you do not return a value, this is not so prominent, but when the result can either be a value or an error the enum solution is much cleaner to me. In the other solution, your closure should have optional value and optional error params. To be very robust you will need to cater for the case where both are nil and the case where both have values. Also, the intention is clear with the enum solution as to the fact that it can only be value OR error. – Carien van Zyl Apr 09 '18 at 08:57