how to throw errors in a closure in swift? - ios

Please look at the following code:
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete", handler: {
(action : UITableViewRowAction, indexPath : NSIndexPath) -> Void in
if let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext{
let restaurantToDelete = self.fetchResultController.objectAtIndexPath(indexPath) as! Restaurant
managedObjectContext.deleteObject(restaurantToDelete)
// Saving managedObjectContext instance, and catch errors if it fails
do {
try managedObjectContext.save()
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
}
})
return deleteAction
}
the error message from Xcode is : Invalid conversion from throwing function of type '(UITableViewRowAction, NSIndexPath) throws -> Void' to non-throwing function type '(UITableViewRowAction, NSIndexPath) -> Void'
I know the problem is managedObjectContext.save() will throw errors and this is not allowed in the completion handler. I found some blog articles where they modified the closure parameters in order to make the error handling in a closure workable. While here the definition of the function is given by apple, so how can i fix this issue? Thanks a lot! :D

the compiler is adding throws to the signature of your block because your catch clause is not exhaustive: the pattern match let error as NSError can fail... see the documentation
the signature of the closure argument is (UITableViewRowAction, NSIndexPath) -> Void, however the compiler is inferring the type of the closure that you are providing to be (UITableViewRowAction, NSIndexPath) throws -> Void
by adding another catch clause (with no pattern) after the one you already have the compiler will see that you are catching the exception locally and it will no longer infer that the signature of the closure you are providing includes throws:
do {
try managedObjectContext.save()
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
} catch {}

Not possible, because the closure can be invoked at any time, probably not at execution time of your function, so where should the error propagate to?
You have to call out to another function which can handle the error:
func handleError(error: ErrorType) {
switch error {
...
}
}
and then call this function with your caught error inside the closure

Related

Completion handler in function

