Objective-C AFnetworking: stopping a request - objective-c

Evening, I'm working with the Marvel-API, trying to download all the characters.
To download all the characters you have to do multiple requests, in each request you can specified the limit and the offset.
So I've set the limit at the max of 100 and for every request I increase the offset by 100.
Doing that, I do infinite request. Of course.
So I thought that I should stop when the "results" array retrieved from the JSON object is empty.
So the logic should be good, I keep requesting characters 100 by 100 until there are no more to retrieve.
But of course working with networking and async code isn't always so easy. And obviously I got stocked.
I'm sure that the problems is in these lines of code:
#pragma mark - Requesting data
-(void)getData {
NetworkManager *networkManager = [NetworkManager alloc];
while(self.requestMustEnd == false) {
NSLog(#"offset: %d", networkManager.offset);
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:networkManager.getUrlPath parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(#"JSON: %#", responseObject);
[self parseResponseData:responseObject];
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[networkManager increaseOffset];
}
}
#pragma mark - Parsing Method
-(void)parseResponseData:(NSDictionary *)responseDictionary {
NSArray *marvelArray = [[responseDictionary objectForKey:#"data"] objectForKey:#"results"];
if (marvelArray.count == 0) {
self.requestMustEnd = true;
}
for(NSDictionary* marvel in marvelArray)
{
Character *currentMarvelEntity = [[Character alloc] initWithMarvel:marvel];
//NSLog(#"currentMarvelEntity %#", currentMarvelEntity.name);
[self.marvelCharacters addObject:currentMarvelEntity];
}
[self.tableView reloadData];
}
The key part to stop the request is:
if (marvelArray.count == 0) {
self.requestMustEnd = true;
}
But, still, it never end to request. it is not for the if condition, I'm sure. But probably because, having an async code, the getData func no matter what keep requesting data.
Any tips?

This post may help. Try:
[manager.operationQueue cancelAllOperations];

Related

recursive function get "Variable is not assignable (missing __block type specifier) error"

I want to send AFNetworking requests consequently in a queue. I create a recursive function as below for this aim:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(#"Start ...");
[self sentTest:0];
}
- (void) sentTest:(int)i{
if(i >= 10)
{
NSLog(#"Finished");
return;
}
NSLog(#"sending message %d ...", i);
NSMutableDictionary *params = [#{#"param1": #"value1",
#"param2": #"value2",
} mutableCopy];
NSString *webServiceUrl = #"MY_REST_SERVICE_URL";
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager POST:webServiceUrl parameters:params success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"message sent successful %d", i);
// Now call the method again
[self sentTest:i++];
return;
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"message sent failure %d", i);
return;
}];
}
I get this error:
Variable is not assignable (missing __block type specifier) error"
I know that I need to define block type, but I don't know how to use it in this recursive function.
I had some concerns that manager was being destroyed when the method ends, however it's being retained by the block.
I am not certain your code will work, however the actual error message relates to updating i in the block (and it would need to have the __block attribute applied), however there is no reason to increment i at all, simply pass in i + 1 to the recursed method:
[self sentTest:i + 1];
(you had a missing semicolon in your code, so I am not convinced that is the real code or not).

How can I refactor duplicated codes for error?

I'm using block for my APIs and the API class throws error via block like below code.
[HJHLifeAPI deletePlantWithIdentifier:identifier completionHandler:^(NSError *error) {
if (error) {
[[error alertView] show];
return ;
}
[self refresh:self.refreshControl];
}];
But the problem is that I use this pattern of codes in several places. As a result, I should write several duplicated codes for error handling. Is there any way to refactor this code? I think exception can be one solution, but I think Apple don't encourage developers to use it.
It's up to How your HJHLifeAPI is designed.
I usually use AFNetworking for API things and here's an example.
// This is the method like deletePlantWithIdentifier:
// It actually invoke __requestWitPath:
- (void)requestSomethingWithId:(NSString *)memId done:(NetDoneBlock)done
{
NSMutableDictionary *param_ = #{#"key":#"id"};
[self __requestWithPath:#"/apiPath.jsp" parameter:param_ done:done];
}
#pragma PRIVATE
- (void)__requestWithPath:(NSString *)apiPath parameter:(NSDictionary *)parameter done:(NetDoneBlock)done
{
AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:[NSURL URLWithString:SERVER_URL]];
AFHTTPRequestOperation *operation = [manager POST:apiPath parameters:parameter constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
done();
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Error Handle Here
}];
[operation start];
}
You can handle all errors in one __request....
Create the block
void(^errorHandler)(NSError *error) = ^(NSError *error) {
if (error) {
[[error alertView] show];
return ;
}
[self refresh:self.refreshControl];
}
Save it somewhere (don't forget to copy it)
self.errorHandler = errorHandler;
Reuse it everywhere:
[HJHLifeAPI deletePlantWithIdentifier:identifier completionHandler:self.errorHandler];

Wrapping blocks based API in convenience methods

I'm using AFNetworking 2.0 to access a web api (although this would apply to NSURLSession as well), and currently I have a bunch of code that looks like this:
[self.rottenTomatoesManager GET:#"movies.json" parameters:#{#"q" : searchString, #"apikey" : [self.rottenTomatoesManager apiKey]}
success:^(NSURLSessionDataTask *task, id responseObject) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
if(response.statusCode == 200){
NSDictionary *responseData = responseObject;
self.searchResults = responseData[#"movies"];
[self.searchDisplayController.searchResultsTableView reloadData];
[self.tableView reloadData];
}
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Error loading movies %#", error.localizedDescription);
}];
I'd like to take that functionality and wrap it in a convenience method that looks something like this
NSArray *results = [self.rottenTomatoesManager searchMoviesWithTitle:#"The avengers"]
to clean up the ViewController code and to make most of the code framework agnostic.
What is the best way to do this so that I'm not turning a nice asynchronous blocks based API into a synchronous API?
Callback blocks are great for this.
[self loadSomethingWithCallback:^(NSArray *results) {
NSLog(#"%#", results);
}];

Dealing with AFNetworking2.0 asynchronous HTTP request

I am very new to the concept of asynchronous programming, but I get the general gist of it (things get run in the backround).
The issue I'm having is I have a method which utilizes AFNetworking 2.0 to make an HTTP post request, and it works for the most part.
However, I can't figure out how to get the method to actually return the value received from the response as the method returns and THEN gets the value from the response.
-(int) registerUser
{
self.responseValue = 000; //Notice I set this to 000 at start of method
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
NSDictionary *parameters = #{ #"Username": #"SomeUsername" };
[manager POST:#"http://XXX/register"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"JSON: %#", responseObject);
NSError *err = nil;
self.responseValue = [[responseObject objectForKey:#"res"] intValue];
//Note: This^ value returns 99 and NSLogs confirm this
}
failure:^(AFHTTPRequestOperation *operation, NSError *err)
{
NSLog(#"Error: %#", err);
}];
return self.responseValue; //This will return 000 and never 99!
}
Whats the 'proper' way to handle this situation? I've heard whispers of using a 'callback', but I don't really understand how to implement that in this situation.
Any guidance or help would be awesome, cheers!
The issue is that the POST runs asynchronously, as you point out, so you are hitting the return line well before the responseValue property is actually set, because that success block runs later. Put breakpoints/NSLog statements in there, and you'll see you're hitting the return line first.
You generally do not return values from an asynchronous methods, but rather you adopt the completion block pattern. For example:
- (void)registerUserWithCompletion:(void (^)(int responseValue, NSError *error))completion
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
NSDictionary *parameters = #{ #"Username": #"SomeUsername" };
[manager POST:#"http://XXX/register"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLog(#"JSON: %#", responseObject);
int responseValue = [[responseObject objectForKey:#"res"] intValue];
if (completion)
completion(responseValue, nil);
}
failure:^(AFHTTPRequestOperation *operation, NSError *err)
{
NSLog(#"Error: %#", err);
if (completion)
completion(-1, err); // I don't know what you want to return if it failed, but handle it appropriately
}];
}
And then, you could use it as follows:
[self registerUserWithCompletion:^(int responseValue, NSError *error) {
if (error)
NSLog(#"%s: registerUserWithCompletion error: %#", __FUNCTION__, error);
else
NSLog(#"%d", responseValue);
// do whatever you want with that responseValue here, inside the block
}];
// Needless to say, don't try to use the `responseValue` here, because
// `registerUserWithCompletion` runs asynchronously, and you will probably
// hit this line of code well before the above block is executed. Anything
// that is dependent upon the registration must called from within the above
// completion block, not here after the block.
Note, I'd suggest you retire that responseValue property you had before, because now that you're using completion blocks, you get it passed back to you via that mechanism, rather than relying on class properties.
Check this one and use search ;-))
Getting variable from the inside of block
its a lot of duplicates already!
:-)

How to return response object in AFNetworking in class method?

I’m sorry if this question is too basic, but I can’t seem to find a an answer online.
I want to fetch the JSON result and have them returned with the class method below. But as you can see, by fetching the JSON in the block method, I don’t have a way to return them as result.
What is the correct way to to return them as NSDictionary from inside block method, or is there any other way to simplify this?
+ (NSDictionary *) fetchtPostsCount:(NSString *) count
page: (NSString *) page {
NSDictionary *requestParameter = [[NSDictionary alloc] initWithObjectsAndKeys:count, #"count", page, #"page", nil];
[[self sharedClient] GET:#"get_recent_posts"
parameters:requestParameter success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"%#", [responseObject objectForKey:#"posts"]);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"%#", error);
}];
return nil;
}
AFNetworking executes requests on different thread, and calls the success or failure block when its done. Conceptually, you can imagine that your fetchPostsCount method will have already completed and returned its value by the time request is finished.
You almost certainly want it to work that way. Running the request on another thread and NOT waiting for it, allows your main UI thread to continue processing events and rendering screen updates. You don't want to get in the way of those things, or the user (and iOS) will get unhappy.
However, if you insist on waiting for the request to complete before returning, you could set a flag to monitor the status of the request, and then wait on that flag until the request is complete:
BOOL requestComplete = NO;
id requestResponseObject = nil;
[[self sharedClient] GET:#"get_recent_posts"
parameters:requestParameter success:^(NSURLSessionDataTask *task, id responseObject) {
requestResponseObject = responseObject;
requestComplete = YES;
NSLog(#"%#", [responseObject objectForKey:#"posts"]);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
requestComplete = YES;
NSLog(#"%#", error);
}];
while (!requestComplete)
{
// Tie up the thread, doing nothing...
}
// Proceed