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()
}
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.
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)
}
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.
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.
Related
I have type aliased the completion handler as below, having a completion handler inside the completion handler.
typealias voidCompletionHandler = () -> Void
typealias dataCompletionHandler = (Data, [String: String], voidCompletionHandler) -> Void
I wanted to use computed properties to define the completion handler.
var completion: dataCompletionHandler {
return { data, _, completion in
jsonDataProcess(data: data) { json in // ERROR --> This is SwiftyJSON process with completion handler.
if json != nil {
Country.instantiate(json: json!) {. // ERROR --> The is Core Data process with completion handler
completion()
}
}
}
}
With the above code, I get "Escaping closure captures non-escaping parameter 'completion'.
The sub processes also has #escaping so, they are not the problem. It is the completion handler inside the dataCompletionHandler that I do not know how to define it as #escaping.
// Inside JSON Handler class
func jsonDataProcess(data: Data, completion: #escaping jsonCompletionHandler) {
do {
let json = try JSON(data: data)
if json["Status"].string == "Success" {
completion(json["Data"])
} else {
completion(nil)
}
} catch {
print(error)
completion(nil)
}
}
// Inside Country Class
static func instantiate(json: JSON, completion: #escaping voidCompletionHandler) {
container.performBackgroundTask { (context) in
let country = Country(context: context)
country.name = json["name"].string
country.iso2digit = json["alpha-2"].string
country.iso3digit = json["alpha-3"].string
country.region = json["region"].string
try? context.save()
print("Country.instantiate : \(String(describing: json["alpha-2"].string))")
completion()
}
}
How do I make the completion handler inside the completion handler as #escaping when using as computed property?
Make the escaping declaration part of the typealias.
I tried to do error handling via throw completion handler.
When i call completion i get compiler error "Argument passed to call that takes no arguments". How can i fix my code?
private func executeRefreshTokenRequest(_ completion: #escaping () throws -> Void) {
DispatchQueue.global(qos: .userInitiated).sync {
guard let refreshToken = currentRefreshToken else {
completion({ throw RefreshTokenError.notFounded })
// Argument passed to call that takes no arguments
}
var refreshTokenUrlRequest:CustomURLRequest
do {
refreshTokenUrlRequest = try AuthService.refresh(token: refreshToken).doUrlRequest()
} catch (let error) {
completion({ throw UrlRequest.notDone })
// Argument passed to call that takes no arguments
}
execute(refreshTokenUrlRequest, PlaceToken.self) { [weak self] (placeToken, error) in
if error != nil {
self?.cancelAllRequests()
NotificationCenter.default.post(name: Notification.Name("AuthorizationFail"), object: nil)
} else {
Cache.currentAuthToken = placeToken
}
completion()
}
}
The following code gives me an error as 'return self.myID' should be at the last curly bracket. Although if I do that, it will claim self.myID as an unresolved identifier since it is outside of the closure. How can I make it so that I can access self.myID outside of the completion handler and place the return value.
func chatPartnerId() -> String? {
nextrequest.startWithCompletionHandler { (connection: FBSDKGraphRequestConnection! , result: AnyObject!, error: NSError!) -> Void in
self.myID = result["id"] as! String
return self.myID
}
}
Instead of returning a String use a completion handler:
func chatPartnerId(completion: (result: AnyObject?, error: NSError?)->()) {
nextrequest.startWithCompletionHandler { (connection: FBSDKGraphRequestConnection! , result: AnyObject!, error: NSError!) -> Void in
if((error) != nil){
completion(result:nil,error:error)
}
else{
self.myID = result["id"] as! String
completion(result:self.myID, error:nil)
}
}
}
Call it as
chatPartnerId() { (result,error) -> Void in
if let error = error{
print(error)
}
if result != nil {
print(result)
}
}
I want to create a generic Network Operation class to handle the communication to my server. I tested a few things and the error must be the responseArrayfunction. But I don't know where?
class NetworkOperation<T> {
let requestType: NSMutableURLRequest
var arrayItems: Array<T>?
typealias JSONDictionaryCompletion = ([String:AnyObject]?) -> Void
typealias ObjectArrayCompletion = (Response<[T], NSError>) -> Void
init(requestType: NSMutableURLRequest, arrayItems: Array<T>) {
self.requestType = requestType
self.arrayItems = arrayItems
}
func downloadJSONFromURL(completion: JSONDictionaryCompletion) {
}
func downloadObjectsFromURL(completion: ObjectArrayCompletion) {
Alamofire.request(requestType).responseArray { (response) in
if let httpResponse = response, let statusCode = httpResponse.response {
switch(statusCode) {
case 200:
print("OK")
}
}
}
}
}
extension Alamofire.Request {
// responseObject<...>(...) declares a new .responseX handling function on Alamofire.Request. It uses the responseSerializer as a custom serializer.
// The <T> means this is a genertic method: it can work with different types of objects. These types must implement the ResponseJSONObjectSerializable protocol.
// This is needed to guarantee that any type of object that will be passed in will have an init function that takes JSON. The response function takes a single
// argument called completionHandler. This is the method being called when done parsing the JSON and creating the object to be called async.
// The completion handler has a single argument Response<T, NSError>. The response object packs up the Result along iwth all the other info from the transaction.
// The responseObject function returns an Alamofire.Request object
public func responseObject<T: ResponseJSONObjectSerializable>(completionHandler: Response<T, NSError> -> Void) -> Self {
// Create the response serilaizer that will work with the generic T type and an NSError. It will take in the results of the URL Request (request, response, data, error)
// and use the Result type defined by Alamofire to return success with the object or failure with an error. The responseObject just returns the responseserializer that gets created
// and allows passing the completion handler where it needs to go.
let serializer = ResponseSerializer<T, NSError> { (request, response, data, error) in
// Checks that it has valid data using guard. Then it turns the data into JSON and parses it using SwiftyJSON.
// Then it creates a new copy of the type of class.
guard error == nil else {
return .Failure(error!)
}
guard let responseData = data else {
let failureReason = "Object could not be serialized because input data was nil."
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, responseData, error)
switch result {
case .Success(let value):
let json = SwiftyJSON.JSON(value)
// Get Data content of JSON
let jsonData = json["data"]
if let object = T(json: jsonData) {
return .Success(object)
} else {
let failureReason = "Object could not be created from JSON."
let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: serializer, completionHandler: completionHandler)
}
// Iterate through the elements in the json and create object out of each one and add it to the objects array.
public func responseArray<T: ResponseJSONObjectSerializable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
let serializer = ResponseSerializer<[T], NSError> { request, response, data, error in
guard error == nil else {
return .Failure(error!)
}
guard let responseData = data else {
let failureReason = "Array could not be serialized because input data was nil."
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, responseData, error)
switch result {
case .Success(let value):
let json = SwiftyJSON.JSON(value)
// Get Data content of JSON
let jsonData = json["data"]
var objects: [T] = []
for (_, item) in jsonData {
if let object = T(json: item) {
objects.append(object)
}
}
return .Success(objects)
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: serializer, completionHandler: completionHandler)
}
}
But when I want to check the status code in the downloadObjectsFromURL function, I get the compiler error message:
Ambigious Reference to member 'request(::parameters:encoding:headers:)'
Where does this error come from. Did I unwrap the optionals in a wrong way?
UPDATE
I tested this:
func createObjectsFromJSON(completion: ObjectArrayCompletion) {
Alamofire.request(requestType).responseArray { (response: Response<[AED], NSError>) -> Void in
print("OK")
}
}
AED is a custom class I created. No error message anymore. When I switch AED to T this error pops up
func createObjectsFromJSON(completion: ObjectArrayCompletion) {
Alamofire.request(requestType).responseArray { (response: Response<[T], NSError>) -> Void in
print("OK")
}
}
expected argument type Response<[_], NSError> -> Void
This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 7 years ago.
My question is simple but after many research and tests I do not succeed to wait for a function end before continuing the flow.
Example :
print("Before stuff")
do_stuff {
print("After stuff")
}
func do_stuff(onCompleted: () -> ()) {
let postEndpoint: String = "http://localhost:8080/users/1234567890987654"
guard let url = NSURL(string: postEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = NSURLRequest(URL: url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("error calling GET on /users/1234567890987654")
print(error)
return
}
// parse the result as JSON
let user: NSDictionary
do {
user = try NSJSONSerialization.JSONObjectWithData(responseData,
options: []) as! NSDictionary
} catch {
print("error trying to convert data to JSON")
// Means that user does not exist
return
}
print("The user is: " + user.description)
})
task.resume()
onCompleted()
}
How to wait do_stuff() end before the second print?
Thanks for your help, I think I miss something.
Thomas
There is something fundamental that you haven't understood. It's not actually the function, which is executing. It's the closure defined inside the function. What you need to wait is the closure to be called when the request has completed. And in fact, you should not wait, but assign an another closure outside the function to be called from the closure inside the function.
print("Before stuff")
do_stuff {
// Now the "function" has completed.
print("After stuff")
}
func do_stuff(onCompleted: () -> ()) {
let task = session.dataTaskWithRequest(urlRequest) { data, response, error in
...
onCompleted()
}
}
You could always use a completion handler for the do_stuff() function:
func do_stuff(completion: (() -> Void)?) -> () {
...
if completion != nil {
return completion!() // Return completion where you want you "after stuff" declarations to run.
}
}
And call the function as such:
do_stuff({
print("After stuff")
})