AFNetworking not returning data synchronously inside a block - iphone

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.

Related

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");

iOS - Performing multiple AFJSONRequestOperations that write to Core Data in background

I am having trouble determining the best way to manage updates to my apps SQLite database (using Core Data)
When my app launches, it hits a server to determine which tables need updating. I then do a service call for each of those tables. Once I get the JSON back for each, it creates/updates the corresponding objects in my SQLite DB.
What I am doing works, as it performs each request and updates each table that needs to be-- but I don't think I am doing this correctly.
Doing this still locks my UI Thread and I need to be able to run this code asynchronously in the background every 10 minutes or so.
AFJSONRequestOperation* operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSMutableArray *operations = [NSMutableArray new];
//for each of the tables that need updating, create a AFJSONRequestOperation
for(NSString *str in [JSON valueForKey:#"Views"])
{
NSString* path = [NSString stringWithFormat:#"cache/%#/?deviceUID=%#&token=%#", str, #"00000-00000-0000-00001", [_globals getToken]];
NSURLRequest* request = [client requestWithMethod:#"GET" path:path parameters:nil];
AFJSONRequestOperation* operation2 = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
[self updateTable:str withJSON:JSON];
}
failure:nil];
[operations addObject:operation2];
}
//AFHTTPClient
[client enqueueBatchOfHTTPRequestOperations:operations progressBlock:nil completionBlock:^(NSArray *operations) {
//this gets called way before the objects are done updating to the DB
NSLog(#"DONE ALL REQUESTS");
[_HUD hide:YES]; // getting called after getting all of the JSON not after all tables are updated
}];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
[_HUD hide:YES];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Failed" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
}];
[operation start];
Heres my updateTable function with only 1 condition
- (void)updateTable:(NSString *)tblName withJSON:(id)JSON
{
for(NSDictionary *record in [JSON valueForKey:#"Records"])
{
NSString *viewName = [[record valueForKey:#"ViewName"] lowercaseString];
//Worker
if([viewName isEqualToString:[[NSString stringWithFormat:#"Worker_vw_iSales"] lowercaseString]])
{
if([Worker doesWorkerExist:[record valueForKey:#"JSONData"]])
{
NSLog(#"deleting old worker");
[ad.managedObjectContext deleteObject:[Worker doesWorkerExist:[record valueForKey:#"JSONData"]]];
}
NSEntityDescription *desc = [NSEntityDescription entityForName:NSStringFromClass([Worker class]) inManagedObjectContext:ad.managedObjectContext];
Worker *worker = [[Worker alloc] initWithEntity:desc insertIntoManagedObjectContext:ad.managedObjectContext];
[worker initWithJSONSting:[record valueForKey:#"JSONData"]];
NSLog(#"Creating Worker: %#", worker.firstName);
}
}
}
I hope this is not all too confusing-- if so I can try to explain more.
I may be doing this completely wrong, and if I am just let me know. I have tried a few other things, including using an NSOperationQueue instead of AFHHTTPs enqueueBatchOfHTTPRequestOperations:requests but I cannot get the behavior I am looking for.
Thanks!
What you are looking for now is setSuccessCallbackQueue: on AFJSONRequestOperation. AFNetworking set all their success blocks to run on the main queue unless otherwise specified.
What I've done is
#implementation myClass {
dispatch_queue_t backgroundQueue;
}
- (id)init
{
if (self = [super init]){
backgroundQueue = dispatch_queue_create("com.proj.myClass", 0);
}
return self;
}
- (void)doSomeStuff
{
NSMutableURLRequest *request = [[myAFAPIClient sharedClient] requestWithMethod:#"GET" path:path parameters:params];
AFJSONRequestOperation *myOperation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
//Success Block
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
}];
[myOperation setSuccessCallbackQueue:backgroundQueue];
[[myAFAPIClient sharedClient].operationQueue addOperation:myOperation];
}
So the different would be you are enqueue'ing operations, where I'm just adding them straight to the client.
Also, where I have //Success Block I do all sorts of things such as dispatching other methods onto backgroundQueue, which those methods make more JSON requests and I don't have any issues.

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 - How to make POST request

EDIT 07/14
As Bill Burgess mentionned in a comment of his answer, this question is related to the version 1.3 of AFNetworking. It may be outdated for the newcomers here.
I'm quite new to iPhone development, and I'm using AFNetworking as my services library.
The API i'm querying is a RESTful one, and I need to make POST requests. To do this, I tried with the following code :
NSDictionary *parameters = [NSDictionary dictionaryWithObjectsAndKeys:#"my_username", #"username", #"my_password", #"password", nil];
NSURL *url = [NSURL URLWithString:#"http://localhost:8080/login"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"Pass Response = %#", JSON);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Failed Response : %#", JSON);
}];
[operation start];
There are two main issues with this code :
AFJSONRequestOperation seems to make a GET request, not a POST one.
I can't put parameters to this method.
I also tried with this code :
NSDictionary *parameters = [NSDictionary dictionaryWithObjectsAndKeys:#"my_username", #"username", #"my_password", #"password", nil];
NSURL *url = [NSURL URLWithString:#"http://localhost:8080"];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
[httpClient postPath:#"/login" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Succes : %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure : %#", error);
}];
Is there a better way to make what I want here to get it done ?
Thanks for the help !
You can override the default behavior of your request being used with AFNetworking to process as a POST.
NSURLRequest *request = [client requestWithMethod:#"POST" path:path parameters:nil];
This assumes you have overridden the default AFNetworking setup to use a custom client. If you aren't, I would suggest doing it. Just create a custom class to handle your network client for you.
MyAPIClient.h
#import <Foundation/Foundation.h>
#import "AFHTTPClient.h"
#interface MyAPIClient : AFHTTPClient
+(MyAPIClient *)sharedClient;
#end
MyAPIClient.m
#implementation MyAPIClient
+(MyAPIClient *)sharedClient {
static MyAPIClient *_sharedClient = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedClient = [[self alloc] initWithBaseURL:[NSURL URLWithString:webAddress]];
});
return _sharedClient;
}
-(id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if (!self) {
return nil;
}
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setDefaultHeader:#"Accept" value:#"application/json"];
self.parameterEncoding = AFJSONParameterEncoding;
return self;
}
Then you should be able to fire off your network calls on the operation queue with no problem.
MyAPIClient *client = [MyAPIClient sharedClient];
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
[[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount];
NSString *path = [NSString stringWithFormat:#"myapipath/?value=%#", value];
NSURLRequest *request = [client requestWithMethod:#"POST" path:path parameters:nil];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// code for successful return goes here
[[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
// do something with return data
}failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
// code for failed request goes here
[[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
// do something on failure
}];
[operation start];

No Response from AFJSONRequestOperation

I'm trying to get the AFJSONRequestOperation to work with http://www.crowdkind.org/mobile/offers.
I get through the synchronous part and it returns data and puts it in the table just fine. When I go to convert it to AFJSONRequestOperation - I don't get a response. The operation is sent as the NSLog tells me that. But I never get "Operation succeeded or failed". Any idea what might be happening? Here's my searchBarButtonClicked method...
URLtoSearch is set to... http://www.crowdkind.org/mobile/offers. (feel free to click to as it's valid JSON).
- (void)searchBarSearchButtonClicked
{
searchResults = [NSMutableArray arrayWithCapacity:10];
[queue cancelAllOperations];
isLoading = YES;
[self.tableView reloadData];
NSURL *url = [self urlWithSearchText:URLtoSearch];
NSLog(#"URL: %#", URLtoSearch);
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
NSLog(#"Operation succeeded");
[self parseDictionary:JSON];
[searchResults sortUsingSelector:#selector(compareName:)];
isLoading = NO;
[self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Operation failed");
[self showNetworkError];
isLoading = NO;
[self.tableView reloadData];
}];
operation.acceptableContentTypes = [NSSet setWithObjects:#"application/json", #"text/json", #"text/javascript", nil];
[queue addOperation:operation];
NSLog(#"Operation added");
}
I get no response. Check the link, it's valid json. Any thoughts greatly appreciated!