Can't get throws to work with function with completion handler

南楼画角 提交于 2019-12-23 07:22:04

问题


I'm trying to add a throws to my existing function with a completion handler but I keep getting a warning saying no calls throwing functions occur within try expression. In the section where I throw the errors, I get an error saying

invalid conversion from throwing function of type '() throwing -> Void' to non-throwing function type.

enum LoginError: ErrorType {
    case Invalid_Credentials
    case Unable_To_Access_Login
    case User_Not_Found
}

@IBAction func loginPressed(sender: AnyObject) {

    do{
        try self.login3(dict, completion: { (result) -> Void in

            if (result == true)
            {
                self.performSegueWithIdentifier("loginSegue", sender: nil)
            }
        })
    }
    catch LoginError.User_Not_Found
    {
        //deal with it
    }
    catch LoginError.Unable_To_Access_Login
    {
        //deal with it
    }
    catch LoginError.Invalid_Credentials
    {
        //deal with it
    }
    catch
    {
        print("i dunno")
    }

}

func login3(params:[String: String], completion: (result:Bool) throws -> Void)
{
    //Request set up
    let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
        do {
            let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves) as? NSDictionary
            if let parseJSON = json
            {
                let userID = parseJSON["user_id"] as? Int
                let loginError = parseJSON["user_not_found"] as? String
                let validationError = parseJSON["invalid_credentials"] as? String
                let exception = parseJSON["unable_to_access_login"] as? String

                var responseArray = [(parseJSON["user_id"] as? Int)]
                if userID != nil
                {
                    dispatch_async(dispatch_get_main_queue()) {
                        completion(result:true)
                    }

                }
                else if loginError != ""
                {
                    dispatch_async(dispatch_get_main_queue()){
                        completion(result: false)
                        self.loginErrorLabel.text = loginError
                        throw LoginError.User_Not_Found
                    }
                }
                else if validationError != ""
                {
                    dispatch_async(dispatch_get_main_queue()){
                        completion(result:false)
                        self.validationErrorLabel.text = validationError
                        throw LoginError.Invalid_Credentials
                    }

                }
                else if exception != nil
                {
                    dispatch_async(dispatch_get_main_queue()){
                        completion(result:false)
                        self.exceptionErrorLabel.text = "Unable to login"
                        throw LoginError.Unable_To_Access_Login
                    }
                }
            }
            else
            {
            }
        }
        catch let parseError {
            // Log the error thrown by `JSONObjectWithData`
        })

        task.resume()

}

回答1:


What you can do is encapsulating the error into a throwable closure like in the following code to achieve what you want:

func login3(params:[String: String], completion: (inner: () throws -> Bool) -> ()) {

   let task = session.dataTaskWithRequest(request, completionHandler: { data, response, error -> Void in

            let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves) as? NSDictionary

            if let parseJSON = json {
               let userID = parseJSON["user_id"] as? Int
               let loginError = parseJSON["user_not_found"] as? String
               let validationError = parseJSON["invalid_credentials"] as? String
               let exception = parseJSON["unable_to_access_login"] as? String

               var responseArray = [(parseJSON["user_id"] as? Int)]
               if userID != nil {
                 dispatch_async(dispatch_get_main_queue()) {
                     completion(inner: { return true })
                 }

            }
            else if loginError != ""
            {
                dispatch_async(dispatch_get_main_queue()) {
                    self.loginErrorLabel.text = loginError
                    completion(inner: { throw LoginError.User_Not_Found })
                }
            }
            else if validationError != ""
            {
                dispatch_async(dispatch_get_main_queue()) {
                    self.validationErrorLabel.text = validationError
                    completion(inner: {throw LoginError.Invalid_Credentials})
                }
            }
            else if exception != nil
            {
                dispatch_async(dispatch_get_main_queue()){
                    self.exceptionErrorLabel.text = "Unable to login"
                    completion(inner: {throw LoginError.Unable_To_Access_Login})
                }
            }
        }
        else
        {
        }
    }

   task.resume()
}

And the you can call it like in the following way:

self.login3(dict) { (inner: () throws -> Bool) -> Void in
   do {
     let result = try inner()
     self.performSegueWithIdentifier("loginSegue", sender: nil)
   } catch let error {
      print(error)
   }
}

The trick is that the login3 function takes an additional closure called 'inner' of the type () throws -> Bool. This closure will either provide the result of the computation, or it will throw. The closure itself is being constructed during the computation by one of two means:

  • In case of an error: inner: {throw error}
  • In case of success: inner: {return result}

I strongly recommend you an excellent article about using try/catch in async calls Using try / catch in Swift with asynchronous closures

I hope this help you.




回答2:


You're asking for X and I'm answering Y, but just in case...

There's always the possibility to add the throwing capability to your function instead of the completion handler:

func login3(params:[String: String], completion: (result:Bool) -> Void) throws {
    ...
}

Then you can call it from inside IBAction:

do {
    try self.login3(dict) { result -> Void in
        ...
    }
} catch {
    print(error)
}



回答3:


Read the below with a grain of salt. I haven't worked much with Swift 2.0 yet:

You created a "dataTask" task who's's completion handler has a throw in it, but the only actual code in your login3 method is task.resume(). The completion handler won't get executed until after login3 returns. (In fact, it's a parameter to another object, so the compiler has no idea what's going to happen with that code.)

As I understand it, the actual top-to-bottom body of your login3 method must contain a throw. Since it's an async method, you can't do that. Thus, don't make your login3 function throw. Instead have it pass an error object to it's completion handler.




回答4:


In my impression this is caused by the signature of your functions. In @IBAction func loginPressed(sender: AnyObject) you don't have to use try when calling login3 as the function itself is not marked as throwing but the completion handler is. But in fact the completion closure of login3 will never throw as you execute all throwing functions inside a do {} catch {} block, so you could try to remove the throws annotation from the login3 completion closure and also call the closure if you catch an error in login3 with an according result. This way you handle all throwing functions inside of login3 in a do {} catch {} block and call the completion handler with a suitable value.

Generally I am also not aware that you can catch without a preceding do {} block like you did in the @IBAction.



来源:https://stackoverflow.com/questions/33402348/cant-get-throws-to-work-with-function-with-completion-handler

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!