How to get AFHTTPRequestOperation to call selectors, not blocks, when completed? - iphone

AFHTTPRequestOperation likes to call GCD blocks when the request operation has been completed. Is there a way to get it to call method selectors instead? I am transitioning my app from ASIHTTPRequest to AFNetworking, and my app is built around selectors and not blocks.

You could call your selector inside of the completion block:
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:urlRequest
success:^(NSURLRequest *completedURLRequest, NSHTTPURLResponse *response, NSDictionary *json) {
[self callMyCustomSuccessMethod:json];
}
failure:^(NSURLRequest *errorRequest, NSHTTPURLResponse *response, NSError *error, id JSON) {
[self callMyCustomErrorMethod:error];
}];
[operation start];

Not sure if AFHTTPRequestOperation supports selector-based callbacks, but you can easily wrap a call to your selector in a block:
success:^(AFHTTPRequestOperation *operation, id responseObject) {
[myDelegate onSuccess:operation];
}
This would work for a callback method declared as:
- (void)onSuccess:(AFHTTPRequestOperation*)operation;

Related

How to determine reason for failure with AFNetworking AFJSONRequestOperation

I've inherited a project and am implementing AFNetworking and reading the docs it sounds great and much simpler than the current code. I have a url with json data so I'm doing the following, but getting the failure block and no idea why. I'm sure it's there, but how can I dig into AF and log the responses to determine failure reason. I know the url works, but perhaps its hitting the url but having trouble parsing?
NSString *listURL = [NSString stringWithFormat:GET_LIST,BASE_URL];
NSURL *url = [NSURL URLWithString:briefListURL];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest: request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
self.list = [NSArray arrayWithArray:(NSArray *)JSON];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
[self listOperationDidFail];
}];
thanks to #Larme this worked:
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) { NSLog(#"Error: %#", error); }
also, to answer my own issue above with text/plain. just add this before setting up operation:
[AFJSONRequestOperation addAcceptableContentTypes:[NSSet setWithObject:#"text/plain"]];

Waiting for AFJSONRequestOperation to complete

I'm working with AFNetworking to get some JSON from the web. How can I get the response from the asynchronous request returned? Here's my code:
- (id) descargarEncuestasParaCliente:(NSString *)id_client{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://whatever.com/api/&id_cliente=%#", id_client]]];
__block id RESPONSE;
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
RESPONSE = JSON;
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"ERROR: %#", error);
}];
[operation start];
return RESPONSE;
}
I think you are confused about how blocks work.
That's an asynchronous request, therefore you cannot return any value computed inside the completion block, since your method already returned when it's executed.
You have to change your design an either perform a callback from inside the success block, or pass your own block and get it called.
As an example
- (void)descargarEncuestasParaCliente:(NSString *)id_client success:(void (^)(id JSON))success failure:(void (^)(NSError *error))failure {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://whatever.com/api/&id_cliente=%#", id_client]]];
__block id RESPONSE;
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
if (success) {
success(JSON);
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"ERROR: %#", error);
if (failure) {
failure(error);
}
}];
[operation start];
}
You will then call this method like follows
[self descargarEncuestasParaCliente:clientId success:^(id JSON) {
// Use JSON
} failure:^(NSError *error) {
// Handle error
}];

AFJSONRequestOperation array populates but cannot NSLog contents outside of success block

