AFNetworking 2 Retry Pattern - ios

I'm using AFNetworking 2.2.3, AFNetworking+AutoRetry 0.0.3 and AFKissXMLRequestOperation#aceontech 0.0.4.
I have the following codes to fetch data from server:
+ (void)doNetwork {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFKissXMLResponseSerializer serializer];
NSDictionary *param = [NSDictionary dictionaryWithObjectsAndKeys:#"someValue", #"someKey", nil];
[manager POST:#"http://example.com/api/" parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSError *error;
DDXMLDocument *xml = [[DDXMLDocument alloc] initWithXMLString:operation.responseString options:0 error:&error];
if(error != nil) {
NSLog(#"Error Parsing XML");
[[NSNotificationCenter defaultCenter] postNotificationName:#"FetchAPINotification" object:nil];
} else {
NSString *xPath = #"response/status";
NSArray *arr_status = [xml nodesForXPath:xPath error:nil];
if(arr_status == nil || arr_status.count == 0) {
NSLog(#"Status Not Found");
[[NSNotificationCenter defaultCenter] postNotificationName:#"FetchAPINotification" object:nil];
} else {
int status = [[arr_status objectAtIndex:0] intValue];
if(status == 0) { // OK
[[NSNotificationCenter defaultCenter] postNotificationName:#"FetchAPINotification" object:nil userInfo:[NSDictionary dictionaryWithObjectsAndKeys:#"OK", #"status", nil];
} else if(status == 123) { // Requires Re-login
[self doLogin];
// How should I call the method again?
[self doNetwork];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:#"FetchAPINotification" object:nil];
}
}
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Network Error");
[[NSNotificationCenter defaultCenter] postNotificationName:#"FetchAPINotification" object:nil];
} autoRetry:3];
}
Here is the explanation:
First of all, I issue a HTTP POST request using AFHTTPRequestOperationManager with param. Then, in success block, if status = 0, post a notification to pre-defined notification observer to mark successful.
If there is any error, I post a notification without userInfo to mark the operation is unsuccessful.
However, there is a case that when the server responses status = 123, which means user token has expired and has to re-login to refresh its token.
My question is: How can I re-try the operation after re-login?
Note: I'm not talking about network timeout retry, which I have already implemented.

Related

Getting JSON text did not start with array or object and option to allow fragments not set

Is there any way that I can print/NSLOG the string or the array that is being returned by the API in this following login code of mine:
-(void)loginToAPI:(NSString *)email password:(NSString *)password {
NSString *controller = #"login";
NSString *action = #"authenticate";
NSDictionary *params = #{#"username": email,
#"userpass": password,
#"controller": controller,
#"action": action,
#"app_id": APP_ID,
#"app_key": APP_KEY};
NSLog(#"params %#", params);
if ([self isNetworkAvailable]) {
AFHTTPRequestOperationManager *client = [AFHTTPRequestOperationManager manager];
client.responseSerializer.acceptableContentTypes = [client.responseSerializer.acceptableContentTypes setByAddingObject:#"text/html"];
[client POST:[[Config sharedInstance] getAPIURL]
parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSDictionary *jsonObject= responseObject;
NSString *status = [jsonObject objectForKey:#"status"];
NSLog(#"Request Successful, response '%#'", jsonObject);
if ([status isEqualToString:#"success"]) {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSDictionary *data = [jsonObject objectForKey:#"data"];
NSDictionary *userDict = [data objectForKey:#"user"];
NSDictionary *djsaDict = [data objectForKey:#"djsa"];
User *currentUser = [[User alloc] initWithProperties:userDict];
[currentUser save];
DJSA_Cutomer *djsa = [[DJSA_Cutomer alloc] initWithProperties:djsaDict];
NSData *userData = [NSKeyedArchiver archivedDataWithRootObject:currentUser];
NSData *djsaData = [NSKeyedArchiver archivedDataWithRootObject:djsa];
[userDefaults setObject:userData forKey:#"currentUser"];
[userDefaults setObject:djsaData forKey:#"djsa_customer"];
[userDefaults synchronize];
[[NSNotificationCenter defaultCenter] postNotificationName: #"LOGIN_SUCCESS" object:currentUser userInfo:nil];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName: #"LOGIN_FAILED" object:nil userInfo:nil];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Request Failed with Error - loginToAPI: %#, %#", error, error.userInfo);
[[NSNotificationCenter defaultCenter] postNotificationName: #"SERVER_ERROR" object:nil userInfo:nil];
}];
}
}
It is always failing and going to the part "Request Failed with Error - loginToAPI". Is it possible to see the actual values returned by the server so I can diagnose the problem?
Thanks!
I had the same problem. The solution to it in my case was
client.responseSerializer = [AFHTTPResponseSerializer serializer];
I did that but I was still getting same error. The problem was that I was setting client.requestSerializer instead of client.responseSerializer. Small mistakes but takes lot of time.
You can get the status code off the response property from the operation object which should give you some idea of what went wrong:
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Request Failed with Error - loginToAPI: %#, %#", error, error.userInfo);
NSLog(#"%#", [NSHTTPURLResponse localized​String​For​Status​Code:operation.response.statusCode]);
[[NSNotificationCenter defaultCenter] postNotificationName: #"SERVER_ERROR" object:nil userInfo:nil];
}];
Otherwise, it's best to use a tool like Charles web proxy to inspect the actual request and response. It has a free trial which should be sufficient to do what you need, and is relatively easy to set up.
Replace this line:
client.responseSerializer.acceptableContentTypes = [client.responseSerializer.acceptableContentTypes setByAddingObject:#"text/html"];
with this:
[client.securityPolicy setValidatesDomainName:NO];
[client.securityPolicy setAllowInvalidCertificates:YES];
client.responseSerializer = [AFHTTPResponseSerializer serializer];

GET Request with AFHttpRequestOperationManager not working on 3G

GET Request with AFHttpRequestOperationManager not working on 3G works only on wifi
here is my code
it works correctly on wifi or when request data was cashed
I find a question put for POST request
-(void)getHomeworksWithChildId:(double)childId AndIsFromRecycleBin:(NSNumber*) isFromRecyclebin
{
if (([isFromRecyclebin isEqualToNumber:#1])) {
isFromRecyclebin = #0;
}
else
isFromRecyclebin = #1;
NSString *reportsUrl = [self getFullURLString:[NSString stringWithFormat:homeworks_URL,self.currentUser.iDProperty,childId,isFromRecyclebin]];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:#"application/json"];
[manager GET:reportsUrl parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject)
{
if ([(NSArray*)responseObject count]>0)
{
NSMutableArray *allPictures = [[NSMutableArray alloc]init];
for (int i=0; i<[(NSArray*)responseObject count]; i++) {
Report *report = [[Report alloc]initWithDictionary:[responseObject objectAtIndex:i]];
[allPictures addObject:report];
}
NSDictionary *userInfo= [[NSDictionary alloc]initWithObjects:#[allPictures,#0] forKeys:#[#"reports",#"digital"]];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setInteger:1 forKey:#"catname"];
[defaults synchronize];
[SVProgressHUD showSuccessWithStatus:NSLocalizedString(#"", #"")];
[[NSNotificationCenter defaultCenter] postNotificationName:REPORTS_RECIEVED_NOTIFICATION object:nil userInfo:userInfo];
}
else
[[NSNotificationCenter defaultCenter] postNotificationName:REPORTS_RECIEVED_NOTIFICATION object:nil userInfo:nil];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[[NSNotificationCenter defaultCenter] postNotificationName:REPORTS_RECIEVED_NOTIFICATION object:nil userInfo:nil];
[SVProgressHUD showErrorWithStatus:error.localizedDescription];
NSLog(#"Error: %#", error);
}];
}

iOS SDK global error handler for errors from server

I have a class called Request. A request is asynchronous. At the end of the request, I check the response for a server error. If there is a server error, I send out a notification.
+(BOOL)checkForResponseError:(NSArray*)response{
if ([[response objectAtIndex:1] boolValue] == YES) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"Server Error" object:nil userInfo:#{#"error":response[0]}];
NSLog(#"Server Response Error %#", response[0]);
return YES;
//[NSException raise:#"Server error on response" format:#"Response error: %#", response[0]];
}else if(response == nil){
NSLog(#"nil response");
#ifdef DEBUG
[NSException raise:#"Error 500" format:#"Check the logs for more information"];
#endif
return YES;
}
return NO;
}
+(Request*)request{
Request *request = [[Request alloc] init];
request.success = ^(AFHTTPRequestOperation *operation, id responseObj) {
NSLog(#"Success on operation %# with result %#", [operation.request.URL absoluteString], operation.responseString);
NSArray *result = responseObj;
if (![Request checkForResponseError:result]){
request.completion(result);
}
};
request.failure = ^(AFHTTPRequestOperation *operation, NSError *error){
NSLog(#"Error: %#, result %#", error.localizedDescription, operation.responseString);
#ifdef DEBUG
[NSException raise:#"action failed" format:#"Link %# Failed with objective c error: %#", [operation.request.URL absoluteString], error];
#endif
};
return request;
}
-(AFHTTPRequestOperationManager*)getWithAction:(NSString *)action parameters:(NSDictionary*)params success:(void (^)(NSArray*))c{
NSLog(#"Getting with action %# and params %#", action, params);
completion = c;
AFHTTPRequestOperationManager *manager = [Header requestOperationManager];
[manager GET:[NSString stringWithFormat:#"%#%#", BASE_URL, action] parameters:params success:success failure:failure];
return manager;
}
The above are the relevant methods in the response class. Now- whenever the request throws up a Server Error notification, no matter where it is in the app, I need the app to show an alert immediately. So I thought to simply put a handler in the application delegate as such:
[[NSNotificationCenter defaultCenter] addObserverForName:#"Server Error" object:nil queue:nil usingBlock:^(NSNotification* notif){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"" message:[notif userInfo][#"error"] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alert show];
}];
Problem is, that when there is an error, the app will freeze for a few minutes and then show the alert. I understand this has something to do with the application lifecycle. Is there a way to achieve what I want (from a code simplicity standpoint), and not have the app freeze for a few minutes?I just don't have any idea how to solve this.
Thank you!

Handling UI update in a for loop - iOS

I'm using NSNotificationcentre to update the UI from a for loop. The UI isn't updated until the execution is out of the loop. Is there way to handle this case?
Here is my code below:
- (void)uploadContent{
NSURLResponse *res = nil;
NSError *err = nil;
for (int i = 0; i < self.requestArray.count; i++) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[NSNotificationCenter defaultCenter] postNotificationName:kUpdatePreviewImageView object:nil userInfo:#{#"image": [self.imageArray objectAtIndex:i],#"count":[NSNumber numberWithInt:i],#"progress":[NSNumber numberWithFloat:0.5f]}];
}];
ImageUploadRequest *request = [self.requestArray objectAtIndex:i];
NSData *data = [NSURLConnection sendSynchronousRequest:request.urlRequest returningResponse:&res error:&err];
if (err) {
NSLog(#"error:%#", err.localizedDescription);
}
NSError *jsonError;
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&jsonError];
NSLog(#"current thread %#",[NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[NSNotificationCenter defaultCenter] postNotificationName:kUpdatePreviewImageView object:nil userInfo:#{#"image":[self.imageArray objectAtIndex:i],#"count":[NSNumber numberWithInt:i],#"progress":[NSNumber numberWithFloat:1.0f]}];
}];
}
[[NSNotificationCenter defaultCenter] postNotificationName:kImageUploaded object:nil];
}
In my viewcontroller.m file I have the observer declared under viewdidLoad()
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updatePreviewView:) name:kUpdatePreviewImageView object:nil];
The updatepreview: class is defined below:
-(void)updatePreviewView:(NSNotification *)notify{
NSDictionary *previewImageDetails = [notify userInfo];
self.previewImageView.image = previewImageDetails[#"image"];
hud.labelText = [NSString stringWithFormat:#"Uploading media %# of %lu",previewImageDetails[#"count"],(long unsigned)self.mediaDetail.count];
hud.progress = [previewImageDetails[#"progress"] floatValue];
}
Since the for loop is running the main thread this thread gets blocked until the for look is completed. Since the main threat is also the UI thread your UI updated aren't done until the loop is finished.
You should run the loop on a background thread an the UI changes should them be run asynchronies on the main thread.
And in your updatePreviewView: make sure the code will run on the main thread.
Do this:
-(void)updatePreviewView:(NSNotification *)notify{
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *previewImageDetails = [notify userInfo];
self.previewImageView.image = previewImageDetails[#"image"];
hud.labelText = [NSString stringWithFormat:#"Uploading media %# of %lu",previewImageDetails[#"count"],(long unsigned)self.mediaDetail.count];
hud.progress = [previewImageDetails[#"progress"] floatValue];
});
}
You should take it in the main thread. But NSOperationQueue could be not sending all in for loop. You can take operation in async queue and send it without NSOperationQueue
dispatch_async(dispatch_get_main_queue(), ^{
NSDictionary *previewImageDetails = [notify userInfo];
self.previewImageView.image = previewImageDetails[#"image"];
hud.labelText = [NSString stringWithFormat:#"Uploading media %# of %lu",previewImageDetails[#"count"],(long unsigned)self.mediaDetail.count];
hud.progress = [previewImageDetails[#"progress"] floatValue];
});

How do I make AFHTTP request wait for operation

At this moment I have a method that calls for the download of data from the web using AFHTTPRequestOperation like so:
- (void)downloadDataForRegisteredObjects:(BOOL)useUpdatedAtDate {
NSLog(#"downloadDataForRegisteredObjects");
NSMutableArray *operations = [NSMutableArray array];
for (NSString *className in self.registeredClassesToSync) {
NSDate *mostRecentUpdatedDate = nil;
if (useUpdatedAtDate) {
mostRecentUpdatedDate = [self mostRecentUpdatedAtDateForEntityWithName:className];
}
NSMutableURLRequest *request = [[SDAFParseAPIClient sharedClient] GETRequestForAllRecordsOfClass:className updatedAfterDate:mostRecentUpdatedDate];
AFHTTPRequestOperation *operation = [[SDAFParseAPIClient sharedClient] HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([responseObject isKindOfClass:[NSDictionary class]]) {
// Write JSON files to disk
[self writeJSONResponse:responseObject toDiskForClassWithName:className];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Request for class %# failed with error: %#", className, error);
[[NSNotificationCenter defaultCenter]
postNotificationName:kSDSyncEngineSyncINCompleteNotificationName
object:nil];
}];
[operations addObject:operation];
}
[[SDAFParseAPIClient sharedClient] enqueueBatchOfHTTPRequestOperations:operations progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {
} completionBlock:^(NSArray *operations) {
// Process JSON into CD
if (useUpdatedAtDate) {
[self processJSONDataRecordsIntoCoreData];
}
}];
}
From what I understand, we create an NSURLMutableRequest, pass it to an AFHTTPRequestOperation with a success & failure block.
The success block says, if and when successful, test if dictionary and if so, write it to disk. The failure block says, log the error and post a notification.
The method gets called twice in my app, in series, one after the other. The first time it returns an empty responseObject but the second time it returns a full responseObject.
Why should that be the case?

Resources