The following code using AFNetworking 2.0 is valid to fetch data through internet:
NSString *URLPath = #"http://www.raywenderlich.com/downloads/weather_sample/weather.php?format=json";
NSDictionary *parameters = nil;
[[AFHTTPRequestOperationManager manager] GET:URLPath parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"success: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"failure: %#", error);
}];
But I want to test those requests synchronously in the unit test. But it would be blocked when using GCD semaphore like this:
// This code would be blocked.
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSString *URLPath = #"http://www.raywenderlich.com/downloads/weather_sample/weather.php?format=json";
NSDictionary *parameters = nil;
[[AFHTTPRequestOperationManager manager] GET:URLPath parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"success: %#", responseObject);
dispatch_semaphore_signal(sema);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"failure: %#", error);
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release_ARC_compatible(sema);
How can I fetch data using AFNetworking 2.0 library synchronously (test those code in Kiwi)?
Your semaphore would be blocked because by default AFNetworking runs on the main loop. So if you're waiting on the main loop for the semaphore, AFNetworking's code never gets to run.
In order to fix this, you simply have to tell AFNetworking to use a different dispatch queue. You do that by setting the operationQueue property on AFHTTPRequestOperationManager
You can create your own dispatch queue, or use one of the predefined ones, like so:
// Make sure that the callbacks are not called from the main queue, otherwise we would deadlock
manager.operationQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
Related
My scene is like this, first I have a server json api which return some data for specify page, the api is like /data/page/1. For this case, suppose the response data is :
page 1 => ['a','b']
page 2 => ['c','d']
page 3 => ['e','f']
I use AFNetworking 2 to fetch data from api, for single page data request it works well.
The problem is now I want to implement parallel request for more than one page. I need one api for view controller which accept one pages number, and callback with all data for these pages collected. The api I need is:
typedef void (^DataBlock)(id data);
- (void) dataForPages:(NSInteger)pages withSuccessBlock:(DataBlock)block;
If view controller pass 3 for pages parameter, I want AFNetworking can request data parallel and then collected the 3 result then use in callback block.
I tried to use NSOperationQueue to process multi AFHTTPRequestOperation but failed, the code demo is like this:
- (void) dataForPages:(NSInteger)pages withSuccessBlock:(DataBlock)block
{
//want to use for each, here suppose pages is 3
NSMutableArray *result = [[NSMutableArray alloc] init];
AFHTTPRequestOperation *op1 = [[AFHTTPRequestOperation alloc] initWithRequest:#"/data/page/1"];
AFHTTPRequestOperation *op2 = [[AFHTTPRequestOperation alloc] initWithRequest:#"/data/page/2"];
AFHTTPRequestOperation *op3 = [[AFHTTPRequestOperation alloc] initWithRequest:#"/data/page/3"];
[op1 setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[result addObjectsFromArray: responseObject]; //responseObject is ['a', 'b']
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
[op2 setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[result addObjectsFromArray: responseObject]; //responseObject is ['c', 'd']
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
[op3 setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[result addObjectsFromArray: responseObject]; //responseObject is ['e', 'f']
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
NSOperationQueue *q = [[NSOperationQueue alloc] init];
[q addOperation:op1];
[q addOperation:op2];
[q addOperation:op3];
[q waitUntilAllOperationsAreFinished];
block(result);
}
In my test the result always empty, I'm not quite understand waitUntilAllOperationsAreFinished.
Anyone knows how to deal this problem with NSOperation or GCD?
After some code research, I found it's difficult to get what I want with NSOperation and NSOperationQueue, because AFNetworking has it's own completion block handler.
The final solution is use dispatch_group, all code is like this:
dispatch_group_t group = dispatch_group_create();
NSURLRequest *req1 = ...;
NSURLRequest *req2 = ...;
AFHTTPRequestOperation *op1 = [[AFHTTPRequestOperation alloc] initWithRequest:req1];
AFHTTPRequestOperation *op2 = [[AFHTTPRequestOperation alloc] initWithRequest:req2];
NSMutableArray *result = [[NSMutableArray alloc] init];
dispatch_group_enter(group); //enter group
[op1 setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[result addObjectsFromArray: responseObject];
dispatch_group_leave(group); //leave group in completion handler
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[op2 setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[result addObjectsFromArray: responseObject];
dispatch_group_leave(group);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
dispatch_group_leave(group);
}];
[op1 start];
[op2 start];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
block(result);
});
I have this bool method that returns a yes or no for an inputted string.
I'm successfully able to return a YES or a NO, but I cannot seem to able to make a network connection and return a YES or a NO depending on the server's response.
I tried using __block and I don't feel like that will wait for the web request to finish, is there a way to return YES or NO in the success block without it giving me the error:
Incompatible block pointer types sending 'BOOL(^)(NSURLSessionTask*__strong, NSError __strong' to parameter of the type 'void(^)(NSURLSessionTask...)
-(BOOL)customResponseForString:(NSString *)text {
__block BOOL response_available;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager.responseSerializer setAcceptableContentTypes:[NSSet setWithObject:#"text/plain"]];
[manager GET:[NSString stringWithFormat:#"http://example.com/response.php?input=%#", text] parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseObject options:NSUTF8StringEncoding error:nil];
response_available = (BOOL)response[#"response_available"];
if (response_available) {
[session sendTextSnippet:response[#"response"] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
} else {
response_available = NO;
}
[session sendTextSnippet:[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
[session sendRequestCompleted];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
//return NO;
}];
});
return response_available;
}
Your block definition syntax is probably erroneous, because you can definitely return a BOOL along other parameters in a block.
- (void)fetchCurrentUserWithCompletion:(void (^)(BOOL success, User *user))completion;
This method would be called like this:
[self.userProfileController fetchCurrentUserWithCompletion:^(BOOL success, User *user) {
if (success) {
NSLog(#"Current User Name: %#", user.fullName);
}
}];
If you use AFNetworking, check the AFHTTPRequestOperation object that handle completionBlocks:
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
User *user = [self userFromResponseObject:responseObject];
if (completion) completion(YES, user);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (completion) completion(NO, user);
}];
Because you are implicitly initializing response_available to NO and then using an async GCD call, your method as written will always immediately return NO without waiting for the request to finish. Note: switching to dispatch_sync won't help either because AFNetworking will queue the GET request asynchronously either way.
Best Approach
Add a completion block argument to customResponseForString:. Then simply execute your completion block in the success or failure blocks of the AFHTTPRequestOperation.
Workable Approach (use caution!)
It is possible to make customResponseForString: wait for a response to the network request, but you will have significant issues if it is ever called from the main thread.
First you create a dispatch group and tell it you are starting some long-running work:
dispatch_group_t networkGroup = dispatch_group_create();
dispatch_group_enter(networkGroup);
Then you need to make your network request and when it completes tell the group that the work is finished with dispatch_group_leave():
[manager GET:[NSString stringWithFormat:#"http://example.com/response.php?input=%#", text] parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:responseObject options:NSUTF8StringEncoding error:nil];
response_available = (BOOL)response[#"response_available"];
if (response_available) {
[session sendTextSnippet:response[#"response"] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
} else {
response_available = NO;
}
[session sendTextSnippet:[[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding] temporary:NO scrollToTop:NO dialogPhase:#"Summary"];
[session sendRequestCompleted];
dispatch_group_leave(networkGroup);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
response_available = NO;
dispatch_group_leave(networkGroup);
}];
Before your original method returns, tell it to wait for the entire group to finish processing:
dispatch_group_wait(networkGroup, DISPATCH_TIME_FOREVER);
return response_available;
You could adjust this time interval as needed or leave it at DISPATCH_TIME_FOREVER to let the network request time out on its own.
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.
I want to stop/cancel the operation in case of running the request again. Method cancelAllHTTPOperationsWithMethod is working ok, but i have a problem when AFNetworking has already fetched the results and my successBlock is being fired - I want to stop it in the nearest future. But the problem is that operation.isCancelled is not cancelled.
The question is do i have to perform my 'very long successBlock' in NSOperation and cancel them too or is there any easier and faster method?
Code:
[[AFHTTPClient sharedInstance] cancelAllHTTPOperationsWithMethod:#"GET" path:#"path"];
[[AFHTTPClient sharedInstance] getPath:#"path" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
for (longLoop) {
// do something long
if (self.isCancelled) return; // this won't fire no matter how often i run it
}
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// do something to fail
}];
I've ended with doing NSOperation inside. Something like:
[[AFHTTPClient sharedInstance] cancelAllHTTPOperationsWithMethod:#"GET" path:#"path"];
[operationQueue cancelAllOperations];
[[AFHTTPClient sharedInstance] getPath:#"path" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
// add new operation
MyOperation *successOperation = [[MyOperation alloc] init];
[successOperation setResponseObject:responseObject];
[operationQueue addOperation:successOperation];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// call delegate
[self didFailFetchDataWithError:error];
}];
So I'm rewriting an app for iOS 7 with AFNetworking 2.0 and I'm running into the issue of sending a batch of requests at once and tracking their progress. In the old AFNetworking there was the enqueueBatchOfHTTPRequestOperations:progressBlock:completionBlock: method on AFHTTPClient, this is clearly refactored out and I'm a bit confused on how to enqueue multiple requests.
I have created a subclass of AFHTTPSessionManager and I'm using the POST:... and GET:... methods to communicate with the server. But I can't find anything in the code and/or docs to enqueue multiple requests at once like with the old AFHTTPClient.
The only thing I can find is the undocumented batchOfRequestOperations:progressBlock:completionBlock: method on AFURLConnectionOperation, but that looks like the iOS 6 way of doing this.
Clearly I'm missing something in the new NSURLSession concept that I should use to batch requests or looking over a new AFNetworking feature. Hope someone can help me on the right track here!
tl;dr: How can I send a batch of requests with my AFHTTPSessionManager subclass?
Thanks Sendoa for the link to the GitHub issue where Mattt explains why this functionality is not working anymore. There is a clear reason why this isn't possible with the new NSURLSession structure; Tasks just aren't operations, so the old way of using dependencies or batches of operations won't work.
I've created this solution using a dispatch_group that makes it possible to batch requests using NSURLSession, here is the (pseudo-)code:
// Create a dispatch group
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 10; i++) {
// Enter the group for each request we create
dispatch_group_enter(group);
// Fire the request
[self GET:#"endpoint.json"
parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
// Leave the group as soon as the request succeeded
dispatch_group_leave(group);
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
// Leave the group as soon as the request failed
dispatch_group_leave(group);
}];
}
// Here we wait for all the requests to finish
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// Do whatever you need to do when all requests are finished
});
I want to look write something that makes this easier to do and discuss with Matt if this is something (when implemented nicely) that could be merged into AFNetworking. In my opinion it would be great to do something like this with the library itself. But I have to check when I have some spare time for that.
Just updating the thread... I had the same problem and after some researches I found some good solutions, but I decided to stick with this one:
I am using the project called Bolts. So, for the same sample above posted by #Mac_Cain13, it would be:
[[BFTask taskWithResult:nil] continueWithBlock:^id(BFTask *task) {
BFTask *task = [BFTask taskWithResult:nil];
for (int i = 0; i < 10; i++) {
task = [task continueWithBlock:^id(BFTask *task) {
return [self executeEndPointAsync];
}];
}
return task;
}] continueWithBlock:^id(BFTask *task) {
// Everything was executed.
return nil;
}];;
- (BFTask *) executeEndPointAsync {
BFTaskCompletionSource *task = [BFTaskCompletionSource taskCompletionSource];
[self GET:#"endpoint.json" parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
[task setResult:responseObject];
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
[task setError:error];
}];
}];
return task.task;
}
Basically, it's stacking all of the tasks, waiting and unwrapping until there is no more tasks, and after everything is completed the last completion block is executed.
Another project that does the same thing is RXPromise, but for me the code in Bolts was more clear.
For request which can be post or get, you can use AFNetworking 2.0 for batch operation as firstly you need to create operation like this:
//Request 1
NSString *strURL = [NSString stringWithFormat:#"your url here"];
NSLog(#"scheduleurl : %#",strURL);
NSDictionary *dictParameters = your parameters here
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:#"POST" URLString:strURL parameters:dictParameters error: nil];
AFHTTPRequestOperation *operationOne = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operationOne = [AFHTTPResponseSerializer serializer];
[operationOne setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
//do something on completion
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"%#",[error description]);
}];
//Request 2
NSString *strURL1 = [NSString stringWithFormat:#"your url here"];
NSLog(#"scheduleurl : %#",strURL);
NSDictionary *dictParameters1 = your parameters here
NSMutableURLRequest *request1 = [[AFHTTPRequestSerializer serializer] requestWithMethod:#"POST" URLString:strURL1 parameters:dictParameters1 error: nil];
AFHTTPRequestOperation *operationTwo = [[AFHTTPRequestOperation alloc] initWithRequest:request1];
operationTwo = [AFHTTPResponseSerializer serializer];
[operationTwo setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
//do something on completion
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"%#",[error description]);
}];
//Request more here if any
Now perform batch operation like this :
//Batch operation
//Add all operation here
NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:#[operationOne,operationTwo] progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations)
{
NSLog(#"%i of %i complete",numberOfFinishedOperations,totalNumberOfOperations);
//set progress here
yourProgressView.progress = (float)numberOfFinishedOperations/(float)totalNumberOfOperations;
} completionBlock:^(NSArray *operations)
{
NSLog(#"All operations in batch complete");
}];
[[NSOperationQueue mainQueue] addOperations:operations waitUntilFinished:NO];
On AFNetworking 2.0, AFHTTPClient has been split on AFHTTPRequestOperationManager and AFHTTPSessionManager, so probably you could start with the first, which has operationQueue property.
Currently, NSURLSession tasks are not suitable for the same kind of patterns request operations use. See the answer from Mattt Thompson here regarding this issue.
Direct answer: if you need dependencies or batches, you'll still need to use request operations.