How do I use a variable outside of a GET request? - ios

I would like to:
Initialise a variable prior to an GET request (AFNetworking)
Assign to the variable inside the request success
Use the variable once request is done
The error I get:
When trying to do something the variable after the GET request, it breaks, claiming there is no value to the variable yet
The following is my code, how do I solve this problem?
- (IBAction)synchronisePressed {
//Would like to initialise a variable here
__block NSArray *received;
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://foo.com/foo/"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//Assign responseObject to received object
received = responseObject;
NSLog(#"Woo: %#", #"got here");
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
//Now outside of request, so use array in some way
[foo received];
}

The problem is not in the code or in the request. The problem is the GET method of AFNetworking is asynchronous and you are trying to use it synchronously.
This means that the GET method will start working and sit in the background working away and the rest of your code will continue as normal. Only when the GET request has finished will the code inside the blocks get run.
This code be 1 second or 30 seconds or any amount of time after you start the request.
Put in an NSLog before your line [foo received];.
The order of logs will be like so...
1. Created NSArray called received.
2. Pass NSArray called received to method foo.
// some time later
3. AFNetworking GET request finished.
4. Assign value from the GET request to the NSArray called received.
What you need to do in this case is put your call to [foo received]; INSIDE the completion block.
Something like this...
- (IBAction)synchronisePressed
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://foo.com/foo/"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//Assign responseObject to received object
NSArray *received = responseObject;
NSLog(#"Woo: %#", #"got here");
[foo received];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
Now you will get...
1. Created NSArray called received.
// some time later
2. AFNetworking GET request finished.
3. Assign value from the GET request to the NSArray called received.
4. Pass NSArray called received to method foo.

Related

AFNetworking View JSON Content For Error Code 3840

I am working on an app where I have AFNetworking fetching JSON requests from the my server. Now JSON is dynamically created based upon the POST variables I send from my app.
I was wondering when I get the following error message
JSON text did not start with array or object and option to allow fragments not set.
Is there a way to adapt my code so that I can actually see in the logger what the returned JSON request looks like. I have a feeling that there is a warning popping up in my PHP that is throwing off the JSON but I am not sure what it is.
Here is my code, it would make it so much easier if I could lets say NSLog the body content.
[manager POST:#"__MY__URL__" parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
} progress:nil success:^(NSURLSessionDataTask *operation, id responseObject) {
NSLog(#"Success: %#", responseObject);
if([[responseObject objectForKey:#"state"] isEqualToString:#"success"]){
//[self performSegueWithIdentifier:#"client_choose_ad" sender:self];
}else {
[self alertError:#"Unable To Create Advert" alertMessage:[responseObject objectForKey:#"message"]];
}
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
Maybe printing the raw data of the request is what you are looking for.
How to print AFNetworking request as RAW data

Variable not changed by AFNetworking GET function

I am trying to use an AFNetworking class to retrieve data from a database. Long story short, the data received from the parameter responseObject is filled with items. Here is my problem however. I am trying to copy the results in responseObject into an NSDictionary called results. I used the following code to get there:
__block NSDictionary *results;
[manager GET:#"http://daneolog.altervista.org/app/getData.php"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) { results = responseObject;
NSLog(#"Inside: %#", results); }
failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(#"%#", error); }];
NSLog(#"Outside: %#", results);
I tried NSLoging the results dictionary INSIDE the success braces, and everything is a-okay there.
I tried NSLoging the results dictionary OUTSIDE the GET function, and it comes up as (null).
These are my results:
2015-11-12 14:34:34.875 TestApp[4864:258743] Outside: (null)
2015-11-12 14:34:35.242 TestApp[4864:258743] Inside: (
{
address = "Sample Address";
}
)
Now notice the peculiar thing: the outside NSLog is being executed first. I don't know why this is so. Can anyone help me on this? Thanks a bundle.
Your code outside the success block is executing before the success block completes that is why results come up as null. results is on the main thread, and you are not blocking (as is correct with an HTTP request).
You can pass a weak reference to an object, and have that updated. Or if you absolutely need to wait for the result (login for instance) then you should do it on the main thread.
Here is an example of a weak object:
//results is an object created earlier
__weak NSDictionary *weakResults = results;
[manager GET:#"http://daneolog.altervista.org/app/getData.php"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
weakResults = responseObject;
NSLog(#"Inside: %#", results);
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error);
}];
//when the success block finishes the results object will be populated
If you wanted to block i.e. do it on the main thread you could use:
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
However, if the response doesn't come back, your app hangs.

AFNetworking callbacks - delegate or notification?

I have a question on which is best way or the correct way to send AFNetworking results to controller. Is it via delegate or notification?
I created a class to handle make API calls that has the code below. So if imported this class to another controller and call this method to make API call. Should I do delegate or notification?
I have read www.raywenderlich.com/59255/afnetworking-2-0-tutorial and it is using delegates. I also been watched CodeSchool tutorial, which they used notification from Model to Controller.
I added the code below in a hope to better show my question.
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:baseURL];
// notification way inside the BLOCK
[ manager GET:path parameters:params
success:^(NSURLSessionDataTask *operation, id responseObject) {
[ [NSNotificationCenter defaultCenter] postNotificationName:notificationName
object:nil
userInfo:responseObject ];
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
[ [NSNotificationCenter defaultCenter] postNotificationName:notificationName
object:nil ];
}];
// delegate way inside the BLOCK
[ manager GET:path parameters:params
success:^(NSURLSessionDataTask *operation, id responseObject) {
if ([delegate respondsToSelector:#selector(getUserFeedsDidFinish:resultDict:)])
{
[delegate performSelector:#selector(getUserFeedsDidFinish:resultDict:) withObject:self withObject:resultDict];
}
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
if ([delegate respondsToSelector:#selector(getUserFeeds:didFailWithResultDict:)]) {
[delegate performSelector:#selector(getUserFeeds:didFailWithResultDict:)
withObject:self
withObject:[NSDictionary dictionaryWithObject:error.userInfo forKey:KEY_ERRORS]];
}
}];
I will recommend use blocks, how? I will write a service for you, this one is wrote in a class called Connection:
+(void)requestLocation:(NSString*)googleReference completionBlock:(void (^)(NSString * coordinates, NSError * error)) handler{
NSString * urlString = #"https://maps.googleapis.com/maps/";
NSMutableDictionary * parametersDictionary = [NSMutableDictionary dictionary];
[parametersDictionary setObject:googleReference forKey:#"reference"];
[parametersDictionary setObject:#"true" forKey:#"sensor"];
[parametersDictionary setObject:#"key(it is not)" forKey:#"key"];
AFHTTPClient *HTTPClient = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:urlString]];
NSURLRequest *URLRequest = [HTTPClient requestWithMethod:#"GET" path:#"api/place/details/json" parameters:parametersDictionary];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:URLRequest];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSError * error = nil;
NSDictionary * response = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:&error];
NSDictionary * dicGeo = [((NSDictionary*)[response objectForKey:#"result"]) objectForKey:#"geometry"];
NSDictionary * coords = [dicGeo objectForKey:#"location"];
NSNumber * lat = [coords objectForKey:#"lat"];
NSNumber * lng = [coords objectForKey:#"lng"];
NSString * coordinates = [NSString stringWithFormat:#"%#,%#", lat.description, lng.description];
handler(coordinates, error);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error);
}];
[requestOperation start];
}
Then to call this service:
[Connection requestLocation:#"google reference (it is not)" completionBlock:^(NSString *coordinates, NSError *error) {
//Your code with results.
}
I've only scratched the surface with AFNetworking. From what I've seen, most of it seems to use a third approach, blocks.
Blocks are somewhat new, and different than both delegates and notifications.
Blocks are an extension to C function pointers that let you pass code into a method when you call it.
A common design pattern using blocks is to create a method that takes a completion block. A completion block is a piece of code that gets invoked when an async request is completed.
Take the AFNewtworking method HTTPRequestOperationWithRequest as an example. That method takes a success block, that gets called if the request succeeds, and a failure block, that gets called if the request fails.
Block is the easiest way to use IMO. You don't need to implement extra delegate methods or you don't need any conformations.
Basically define your wrapper like this.
typedef void(^SampleRequestCompletion)(NSError *error, id data);
- (void)GET:(NSString *)URLString
parameters:(NSDictionary *)parameters
completion:(SampleRequestCompletion)completion
{
[self GET:URLString parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
// Do what you want
if (completion) {
completion(nil, data);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Failure case
if (completion) {
completion(error,nil);
}
}];
}
And call this method from any objects like this,
[self GET:path parameters:dictionary completion:^(NSError *error, id data) {
}];
So you can manage what to do whenever the call ends with success or failure.
As the tutorial recommended, we can extract the web service related code into a module which acts more like a model level thing. Considering the communication between the network module and views, view invoke/start the request on a singleton web service client, once response back the usual workflow would be send the result to view controller and show the data in the views. We don't need to return anything back to network module.
So this workflow is more like a notification than delegation. And set the V as the M's delegate, it's weird.
Notification : Hey, man, I have done my job, it's your turn.
Delegation: Hey, man, I have done lots, now I need you cover/back up/provide me some tasks, then I will continue/complete the work.
In some situations, it's difficult to choose which one better. For AFNetworking, I thought the Notification approach better.

Value of NSMutableDictionary is not changing inside the block

I am passing the URL in this method and getting the data as output. i want to assign a new value to nsmutabledictionary but it is not assigning the value.
-(NSDictionary*) getDatafromURL: (NSString*)url{
__block NSMutableDictionary *returnData=[[NSMutableDictionary alloc] init];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
returnData=(NSMutableDictionary*)responseObject;
NSLog(#"Data 1: %#",returnData);// it is printing the data
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
NSLog(#"Data 2: %#",returnData);// it is not printing any data
return returnData;
}
in this above example the Data 1 is showing value successfully
Data 2 gives me empty dictionary.why it is not assigning the new value?
That happens because you get to the line with "Data 2" first and the block is executed only afterwards, since it is an async request. I would suggest that you change your method to something like:
- (void)getDataFromURL:(NSString *)url completionHandler:(void (^)(NSMutableDictionary *returnData, NSError *error))handler {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
returnData=(NSMutableDictionary*)responseObject;
NSLog(#"Data 1: %#",returnData);// it is printing the data
handler(returnData, nil);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
handler(nil, error);
}];
}
There might be some compile errors in the code I provided.
The other solution would be to do a synchronous request, in which case the block would be executed before the code that is after the block.
EDIT:
If you are choosing the first solution, you have to continue using it asynchronously. So you would call it like:
[self getDataFromURL:#"abc.com" completionHandler:^ (NSMutableDictionary *returnData, NSError *error) {
// process your dictionary and the error object
}];
Please check whether your Data 2 is printing before data 1? If yes, its because, the response object gets downloaded only after a certain delay. Take away the return statements. Pass the data to the dictionary to which you return the method. For eg: like
instead of
self.myDictionary = [self getDatafromURL:someURl];
to
-(void) getDatafromURL: (NSString*)url{
__block NSMutableDictionary *returnData=[[NSMutableDictionary alloc] init];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
returnData=(NSMutableDictionary*)responseObject;
NSLog(#"Data 1: %#",returnData);// it is printing the data
self.myDictionary = returnData;
// Continue whatever you want to do
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
Or use the dispatch methods instead of the blocks.
like
Or use manager waitUntilFinish method below.

Passing JSON object received asynchronously in block to an instance variable - using AFNetworking on iOS6

This code has the JSON but no matter what I do I haven't been able to figure out how to pass this JSON to an instance variable. Each time I set it to a variable and call that variable outside of this method its nil. So what I can gather is that the variable is called before the following async call returns.
So the question is what can I do to the following code so that I can extract the JSON value. Somewhere on the internet I read that I would need to pass it a block which would server as a call back on completion but I cannot figure out how to do that for the following code
//Gets the JSON object that contains the entries from the server
-(void)getEntriesFromServer
{
NSLog(#"%s", __PRETTY_FUNCTION__);
[[appAPIClient sharedClient] getPath:#"/entries"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id JSON)
{
NSLog(#" JSON array = %#",[JSON valueForKeyPath:#"entries"]);
}failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#" Json not received");
}];
}
Thanks
try to add __block to definition of an array e.g __block NSArray* entriesArray;
or make a property like
#property (nonatomic, retain) NSArray* entriesArray;
and change your code like this
[[appAPIClient sharedClient] getPath:#"/entries"
parameters:nil
success:^(AFHTTPRequestOperation *operation, id JSON)
{
//NSLog(#" Json value received is : %# ",[JSON description]);
//NSLog(#" JSON array = %#",[JSON valueForKeyPath:#"entries"]);
self.entriesArray = [NSArray arrayWithArray:[JSON valueForKeyPath:#"entries"]];
NSLog(#" JSON array from inside block = %#", _entriesArray);
}failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#" Json not received");
}];
Hope it'll help

Resources