AFNetworking with Unreliable wifi - Detecting / Retrying connections when they timeout - ios

I'm using AFNetworking in my app to connect / download data from a web service. This app is deployed via Enterprise deployment to users at various locations. At one of the locations where people use our app, the wifi network seems to randomly go down / come back up in a few seconds. In these cases, the requirement was to retry a request thrice before giving up and failing. I've got the retry part of it working fine, but have some problems detecting a network failure. Some code:
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:
^(AFHTTPRequestOperation *operation, id responseObject) {
[self parseResponse:operation.responseString];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self messageFailureWithCode:error.code
reason:[NSString stringWithFormat:#"%#", error]];
}
];
The error code I'm retrying on is NSURLErrorTimedOut. But I just got another log file which indicated a failure with the following error: -1003 "A server with the specified hostname could not be found."
I could add this code to my list too, but I wanted to be sure that I'm capturing all the errors and not just handling them as they appear. I was looking through the NSURLError.h file and found the following error codes that vaguely look like they could be caused by a network failure.
Can someone help me figuring out under what conditions each error is triggered and if I'm missing any error codes? The list is below:
NSURLErrorCannotFindHost
NSURLErrorTimedOut
NSURLErrorCannotConnectToHost
NSURLErrorNetworkConnectionLost
NSURLErrorDNSLookupFailed
NSURLErrorResourceUnavailable
NSURLErrorNotConnectedToInternet
Thanks,
Teja.

It sounds like you could safely just retry every failure with an error domain of NSURLErrorDomain (exempting the case where an operation was cancelled). That should cover all of those cases.

Related

CKModifyRecordsOperation modifyRecordsCompletionBlock not being called