I have a function that looks like this, and I have tried to add a completionHandler in the code below:
func getValueFromAPI(completionHandler: (_ result: Bool) -> Void){
apii.getVehicle(id!).done {
(vehicle: Vehicle) -> Void in
print("ggg.state: \(vehicle.state!)")
print("ggg.state: \(vehicle.displayName!)")
apii.getAllData(vehicle).done { (extendedVehicle: VehicleExtended) in
let entryBattery = (extendedVehicle.chargeState?.batteryLevel)!
let entryCarState = (extendedVehicle.state)!
print("entryBattery: \(entryBattery)")
print("entryCarState: \(entryCarState)")
completionHandler(true)
}.catch { (error) in
print("ERROOOOR: \(error)")
}
}.catch { error in
print("errorr: \(error)")
}
}
I have already tried to add a complete handler, but I get the following error on these lines:
Line: apii.getVehicle(id!).done {
Error: Escaping closure captures non-escaping parameter 'completionHandler'
Line: apii.getAllData(vehicle).done { (extendedVehicle: VehicleExtended) in
Error: Escaping closure captures non-escaping parameter 'completionHandler'
What am I doing wrong here, and how can I fix this?
I am using Swift 5.
You need to declare your completionHandler to be an escaping closure. E.g.:
func getValueFromAPI(completionHandler: #escaping (Bool) -> Void) {
...
}
Note the #escaping qualifier.

PromiseKit flatMapError

ReactiveSwift has this great function called flatMapError that allows you to respond with an event stream when an error occurs. A simple example might look like:
authenticationProducer.flatMapError { _ in self.reauthenticate() }
Whenever an error occurs, that error gets mapped into a producer that attempts to re-authenticate.
How would I build a similar operator using PromiseKit? The function signature would look like:
func flatMapError<U>(_ transform: #escaping (Error) -> Promise<U>) -> Promise<U>
My implementation so far:
func flatMapError<U>(_ transform: #escaping (Error) -> Promise<U>) -> Promise<U> {
return Promise<U> { resolve, reject in
self.catch { error in
let promise = transform(error)
let _ = promise.then { value in
resolve(value)
}
}
}
}
Use recover, it behaves as you request.
https://github.com/mxcl/PromiseKit/blob/master/Sources/Promise.swift#L254-L278

deleteInBackgroundWithBlock always return true

I've been delving into swift and using the Parse SDK and was wondering if anyone could shed some light on the following:
I am trying to delete an object in the Parse DB, and have set the method up to fail - but it fails to fail.
func destroy(onComplete: Bool -> Void) {
let object = PFObject(className: "ClassName")
object.deleteInBackgroundWithBlock({
(success: Bool, error: NSError?) -> Void in
NSLog("Error: \(error)")
if let error = error {
onComplete(false)
NSLog("Error: \(error)")
} else {
onComplete(success)
}
})
}
When I set the objectId property the object is destroyed fine, but here I am omitting it so, obviously, nothing is destroyed, but when the closure runs, success is always equal to true, and error is always nil.
Does anyone know if this is intended behaviour, because if no object is destroyed, surely, either success should equal false, or error should be non-nil?
Thanks
Paul

Asynchronous block call to self crashes with EXC_BAD_ACCESS in Swift

I have an API call (using AFNetworking) that when fails calls a failure block. This block simply stop the table view refresh controller via 'self.refreshController.stopRefreshing();
However at run-time this causes a EXC_BAD_ACCESS error.
failure: { (error: NSError!, reason: String!) -> Void in
self.refreshController.endRefreshing()
}
I've tried putting the call in a 'dispatch_async' main queue but the call is already on the Main queue and the same error arises.
failure: { (error: NSError!, reason: String!) -> Void in
dispatch_async(dispatch_get_main_queue())
{
self.refreshController.endRefreshing()
}
}
This leads me to believe the issue is to do with a pointer to 'self' at the time the failure block is called... I've tried 'weak' and 'unowned' self but these don't resolve it.
failure: { [weak self] (error: NSError!, reason: String!) -> Void in
self?.refreshController.endRefreshing()
}
Any thoughts would be welcome.
UPDATE: Initialisaton
class ResultsViewController: UIViewController, UITableViewControllerDelegate, UITableViewControllerDataSource
{
var refreshController = UIRefreshControl()
override func viewDidLoad()
{
super.viewDidLoad()
// pull-to-refresh setup
self.refreshController.addTarget(self, action: "refreshTable:", forControlEvents: UIControlEvents.ValueChanged)
self.tableView.addSubview(self.refreshController)
}
}
Eventually found the root cause.
I was using AFNetworking for API calls and had created a custom Failure block which included the reason if the API call failed. This was up the chain in the call stack but this error seemed to mask it. None of the above methods were required, but using the weak self method did help the compiler surface the issue in the right file.
The issue could be traced back to the here:
Two block type aliases
typealias SuccessBlock = (responseArray: AnyObject!) -> Void
typealias FailureBlock = (error: NSError, reason: String) -> Void
AFNetworking POST call
private func post(url: String, inout parameters: [String : AnyObject], success: SuccessBlock, failure: FailureBlock)
{
self.sessionManager.POST(url,
parameters: parameters,
success: { (operation: NSURLSessionDataTask!, responseObject: AnyObject!) -> Void in
println("DEBUG: API POST Request to \(url) successful.")
var responses = responseObject as? [AnyObject]
success(responseArray:responses)
},
failure: { (operation: NSURLSessionDataTask!, error: NSError!) -> Void in
let reason = self.getResponseReasonFromError(error)
println("DEBUG: API GET Request to \(url) \nFailed: \(error). \nAPI Response: \(reason)")
failure(error: error, reason: reason)
}
)
}
Error was here in the FailureBlock type alias
typealias FailureBlock = (error: NSError, reason: String) -> Void
typealias FailureBlock = (error: NSError, reason: String!) -> Void
Missing a ! after String.

Nested closures does not like argument list

A UIView needs to change a warning label depending on the completion handler of a custom control:
voucherInputView.completionHandler = {[weak self] (success: Bool) -> Void in
self?.proceedButton.enabled = success
self?.warningLabel.alpha = 1.0
if success
{
self?.warningLabel.text = "Code you entered is correct"
self?.warningLabel.backgroundColor = UIColor.greenColor()
}
else
{
self?.warningLabel.text = "Code you entered is incorrect"
self?.warningLabel.backgroundColor = UIColor.orangeColor()
}
UIView.animateWithDuration(NSTimeInterval(1.0), animations:{ ()-> Void in
self?.warningLabel.alpha = 1.0
})
The final animation block shows an error in the form.
Cannot invoke 'animateWithDuration' with an argument list of type '(NSTimeInterval), animations: ()-> Void)'
If i call this somewhere outside of the completion closure it works.
The problem is that the closure is implicitly returning the result of this expression:
self?.warningLabel.alpha = 1.0
but the closure itself is declared as returning Void.
Adding an explicit return should resolve the problem:
UIView.animateWithDuration(NSTimeInterval(1.0), animations: { ()-> Void in
self?.warningLabel.alpha = 1.0
return
})
Antonio's solution also applies with nested closures, like doing an AFNetworking request within UITableViewRowAction handler.
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {
let cleanRowAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Do Stuff", handler: {[weak self](action: UITableViewRowAction!, indexPath: NSIndexPath!) in
AFHTTPSessionManager(baseURL: NSURL(string: "http://baseurl")).PUT("/api/", parameters: nil, success: { (task: NSURLSessionDataTask!, response: AnyObject!) -> Void in
// Handle success
self?.endEditing()
return
}, failure: { (task: NSURLSessionDataTask!, error: NSError!) -> Void in
// Handle error
self?.endEditing()
return
})
return
})
cleanRowAction.backgroundColor = UIColor.greenColor()
return [cleanRowAction]
}

Resources