I am currently working on an iOS App that is developed using Swift. For the REST calls, I am using the RestKit framework.
The next stage of my project is to start using authentication against the services. An issue that I have come up against is handling 401 (Not Authenticated) responses from the service. In all of these circumstances, I would like to display a login page. I want to avoid implementing the error handling of this multiple times.
I followed the tutorial at http://blog.higgsboson.tk/2013/09/03/global-request-management-with-restkit/. However, this is in Objective-C and I would like to do things slightly differently.
As such, I want to build a class that extends RKObjectRequestOperation as in the tutorial but using Swift. I have come up with an issue as I am receiving the error
Overriding method with selector 'setCompletionBlockWithSuccess:failure:' has incompatible type '((RKObjectRequestOperation, RKMappingResult) -> Void (RKObjectRequestOperation, NSError) -> Void) -> Void'
I am a bit stuck on this and so was hoping someone could help. The code for the method that is failing is below.
class CustomRequestOperation : RKObjectRequestOperation {
func setCompletionBlockWithSuccess(success: (operation: RKObjectRequestOperation, mappingResult: RKMappingResult) -> Void, failure: (operation: RKObjectRequestOperation, error: NSError) -> Void) -> Void {
}
}
Can anyone point out what is wrong with my method signature?
You're overriding the method so you can get Xcode to add the signature for you if you start typing the method name and hie escape.
It should be
func setCompletionBlockWithSuccess(success: (operation: RKObjectRequestOperation, mappingResult: RKMappingResult) -> Void, failure: (operation: RKObjectRequestOperation, error: NSError) -> Void) {
(you are adding a return spec that doesn't exist in the superclass method)
Here is the full class in swift...
class CustomRKObjectRequestOperation : RKObjectRequestOperation
{
override func setCompletionBlockWithSuccess(success: ((RKObjectRequestOperation!, RKMappingResult!) -> Void)!, failure: ((RKObjectRequestOperation!, NSError!) -> Void)!) {
super.setCompletionBlockWithSuccess({ (operation, RKMappingResult mappingResult) -> Void in
if ((success) != nil) {
success(operation, mappingResult);
}
}, failure: { (RKObjectRequestOperation operation, NSError error) -> Void in
NSNotificationCenter.defaultCenter().postNotificationName("connectionFailure",object:operation)
if ((failure) != nil) {
failure(operation, error);
}
})
}
}
App Delegate
Register notification
NSNotificationCenter.defaultCenter().addObserver(self, selector:"connectionFailedWithOperation:",name:"connectionFailure", object:nil)
func connectionFailedWithOperation(notification: NSNotification ){
let operation = notification.object as! RKObjectRequestOperation?;
if ((operation) != nil) {
let statusCode = operation!.HTTPRequestOperation.response.statusCode;
if (statusCode == 401) {
// log user out
}
}
}
Related
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
I'm currently trying to use the Venmo-iOS-SDK for an application I am working on. The SDK is in objective-C, while I'm trying to use it with a swift app.
I'm having trouble translating the syntax of a completion obj-c block to swift. I found sample code implementing a function I want to use.
- (IBAction)logInButtonAction:(id)sender {
[[Venmo sharedInstance] requestPermissions:#[VENPermissionMakePayments,
VENPermissionAccessProfile]
withCompletionHandler:^(BOOL success, NSError *error) {
if (success) {
NSLog("Success")
} else {
NSLog("Failure")
}
}];
}
I've tried doing this
#IBAction func loginButtonAction(sender: AnyObject){
Venmo.sharedInstance().requestPermissions([VENPermissionMakePayments, VENPermissionAccessPhone], withCompletionHandler: { (success: Bool, error: NSErrorPointer) -> Void in
if success{
println("Yes")
}else{
println("No")
}
})
}
But get the error
"Cannot invoke 'requestsPermissions with an argument list of type
'([String], withCompletionHandler: (Bool, NSError) -> Void)'
Is this a problem with how i'm translating the block? Or something else. Looking at the Venmo-SDK the obj-C functions are defined like this
- (void)requestPermissions:(NSArray *)permissions withCompletionHandler:(VENOAuthCompletionHandler)handler;
and
- (void)requestPermissions:(NSArray *)permissions withCompletionHandler:(VENOAuthCompletionHandler)handler;
You can write it like this (note the lack of types on the completion handler params):
#IBAction func loginButtonAction(sender: AnyObject) {
Venmo.sharedInstance().requestPermissions([VENPermissionMakePayments, VENPermissionAccessPhone], withCompletionHandler: { (success, error) -> Void in
// code here
})
}
A bit more concise with Swift 2 syntax would be omitting the -> Void and explicit withCompletionHandler: parameter:
#IBAction func loginButtonAction(sender: AnyObject) {
Venmo.sharedInstance().requestPermissions([VENPermissionMakePayments, VENPermissionAccessPhone]) { (success, error) in
// code here
}
}
You'll also want to make sure you change your println statements to print.
In one of my app I have used block for webservice calling and getting response. Now I want to write this app in swift, but I am getting trouble to use blocks/Closure in Swift.
Here is my objective C code which I want to migrate in swift:
calling a class method of Communicator
[[Communicator sharedInstance]callWebService:WS_LOGIN withMethod:POST_METHOD andParams:params showLoader:YES completionBlockSuccess:^(id obj) {
//Do play with data
}completionBlockFailiure:^(id obj) {
//Show alert with error
}];
in communicator class
-(void)callWebService:(NSString *)serviceName withMethod:(NSString *)methodName andParams:(NSDictionary *)params showLoader:(BOOL)showLoader completionBlockSuccess:(void (^)(id))aBlock completionBlockFailiure:(void (^)(id))aFailBlock
{
if (showLoader) {
// show loader
}
[self performRequestWithServiceName:serviceName method:methodName andParams:params successblock:aBlock failureblock:aFailBlock];
}
- (void)performRequestWithServiceName:(NSString *)serviceName method:(NSString*)methodName andParams:(NSDictionary*)params
successblock:(void (^)(id obj))successBlock
failureblock:(void (^)(id obj))failBlock {
if(callSuceess){
successBlock(#"Success");
}else{
successBlock(nil);
}
}
For Swift. Use AnyObject for id objc type.
func callWebservice (serviceName: String, withMethod method: String, andParams params: NSDictionary, showLoader loader: Bool, completionBlockSuccess aBlock: ((AnyObject) -> Void), andFailureBlock failBlock: ((AnyObject) -> Void)) {
if loader {
// Show loader
}
performRequestWithServiceName(serviceName, method: method, andParams: params, success: aBlock, failure: failBlock)
}
func performRequestWithServiceName(serviceName: String, method methodName: String, andParams params: NSDictionary, success successBlock: ((AnyObject) -> Void), failure failureBlock: ((AnyObject) -> Void)) {
if callSuceess {
successBlock("Success")
}else {
successBlock(nil)
}
}
UPDATE: An example when you want call web service. See code below
callWebservice("your-service-name", withMethod: "your-method", andParams: ["your-dic-key": "your dict value"], showLoader: true/*or false*/, completionBlockSuccess: { (success) -> Void in
// your successful handle
}) { (failure) -> Void in
// your failure handle
}
Your code might look like this:
func callWebService(serviceName: String, method: String, params: [String : AnyObject], showLoader: Bool, success: (responseObject: AnyObject) -> Void, failure: (responseObject: AnyObject) -> Void) {
if showLoader {
// show loader
}
performRequest(serviceName, method: method, params: params, success: success, failure: failure)
}
func performRequest(serviceName: String, method: String, params: [String : AnyObject], success: (responseObject: AnyObject) -> Void, failure: (responseObject: AnyObject) -> Void) {
}
I replaced NSDictionary with [String : AnyObject]. If you can replace any of the uses of AnyObject with more specific types, your code will be cleaner and more stable.
For Swift Closures we have to use ( ) -> ( )
For example:
func yourFunction(success: (response: AnyObject!) -> Void, failure: (error: NSError?) -> Void) {
}
You can call it as:
yourFunction({(response) -> Void in
// Success
}) { (error) -> Void in
// Handle Errors
}
Hope it will help you to create Closures with your requirements.
In the communicator class the method that cals the webservice would be defined something like this depending on the type of object you want to return
func performRequest(serviceName: NSString, methodName: NSString,paramaters:NSDictionary, successblock: (String)->(), failureBlock: () -> ()) {
if(callSuccess) {
successblock("Success")
} else {
failureBlock()
}
We define the success and failure blocks types as by their function signatures in the case above success is defined as a method that takes a string as an input parameter and returns nothing so we can then call successBlock passing in a string. The failure block is defined above as a block that takes no parameters and returns nothing.
To call this method
func callWebService(serviceName: NSString, method: NSString and parameters: NSDictionary, showLoader: Bool, completionBlockSuccess:(String) -> (), completionBlockFailiure:() -> ()) {
if (showLoader) {
// show loader
}
performRequest(serviceName: serviceName, methodName: method, parameters, successBlock:completionBlockSuccess, failureBlock: completionBlockFailiure)
}
Finally to call this
Communicator.sharedInstance().callWebService(serviceName: WS_LOGIN , method: POST_METHOD and parameters: params, showLoader: true, completionBlockSuccess:{ returnedString in
//Do play with data
}, completionBlockFailiure:{
//Show alert with error
})
For the completion block we define a variable returnedString to allow us to manipulate that input parameter (in the above example it would be the string "Success"). I assume your data is not just returning a string though so you will probably need to play around with they type depending on what your service returns.
Also here I tried to match your method signatures by using NSString and NSDictionary though depending on your needs the Swift equivalents String and [String: AnyObject] could be more appropriate.
func processingWithAnyObject(input: String, completion: #escaping (_ result: AnyObject) -> Void) {
...
completion(response.result.value! as AnyObject)
}
processingWithAnyObject("inputString") {
(result: AnyObject) in
print("back to caller: \(result)")
}
So far I am having issues with blocks like this:
user.signUpInBackgroundWithBlock {
(succeeded: Bool!, error: NSError!) -> Void in
if error == nil {
println("success")
} else {
println("\(error)");
// Show the errorString somewhere and let the user try again.
}
}
When I add this into Xcode I get this:
Cannot invoke 'signUpInBackgroundWithBlock' with an argument list of type '((Bool!, NSError!) -> Void)'
When I run this code in Xcode 6.3 (non beta) it works fine. But in the Beta it fails and wont allow me to build. Any ideas if this will be cleared up or if there is a different implementation that I could use. Ive tried using just the signUpInBackgroundWithTarget but Im just not able to access the error correctly if one is received.
be sure you are using SDK version 1.7.1, then removing the types from your closure should do the trick:
user.signUpInBackgroundWithBlock { (succeeded, error) -> Void in
if error == nil {
println("success")
} else {
println("\(error)");
// Show the errorString somewhere and let the user try again.
}
}
Due to the new addition of "Nullability Annotations" to Swift 1.2, you have to rewrite the code above like this (using Parse 1.7.1+):
user.signUpInBackgroundWithBlock { (succeeded: Bool, error: NSError?) -> Void in
if let error = error {
println(error) // there is an error, print it
} else {
if succeeded {
println("success")
} else {
println("failed")
}
}
}
Parse is now returning optionals (?) instead of explicitely unwrapped objects (!).
Notation of Swift is changed
class AAPLList : NSObject, NSCoding, NSCopying {
// ...
func itemWithName(name: String!) -> AAPLListItem!
func indexOfItem(item: AAPLListItem!) -> Int
#NSCopying var name: String! { get set }
#NSCopying var allItems: [AnyObject]! { get }
// ...
}
After annotations:
class AAPLList : NSObject, NSCoding, NSCopying {
// ...
func itemWithName(name: String) -> AAPLListItem?
func indexOfItem(item: AAPLListItem) -> Int
#NSCopying var name: String? { get set }
#NSCopying var allItems: [AnyObject] { get }
// ...
}
So you can change
(succeeded: Bool!, error: NSError!) -> Void in
to
(success: Bool, error: NSError?) -> Void in
Which Parse SDK are you using? They released version 1.7.1 a few days ago that should fix your issue.
Change:
(succeeded: Bool!, error: NSError!) -> Void in
to
(succeeded, error) -> Void in
This change is required due to changes in the Parse SDK
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.