João Serra
João Serra

Reputation: 63

How to escape a value with a throw '() throws -> Bool' is not convertible to 'Bool'

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

Upvotes: 1

Views: 1160

Answers (2)

OOPer
OOPer

Reputation: 47886

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()
                }
            })
        }
    } )
}

Upvotes: 1

Carien van Zyl
Carien van Zyl

Reputation: 2873

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)
}

Upvotes: 0

Related Questions