The following code is taken from this tutorial
I've used this snippet before but I never noticed this issue before. NSLog of the array contents prints in a delegate method but not in the viewDidLoad outside of the success block. I require a way to save the JSON data into an array for use elsewhere in the code. I should also add that I'm not using UITableView to display my data. What am I missing or how can I accomplish this?
This does not print the JSON content thought it does populate the array:
#import "AFNetworking.h"
...
- (void)viewDidLoad {
...
self.movies = [[NSArray alloc] init];
NSURL *url = [[NSURL alloc] initWithString:#"http://itunes.apple.com/search?term=harry&country=us&entity=movie"];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
self.movies = [JSON objectForKey:#"results"];
[self.activityIndicatorView stopAnimating];
[self.tableView setHidden:NO];
[self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
NSLog(#"self.movies %#",self.movies); // does not print
...
}
This does print the JSON content: I've only used numberOfRowsInSection as a separate location for the NSLog statement.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (self.movies && self.movies.count) {
NSLog(#"self.movies %#",self.movies); // prints
...
}
You are kicking off an asynch operation and then immediately trying to print out the contents. Move your first NSLog statement into the success block.
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
//the following lines of code execute after the response arrives
self.movies = [JSON objectForKey:#"results"];
NSLog(#"self.movies %#",self.movies);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
//this line of code executes directly after the request is made,
//and the response hasn't arrived yet
NSLog(#"I probably don't have the response yet");

AFNetworking setImageWithURLRequest download progress

I am using this code to set image to UIImageView.
NSURLRequest *URLRequest = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:imageToLoad]];
[imageView setImageWithURLRequest:URLRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[cell.image setImage:image];
[cell.activityIndicator stopAnimating];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
// Nothing
}];
But I want to track download progress with that method, is it possbile to do it in setImageWithURLRequest method?
Normally I do this to show loading progress percentage:
[SVProgressHUD showWithStatus:#"Start download..."];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:link]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// success
[SVProgressHUD showSuccessWithStatus:#"Done."];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// failed
[SVProgressHUD showErrorWithStatus:#"Failed."];
}];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
[SVProgressHUD showWithStatus:[NSString stringWithFormat:#"Downloading... %0.0f%%", totalBytesRead*100*1.0/(totalBytesRead+totalBytesExpectedToRead)]];
}];
Out of the box, no UIImageView+AFNetworking category doesn't have this functionality. However, it can easily be added to by adding this method to the category:
-(void)setDownloadProgressBlock:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block{
[self.af_imageRequestOperation setDownloadProgressBlock:block];
}
Take a look at this cocoa pod: https://github.com/xmartlabs/XLRemoteImageView . It uses objective-c internals to achieve what you want. I hope it helps you.

AFNetworking not returning data synchronously inside a block

Can't receive JSON synchronously inside a block using AFNetworking. I checked this solution. It
always nil at the end of method.
Here is my method:
- (BOOL)whois:(NSString *)domain withZone: (NSString*) zone
{
__block NSString *resultCode;
NSURL *url = [[NSURL alloc] initWithString:#"myurl"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
resultCode = [JSON valueForKeyPath:[NSString stringWithFormat:#"%#.%#", domain,zone]]; //checked with NSLog, works well
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation: operation];
[operation waitUntilFinished];
if(resultCode == #"available") //nil here
{
return YES;
}
return NO;
}
Instead of creating aNSOperationQueue, start your AFJSONRequestOperation with [operation start] and then call [operation waitUntilFinished] and it will block the main thread until it's finished. Then your resultCode should not be nil.
As #mattt said in the post you linked, it is strongly discouraged to freeze the thread like this. Consider figuring out another way to do this, such as calling a new method you hope to continue from your success block, and a different failure method from your failure block.
Your method can't function with its current design.
- (BOOL)whois:(NSString *)domain withZone: (NSString*) zone
{
__block NSString *resultCode;
NSURL *url = [[NSURL alloc] initWithString:#"myurl"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
// *** Runs 1st
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// *** runs 3rd
resultCode = [JSON valueForKeyPath:[NSString stringWithFormat:#"%#.%#", domain,zone]]; //checked with NSLog, works well
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
// *** Runs 2nd
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation: operation];
[operation waitUntilFinished];
if(resultCode == #"available") //nil here
{
return YES;
}
return NO;
}
Because the material in the block runs third, and asynchronously, you won't be able to return that value to the greater method in the manner it is currently designed. Perhaps use something like this:
- (void)whois:(NSString *)domain withZone: (NSString*) zone
{
NSURL *url = [[NSURL alloc] initWithString:#"myurl"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
__weak id weakSelf = self;
// Runs 1st
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSString *resultCode = [JSON valueForKeyPath:[NSString stringWithFormat:#"%#.%#", domain,zone]]; //checked with NSLog, works well
[weakSelf receivedResultCode:resultCode];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
}
- (void) receivedResultCode:(NSString *)resultCode {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation: operation];
[operation waitUntilFinished];
if(resultCode == #"available") //nil here
{
// do #YES stuff
}
else {
// do #NO stuff
}
}
Obviously you'll have to change the design of whoever's calling it because it won't return a value in the way you specified. Perhaps there is a better solution, but I think this is the type of design required for it to work.