I am using the AFXMLRequestOperation method of the wonderful AFNetworking. What I would like to use is use the following, but wrap my own method around it, with my own completion callback.
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://api.flickr.com/services/rest/?method=flickr.groups.browse&api_key=b6300e17ad3c506e706cb0072175d047&cat_id=34427469792%40N01&format=rest"]];
AFXMLRequestOperation *operation = [AFXMLRequestOperation XMLParserRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser) {
XMLParser.delegate = self;
[XMLParser parse];
} failure:nil];
[operation start];
Is it possible to do something like the following?
+ (void)makeRequestWithURL:(NSURL *)url completion:(void (^)(BOOL finished))completion {
NSURLRequest *request = [NSURLRequest requestWithURL:url]];
AFXMLRequestOperation *operation = [AFXMLRequestOperation XMLParserRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser) {
XMLParser.delegate = self;
[XMLParser parse];
} failure:nil];
[operation start];
if (completion) {
// How can I call my cometion block when AFXMLRequestOpersation is finished?
}
}
Then call it using:
[MyClass makeRequestWithURL:url completion^(BOOL finished){
if (finished) {
NSLog(#"AFNetworking Finished");
}
}];
Can can I know when AFNetworking has finished in MY completion block?
Just call your completion block in the success block:
+ (void)makeRequestWithURL:(NSURL *)url completion:(void (^)(BOOL finished))completion {
NSURLRequest *request = [NSURLRequest requestWithURL:url]];
AFXMLRequestOperation *operation = [AFXMLRequestOperation XMLParserRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser) {
XMLParser.delegate = self;
[XMLParser parse];
// call completion block here
if (completion) {
completion(YES);
}
} failure:nil];
[operation start];
}
You should also implement the failure block of AFXMLRequestOperation.
Assume the completion block will be run regardless of the result with the BOOL showing success/failure it would need to look like this:
NSURLRequest *request = [NSURLRequest requestWithURL:url]];
AFXMLRequestOperation *operation = [AFXMLRequestOperation XMLParserRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser) {
XMLParser.delegate = self;
[XMLParser parse];
if (completion) {
completion(YES);
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLParser *XMLParse) {
if (completion) {
completion(NO);
}
}];
[operation start];
Related
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 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.
I using AFNetworking to populate a UITableView with youtube videos. YouTube API only allows maximum 50 result for each request. So I have to use multiple URLs to get more than 50 results.
I created a Method which will do AFNetworkings AFJSONRequestOperation on the given URLs.
I think the UITable is getting created before i receive the JSON data. Everything was working perfectly before i created the Method.
This the first time i have created a Method. I have been trying to load more than 50 youtube videos on a UITable for the last few days.Please have a look at my code.
Here is my code, you can also download the entire project from
QQViewController.m
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
//[super viewDidLoad];
NSString *urlAsString = #"http://gdata.youtube.com/feeds/api/playlists/PL7CF5B0AC3B1EB1D5?v=2&alt=jsonc&max-results=50";
// I am not sure how i am supposed to populate the uitableview with the second link :(
NSString *urlAsString2 = #"http://gdata.youtube.com/feeds/api/playlists/PL7CF5B0AC3B1EB1D5?v=2&alt=jsonc&max-results=50&start-index=51";
self.myurl = [NSURL URLWithString:urlAsString];
[self getJSONfromURL:self.myurl];
[self.tableView reloadData];
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.videoMetaData = [self.myJSON valueForKeyPath:#"items.video"];
NSLog(#" QQVC video Meta Data %#", self.videoMetaData);
self.allThumbnails = [self.myJSON valueForKeyPath:#"data.items.video.thumbnail"];
// The table need to be reloaded or else we will get an empty table.
[self.tableView reloadData]; // Must Reload
// NSLog(#" video Meta Data %#", self.videoMetaData);
}
// Here is the Method
-(void)getJSONfromURL:(NSURL *)url {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// setup AFNetworking stuff
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// call delegate or processing method on success
// [self.myJSON = (NSArray *)JSON];
self.myJSON = [JSON valueForKey:#"data"];
[self.tableView reloadData];
//NSLog(#" in get JSon method %#", self.myJSON);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
There were several problems in the getJSONfromURL: method. The main problem was that you were defining self.myJSON as [JSON valueForKey:#"data"], but then defining self.allThumbnails as [self.myJSON valueForKeyPath:#"data.items.video.thumbnail"] -- you used "data" again, so self.thumbnails was null. This seems to work ok:
-(void)getJSONfromURL:(NSURL *)url {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// setup AFNetworking stuff
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
self.myJSON = [JSON valueForKey:#"data"];
self.allThumbnails = [self.myJSON valueForKeyPath:#"items.video.thumbnail"];
self.videoMetaData = [self.myJSON valueForKeyPath:#"items.video"];
[self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
}
If you want to load date from multiple URLs, you can do it like this. I added a counter to keep track of the progress through the array of URL strings. The table is reloaded after each download, so it doesn't have to wait for the whole thing to complete before some data shows up.
#interface QWViewController ()
#property (strong,nonatomic) NSArray *urlStrings;
#end
#implementation QWViewController {
int counter;
}
-(void)viewDidLoad {
[super viewDidLoad];
counter = 0;
NSString *urlAsString = #"http://gdata.youtube.com/feeds/api/playlists/PL7CF5B0AC3B1EB1D5?v=2&alt=jsonc&max-results=50";
NSString *urlAsString2 = #"http://gdata.youtube.com/feeds/api/playlists/PL7CF5B0AC3B1EB1D5?v=2&alt=jsonc&max-results=50&start-index=51";
self.urlStrings = #[urlAsString,urlAsString2];
self.allThumbnails = [NSMutableArray array];
self.videoMetaData = [NSMutableArray array];
[self getJSONFromURL:self.urlStrings[0]];
}
-(void)getJSONFromURL:(NSString *) urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
self.myJSON = [JSON valueForKey:#"data"];
[self.allThumbnails addObjectsFromArray:[self.myJSON valueForKeyPath:#"items.video.thumbnail"]];
[self.videoMetaData addObjectsFromArray:[self.myJSON valueForKeyPath:#"items.video"]];
[self.tableView reloadData];
counter += 1;
if (counter < self.urlStrings.count) [self getJSONFromURL:self.urlStrings[counter]];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
}
Move your reload tableview call to here.
-(void)getJSONfromURL:(NSURL *)url {
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// setup AFNetworking stuff
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// call delegate or processing method on success
// [self.myJSON = (NSArray *)JSON];
self.myJSON = [JSON valueForKey:#"data"];
[self.tableView reloadData];
//NSLog(#" in get JSon method %#", self.myJSON);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request Failed with Error: %#, %#", error, error.userInfo);
}];
[operation start];
}
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.