I'm using CKModifyRecordsOperation to save a set of records and if I have internet connection all works well and completion block is being called. But when I don't have connection the completion block is not being called and I don't get any information that my operations failed.
I'm using the following code in completion block
modifyOperations.modifyRecordsCompletionBlock = ^(NSArray *savedRecords, NSArray *deletedRecordIDs, NSError *error)
{
if(error){
NSLog(#"Error: %#", error.localizedDescription);
}
item.creatorRecordId = record.recordID;
};
and then I'm performing operation using
[self.publicDB addOperation:modifyOperations];
Any ideas how can I get an information if the operation failed for example in the case where there is no internet connection?
CloudKit operations have their qualityOfService property set to NSQualityOfServiceUtility by default.
Operations that use NSQualityOfServiceUtility or NSQualityOfServiceBackground may be marked as using discretionary network requests. The system can hold discretionary network requests if network connectivity is poor, so you might not get a response from the server until conditions improve and the system sends the request.
If you'd like your request to be sent immediately in all cases, set CKOperation.qualityOfService to NSQualityOfServiceUserInitiated or NSQualityOfServiceUserInteractive.

AFNetworking with request error code 999

I'm new to objective-c and i'm having a hard time with a AFNetworking.
So the thing is that i want to make a simple POST request to a server who will send me back a salt.
I'v make a simple app, in order to test my request but i don't understand why i'm getting the error code 999.
Here a sample of my code.
+ (void)simpleRequest; {
NSURL *mailserver = [NSURL URLWithString:#"https://localhost:4443/"];
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]initWithBaseURL:mailserver];
manager.securityPolicy.allowInvalidCertificates = TRUE;
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
NSDictionary *parameters = #{#"username": #"testtest"};
[manager POST:#"api/v0/login/salt" parameters:parameters success:^(NSURLSessionDataTask *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
I' have link this code to a simple Button which's calling this function.
I have an other app, in ruby motion which's work fined with this function i can get the response without any error. But with this simple app i can't do any request, they all returned this error code 999.
Error: Error Domain=NSURLErrorDomain Code=-999 "cancelled"
UserInfo={NSErrorFailingURLKey=https://localhost:4443/api/v0/login/salt,
NSLocalizedDescription=cancelled,
NSErrorFailingURLStringKey=https://localhost:4443/api/v0/login/salt}
So i'm really wondering what i'm doing wrong, anyone can help me on this ?
Thanks
EDIT:
Is it the good way for saving the manager in a property or am i doing something wrong ?
If it's the good way, this seems to not work
Thanks for the help
.h file
#property (nonatomic, retain) AFHTTPSessionManager *session;
.m file
#synthesize session;
- (IBAction)log:(id)sender {
NSURL *mailserver = [NSURL URLWithString:#"https://localhost:4443/"];
self.session = [[AFHTTPSessionManager alloc]initWithBaseURL:mailserver];
self.session.securityPolicy.allowInvalidCertificates = TRUE;
self.session.responseSerializer = [AFJSONResponseSerializer serializer];
self.session.requestSerializer = [AFJSONRequestSerializer serializer];
NSDictionary *parameters = #{#"username": #"testtest"};
[self.session POST:#"api/v0/login/salt" parameters:parameters success:^(NSURLSessionDataTask *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
In my case iOS 10 SDK's caused AFNetworking error code -999. If you're trying to reach a server that has SSL and you don't want to check it out, add some privacy Policy to Afnetworking
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
securityPolicy.allowInvalidCertificates = YES;
[securityPolicy setValidatesDomainName:NO];
That's error -999, not 999. That is NSURLErrorCancelled. Your request has been cancelled before it can be completed.
Looking at your code, you aren't retaining the AFHTTPSessionManager *manager anywhere. This means that the manager will be disposed as soon as +simpleRequest returns. I'm guessing that this is what is cancelling your request.
You need to save the manager so that it lives for the full duration of the request. Save it in a property somewhere.
In my case iOS 9 SDK's "App transport security" cause AFNetworking error code : -999.
If you're trying to reach a server that doesn't have a SSL add keys like screenshot below.
I noticed that your API endpoint indicates to a secure connection:
httpS://localhost:4443/api/v0/login/salt
Just try it just in case, maybe it repeats your situation.
In my case, this was a typo in the API manager code. Which from the part can be said is connected with App Transport Security Settings.
Just changed the protected protocol from httpS:// to http:// and the error:
NSURLErrorDomain Code = -999 "cancelled"
was gone and it all worked!
+And also if you had a similar problem. Be sure to discuss this with a backend specialist who deals with the server or API configuration for your application. This means that the server does not have valid security certificates. Perhaps you still need a secure connection. Or this specialist can again configure everything back from http:// to httpS://, and I'm not sure (did not check) whether this will work again when in the code you are already using a non-secure http:// connection.
[Original OP question may not mention Cordova but this link was very high in the search result when Googling this issue]
For my Cordova project (or similar), turns out it was a plugin issue. Make sure you're not missing any plugins and make sure they're installed properly without issue.
Easiest way to verify this is simply to start fresh by recreating the Cordova project (cordova create <path>) along with the required platforms (cordova platform add <platform name>) and add each plugin with the verbose flag (--verbose) so that you can see if anything went wrong in the console log while the plugin is being downloaded, added to project and installed for each platform (cordova plugin add cordova-plugin-device --verbose)
Recap:
cordova create <path>
cordova platform add <platform name>
cordova plugin add cordova-plugin-device --verbose

Strongloop iOS User Creation Error

I'm trying to create and save a test user in server with this code:
LBRESTAdapter *adapter = ((AppDelegate *)[[UIApplication sharedApplication] delegate]).adapter;
if (adapter) {
TeacherRepository *repo = (TeacherRepository *)[adapter repositoryWithClass:[TeacherRepository class]];
if (repo) {
Teacher *st = (Teacher *)[repo createUserWithEmail:#"test#test.com" password:#"test"];
if (st) {
[st saveWithSuccess:^{
NSLog(#"Saved in server!");
} failure:^(NSError *error) {
NSLog(#"Error: %#", error.description);
}];
}
}
}
but I keep on getting this error response:
Error Domain=AFNetworkingErrorDomain Code=-1011 "Expected status code in (200-299), got 404"
I have searched for this error and similar others, but couldn't find anything that would solve my problem, so what could be causing this?
The HTTP Status Code of 404 means that the resource was not found:
404 Not Found
The server has not found anything matching the Request-URI. No indication is given of whether the condition is temporary or permanent. The 410 (Gone) status code SHOULD be used if the server knows, through some internally configurable mechanism, that an old resource is permanently unavailable and has no forwarding address. This status code is commonly used when the server does not wish to reveal exactly why the request has been refused, or when no other response is applicable.
It would appear that there is an error in the building of the URL in your AFNetworking code. You haven't shared that portion of code, so it's hard to comment on the specifics. I think if you log the entire error object (not just error.description) it will show you what URL it attempted to use unsuccessfully.

AFNetworking globally prompt user to login on 401 error code?

I have been struggling to find a clean solution for this problem for a few I have created an app which makes multiple restful web service requests which work fine however part of the request the login details or API Key could expire and I need to be able to handle this and present the user the login screen again.
In my API Client class I am doing the following which works fine, however because the app does multiple web service requests I am seeing the UI AlertView multiple times.
Any ideas on how I can make this block of code only run once for the first error which occurs and only show one alert view?
AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest:apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
completionBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSInteger statusCode = [operation.response statusCode];
if (statusCode == 401) {
[UIAlertView error:#"Your session has expied, please log in again."];
[[NSNotificationCenter defaultCenter]
postNotificationName:#"Logout"
object:self];
} else {
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}
}];
One way would be to create a global variable which contains the current login status. You should check this login status before a request or before the success/failure blocks if the requests are not chained together.
A better approach would be to create a NSOperationQueue to manage the AFJSONRequestOperation objects. This would give you more control over the lifespan of each request. If one returns a 401 then you could cancel all the operations in the queue.
You can find more about creating and using queue here at this link.
Typically you encounter a similar issue when initialising the shared instance of a singleton object that you want to avoid performing the initialisation more than once.
One way to solve this is using Grand Central Dispatch's dispatch_once, as shown below, which is also included in Xcode as a default snippet (GCD: Dispatch Once). In your case you'd present the alert inside the block you pass to dispatch_once.
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
/*code to be executed once*/
});

How to tell if TWRequest performRequestWithHandler failed or succeeded

I am posting a request and checking for errors like so:
// Send request out on a background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
[postRequest performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
if ([urlResponse statusCode] == 200) {
NSLog(#"Tweet Successful");
}else {
NSLog(#"Tweet Failed");
if (responseData) {
// Use the NSJSONSerialization class to parse the returned JSON
NSError *jsonError;
NSArray *requestResponse =
[NSJSONSerialization JSONObjectWithData:responseData
options:NSJSONReadingMutableLeaves
error:&jsonError];
if (requestResponse) {
NSLog(#"%#",requestResponse);
} else {
NSLog(#"%#",jsonError);
}
}
}
}];
});
It seems to work fine, so far as the request is concerned. My intention was to queue the request on failure and try again at a later date, or not depending on the error and how many attempts had failed thus far.
The problem I have is that I find it frequently appears to fail, with error 34 "Page not Found" where in fact the request did succeed, was posted as intended and can be seen clear as day in twitter stream.
Now, if I can't trust the error code returned then I can't really go on and keep retrying. How do I know for sure if it succeeds or fails?
RECENT OBSERVATIONS:
Recently, I tried to post a photo from Apple photo app directly and it sent me an alert that read "Tweet may not have been successful". That is funny, because that is basically what I was coming to as my only option in this case. This makes me think that even Apple had to acknowledge that there is not way to tell for sure that post failed when the return does not confirm it.
according to every example found in researching this problem, none of which use any other API than what you are using, and including this example from twitter's own documentation for posting a photo to the API, none of them check the urlResponse code first.
the most that ever seems to be done is this example from twitter's documentation where a GET is performed and then the response is processed. notice that it checks the responseData first, and if it exists, it is simply treated like success. only if there is no responseData is there an attempt to bother looking at the error. the example does not bother with the urlResponse … and neither does any other example i saw in 10 minutes of googling.
(finally, i doubt this matters, or it may be because you cleaned up the example for the display, but you're processing the error on the main-queue when you are not performing any UI. you could do the processing in the handler immediately, and pass along whatever post-processing you do to whatever UI you are trying to display it with. i doubt post-processing the response in the main-queue as opposed to the queue of the handler (as shown in both examples cited here and all other examples i've seen) is really causing a problem, but i wonder if it might cut down on the incidence of false negatives you are seeing. at the very least, it will make your response and any UI display of the response cleaner and more efficient.)

Resources