I am using AFNetworking in my project and I simply need to check if a URL is successful (status 2xx).
I tried
NSURLRequest *request = [NSURLRequest requestWithURL:url2check];
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
if (response.status == 200) {
NSLog(#"Ok %#", JSON);
}
}
failure:nil
];
[operation start];
But as I'm not awaiting JSON especially, I was expecting to use something like
AFHTTPRequestOperation *operation = [AFHTTPRequestOperation
HTTPRequestOperationWithRequest: success: failure:];
But this does not exist (I don't know why), so I used
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]
initWithRequest:request];
[operation setCompletionBlockWithSuccess:...]
Isn't there simpler ?
How would you do this simple URL check with AFNetworking ?
Here is what I ended up with
NSURLRequest *request = [NSURLRequest requestWithURL:url2check];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]
initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation
, id responseObject) {
// code
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// code
}
];
[operation start];
Feel free to add a simpler way.
You just could instantiate a AFHTTPClient object with the base URL and call getPath:parameters:success:failure: on it.
from the docs:
Creates an AFHTTPRequestOperation with a GET request, and enqueues it to the HTTP client’s operation queue.
Related
I'm trying to learn AFNetworking so have written a simple block. I'm trying to retrieve & log the json from the site url below.
NSString *string =
#"http://transportapi.com/v3/uk/bus/stop/490012745J/live.json?api_key=6ee115459cbeccdb902b14d39b61330d&app_id=9deefeb1&group=route";
NSURL *url = [NSURL URLWithString:string];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSDictionary *mydict = (NSDictionary *)responseObject;
NSString *key;
for(key in mydict){
NSLog(#" key %#", key);
}
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"ERROR");
}];
But neither the success or the failure block is being called. Can someone point out what I've done wrong?
You're not actually firing the operation you need to add
[[NSOperationQueue mainQueue] addOperation:operation];
I'm trying to do some unit-testing on my code.
I'm facing an issue, unit-testing fonctions aren't waiting for the asynchronous request to be finished. I'd like to wait for it only if i'm testing it, and not when i run it.
I figured out how to check for this :
if([[[[NSProcessInfo processInfo] environment] objectForKey:#"TARGET"] isEqualToString:#"TEST"])
With the respective environnement variables set in my project.
But i can't find a proper way to wait for it. I tryed with a semaphore, and that works, but i'm wandering if there's any other way which would be easier, and reader-friendly.
EDIT :
Finally, i did it like this : I guess i could have do something better, but that works and looks fair enough to me.
static int statusCode;
- (void)requestContent:(NSString*)urlString
{
statusCode = 0;
NSLog(#"Request Content");
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
[self ParseGoogleImageSearchResponse:JSON];
statusCode = 1;
NSLog(#"Done");
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON)
{
NSLog(#"%#", [error userInfo]);
statusCode = 2;
}];
[operation start];
if([[[[NSProcessInfo processInfo] environment] objectForKey:#"TARGET"] isEqualToString:#"TEST"])
{
while(statusCode == 0)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
NSLog(#"WAITING");
}
}
}
One way to accomplish async unit tests for AFNetworking calls is to use GHUnit.
GHUnit handles spinning the runloop and waiting for async operations to finish automatically, so your unit test code can be much more readable.
Here is a basic tutorial that covers using AFNetworking, GHUnit, and CocoaPods all together.
Here is a basic test written using AFNetworking and GHUnit.
- (void)test
{
[self prepare];
__block NSError *responseError = nil;
__block id resposeObject = nil;
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
resposeObject = JSON;
[self notify:kGHUnitWaitStatusSuccess forSelector:_cmd];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON)
{
responseError = error
[self notify:kGHUnitWaitStatusSuccess forSelector:_cmd];
}];
[operation start];
// Wait for the async activity to complete
[self waitForStatus:kGHUnitWaitStatusSuccess timeout:kNetworkTimeout];
// Check Error
GHAssertNil(responseError, #"");
GHAssertNotNil(resposeObservation, #"Response Observation is nil");
// Validate data...
}
Hope that helps!
I have this code that I want to eager load using AFJSONRequestOperation before continuing with the for loop but somehow it doesn't work. The code below is inside my for loop. How can I prevent my loop from proceeding even if the request is not yet complete? This will help me place all the values got from a success request inside an array in the order I wanted it.
This is the code:
NSURL *url = [NSURL URLWithString:[nowShowingTitleListArray objectAtIndex:i]];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:url];
[AFJSONRequestOperation addAcceptableContentTypes:[NSSet setWithObject:#"text/html"]];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:theRequest
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
// Code when success
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON)
{
NSLog(#"Request Failed with Error: %#", [error localizedDescription]);
}];
[operation start];
Currently, I'm doing this to ensure that they'll be fetched and added to my array in order:
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[nowShowingTitleListArray objectAtIndex:i]]];
NSError *error;
NSDictionary *JSONDict = [NSJSONSerialization JSONObjectWithData:data
options:kNilOptions
error:&error];
if (!JSONDict)
{
NSLog(#"Request Failed with Error: %#", [error localizedDescription]);
}
else
{
// Rest of code
}
This will also help me minimize some of the crash issues I'm having with the request when the network connection suddenly drops out.
Why you are you fetching your data from the same URL twice?
Once using AFJSONRequestOperation and then dataWithContentsOfURL:
Are you doing this in the same for loop?
You already have your JSON object in success block
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
// Code when success
// write success code here
}
you can call AFJSONRequestOperation in loop for multiple network requests.
Before someone spams the thread telling me its not best practice, there is valid reason for my not using an async method in this case.
I am trying to force my AFNetworking library to run in a synchronous mode for certain method calls (its been factoried).
Here is my (hacked) snippet:
NSURL *url = [NSURL URLWithString:_apiBaseUrl];
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:url];
NSMutableURLRequest *request = [client requestWithMethod:#"POST" path:path parameters:nil];
NSDictionary *requestDict = [NSDictionary dictionaryWithObjects:#[#"2.0", path, params, [NSNumber numberWithInt:(int)[[NSDate date] timeIntervalSince1970]]] forKeys:#[#"jsonrpc", #"method", #"params", #"id"]];
NSString *requestBody = [requestDict JSONString];
NSLog(#"requestBody: %#", requestBody);
// Set the request body
[request setHTTPBody:[requestBody dataUsingEncoding:NSASCIIStringEncoding]];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// Do something with the success (never reached)
successBlock(request, response, JSON);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
failureBlock(request, response, error, JSON);
}];
if (queue == 1) {
[self.operationArray addObject:operation];
}
else {
[operation waitUntilFinished];
successBlock(nil, nil, nil);
//[operation start];
}
When the method is ran and the queue param forces the method to waitUntilFinished the success block is never reached nor is the next line. If I use the start method it works fine.
I am making a JSON request with AFNetworking and then call [operation waitUntilFinished] to wait on the operation and the success or failure blocks. But, it seems to fall right though - in terms of the log messages, I get "0", "3", "1" instead of "0", "1", "3"
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"http://google.com"]];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
httpClient.parameterEncoding = AFFormURLParameterEncoding;
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:#"query", #"q", nil];
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:[url path] parameters:params];
NSLog(#"0");
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *innerRequest, NSHTTPURLResponse *response, id JSON) {
NSLog(#"1");
gotResponse = YES;
} failure:^(NSURLRequest *innerRequest, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"2");
gotResponse = YES;
}];
NSLog(#"Starting request");
[operation start];
[operation waitUntilFinished];
NSLog(#"3");
This works by using AFNetworking to set up the requests, but making a synchronous call then handling the completion blocks manually. Very simple. AFNetworking doesn't seem to support this https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-FAQ, though the work around is simple enough.
#import "SimpleClient.h"
#import "AFHTTPClient.h"
#import "AFJSONRequestOperation.h"
#import "AFJSONUtilities.h"
#implementation SimpleClient
+ (void) makeRequestTo:(NSString *) urlStr
parameters:(NSDictionary *) params
successCallback:(void (^)(id jsonResponse)) successCallback
errorCallback:(void (^)(NSError * error, NSString *errorMsg)) errorCallback {
NSURLResponse *response = nil;
NSError *error = nil;
NSURL *url = [NSURL URLWithString:urlStr];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
httpClient.parameterEncoding = AFFormURLParameterEncoding;
NSMutableURLRequest *request = [httpClient requestWithMethod:#"POST" path:[url path] parameters:params];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if(error) {
errorCallback(error, nil);
} else {
id JSON = AFJSONDecode(data, &error);
successCallback(JSON);
}
}
#end
That should (almost) work. Your call to
NSMutableURLRequest *request = [httpClient requestWithMethod:#"GET" path:[url path] parameters:params];
should probably not pass [url path] to the path: parameter. In AFNetworking land, that path is everything after the base url (for example the base url could be "http://google.com" and the path "/gmail" or whatever).
That being said, it's probably not a good idea to make the asynchronous operation into a thread-blocking synchronous operation with waitUntilFinished, but I'm sure you have your reasons... ;)
I just had the same problem and found a different solution. I had two operations that depend on each other, but can load in parallel. However, the completion block of the second operation can not be executed before the completion block of the first one has finished.
As Colin pointed out, it might be a bad choice to make a web request block. This was essential to me, so I did it asynchronously.
This is my solution:
// This is our lock
#interface SomeController () {
NSLock *_dataLock;
}
#end
#implementation
// This is just an example, you might as well trigger both operations in separate
// places if you get the locking right
// This might be called e.g. in awakeFromNib
- (void)someStartpoint {
AFJSONRequestOperation *operation1 = [AFJSONRequestOperation JSONRequestOperationWithRequest:[NSURLRequest requestWithURL:url1]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id data) {
// We're done, we unlock so the next operation can continue its
// completion block
[_dataLock unlock];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id data) {
// The request has failed, so we need to unlock for the next try
[_dataLock unlock];
}];
AFJSONRequestOperation *operation2 = [AFJSONRequestOperation JSONRequestOperationWithRequest:[NSURLRequest requestWithURL:url2]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id data) {
// The completion block (or at least the blocking part must be run in a
// separate thread
[NSThread detachNewThreadSelector:#selector(completionBlockOfOperation2:) toTarget:self withObject:data];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id data) {
// This second operation may fail without affecting the lock
}];
// We need to lock before both operations are started
[_dataLock lock];
// Order does not really matter here
[operation2 start];
[operation1 start];
}
- (void)completionBlockOfOperation2:(id)data {
// We wait for the first operation to finish its completion block
[_dataLock lock];
// It's done, so we can continue
// We need to unlock afterwards, so a next call to one of the operations
// wouldn't deadlock
[_dataLock unlock];
}
#end
Use Delegate method call
Put the method inside block which will call itself when downloading/uploading completes.