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);
});
Related
Hello I want to get result from asynchronous block from the class DataViewControllerto use it in the class MagazineViewControllerbut I don't get the value of returnedDataunless the ViewDidLoadmethod is called for the second time or more as the operation is asynchronous, I know that I have to implement a completion block and call it but I don't know exactly how to do it, this is my code, how can I edit it to give me the value returned in the setCompletionBlockWithSuccessblock and use it in other classes
DataViewController.m
- (void)getDataFromServer:(NSString *)urlString completion:(void (^)(AFHTTPRequestOperation *operation, id responseObject))completion {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSURLCredential *credential = [NSURLCredential credentialWithUser:userName password:Password persistence:NSURLCredentialPersistenceNone];
NSMutableURLRequest *request = [manager.requestSerializer requestWithMethod:#"GET" URLString:urlString parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCredential:credential];
[operation setResponseSerializer:[AFJSONResponseSerializer alloc]];
[operation setCompletionBlockWithSuccess:completion failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#", error);
} ];
[manager.operationQueue addOperation:operation];
}
The MagazineViewController class (Where I want to use the returnedData)
#implementation MagazineViewController
void (^completion)(AFHTTPRequestOperation* , id) = ^(AFHTTPRequestOperation *operation, id responseObject){
returnedData = responseObject;};
- (void)viewDidLoad {
[super viewDidLoad];
DataViewController *dataViewController = [[DataViewController alloc] init];
[dataViewController getDataFromServer:#"http://firstluxe.com/api/search/search?query=vogue&language=1&output_format=JSON" completion:completion];
NSLog(#"returned %# ", returnedData); // here I get the value after the view controller is loaded for the second time or more
You should define completion inline. Your first NSLog statement is called before completion has been called, so the returnedData variable has not been set.
#implementation MagazineViewController
- (void)viewDidLoad {
[super viewDidLoad];
DataViewController *dataViewController = [[DataViewController alloc] init];
[dataViewController getDataFromServer:#"http://firstluxe.com/api/search/search?query=vogue&language=1&output_format=JSON"
completion:^ (AFHTTPRequestOperation *operation, id responseObject) {
returnedData = responseObject;
NSLog(#"returned %# ", returnedData);
}];
}
#end
[self getDataFromServer:#"API URL" completion:^(int *operation, id responseObject) {
// Result from async method here.
}];
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.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I create block like this:
1) Define your own completion block,
typedef void(^myCompletion)(BOOL);
2) Create a method which takes your completion block as a parameter,
-(void) myMethod:(myCompletion) compblock{
//do stuff
compblock(YES);
}
3)This is how you use it,
[self myMethod:^(BOOL finished) {
if(finished){
NSLog(#"success");
}
}];
How can I send array in block and then get new array from block?
//here I get array of image id's and go in loop for download it all,
NSString *URLString = [NSString stringWithFormat: #"%#", requestString];
NSURL * url = [NSURL URLWithString:URLString];
NSURLRequest * urlRequest = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
requestOperation.responseSerializer = [AFImageResponseSerializer serializer];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
userWithImage = [responseObject copy];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Image error: %#", error);
}];
[requestOperation start];
//here I save it to mutable array and send as completion block,
yep, I think it will be better to send 1 image id and return in block 1 image. And in method there I will call the block - make action with photo separately. so, Is it possible to do?
I can do something like this with NSNotifications, but it will be more widely when it can be in blocks..
1) Define your own completion block
typedef void(^myCompletion)(BOOL finished, NSArray *myArray);
2) Create a method which takes your completion block as a parameter,
-(void)myMethod:(myCompletion)compblock {
//do stuff
NSArray *myArray = ...;
compblock(YES, myArray);
}
3)This is how you use it,
[self myMethod:^(BOOL finished, NSArray *myArray) {
if (finished){
NSLog(#"success");
}
}];
If you just want to write a wrapper around the AFNetworking request you could write a method like this:
- (void)downloadImageWithPath:(NSString *)path completion:(void (^)(AFHTTPRequestOperation *operation, UIImage *image, NSError *error))completion __attribute__((nonnull(2)));
{
NSParameterAssert(completion);
NSURL *url = [NSURL URLWithString:path];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *requestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
requestOperation.responseSerializer = [AFImageResponseSerializer serializer];
[requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
completion(operation, responseObject, nil);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
completion(operation, nil, error);
}];
[requestOperation start];
}
You would invoke this with something like:
[self downloadImageWithPath:#"http://url/to/image.jpg"
completion:^(AFHTTPRequestOperation *operation, UIImage *image, NSError *error) {
if (error) {
// handle error
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
// Update UI with image
});
}];
I am using AFnetworking for authentication, but after Authenticating user I need to move to the next View controller. It's moving, but also it moves when there's an error too. How can I make use of the responseObject in AFNetworking to my need...... Below is my CODE
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
//[operation setCredential:credential];
[operation setResponseSerializer:[AFJSONResponseSerializer alloc]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//This is where if the response is successful it should move
if ([responseObject objectForKey:#"Success"]) {
MainView *home = [self.storyboard instantiateViewControllerWithIdentifier:#"MainViewController"];
[self.navigationController pushViewController:home animated:YES];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#", error);
}];
[manager.operationQueue addOperation:operation];
To check if success or not, you are using :
if ([responseObject objectForKey:#"Success"])
I don't know your service and the retrieve Diccionary, however I think you always have an object for the key #"Success", and because #"Success" exists is passing in all the scenarios.
Try to check the value, e.g.:
if ([[responseObject objectForKey:#"Success"] isEqualsToString #"ok"])
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];
}];