AFNetworking, using shared AFHttpSessionManager, pending request is cancelled when a newer request complete - afnetworking

I'm using AFHTTPSessionManager in singleton mode, and start multiple request like :
[MyAPI testSlow];
[MyAPI testQuick];
MyAPI definition:
+(void)testSlow {
AFHTTPSessionManager *manager = [BPNetworkManager sharedManager]; // Get Manager Singleton
NSString *urlString = [BPCommonUtils getUrlByPath:#"/api/test/testSlow"];
[manager GET:urlString parameters:nil success: ^(NSURLSessionTask *operation, id responseObject) {
NSLog(#"slowSuccess");
} failure:^(NSURLSessionTask *operation, NSError *error){
NSLog(#"slowFail");
}];
}
+(void)testQuick {
AFHTTPSessionManager *manager = [BPNetworkManager sharedManager];
NSString *urlString = [BPCommonUtils getUrlByPath:#"/api/test/testQuick"];
[manager GET:urlString parameters:nil success: ^(NSURLSessionTask *operation, id responseObject) {
NSLog(#"quickSuccess");
} failure:^(NSURLSessionTask *operation, NSError *error){
NSLog(#"quickFail");
}];
}
When 'quick Task' returned with success, the 'slow Task' failed immediately, fail message info :
#"NSURLErrorDomain" - code: 18446744073709550617
#"NSLocalizedDescription" : #"cancelled"
How can I solve it? Thank you!

Related

Untraceable AFNetworking memory leak

TL;DR : Clone and check leak yourself https://github.com/JakubMazur/SO41343532/
I have a one class that handle all my networking. It's called ResponseOrganizer and in there I have a class method:
+ (void)getSth:(void (^)(NSURLSessionDataTask *operation, NSArray *locales, id plainObject))success failure:(void (^)(NSURLSessionDataTask *operation, NSError *error))failure {
Connection *connection = [Connection new];
connection.urlString = #"http://sample-file.bazadanni.com/download/txt/json/sample.json";
connection.requestMethodType = GET;
[connection fireWithSuccess:^(NSURLSessionDataTask *operation, NSArray *returnArray, id originalResponse) {
success(operation, returnArray, originalResponse);
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
failure(operation, error);
}];
}
Where Connection is a single my internal connection object:
Here is the implementation:
#import "Connection.h"
#interface Connection()
#property (weak,nonatomic) AFHTTPSessionManager *manager;
#end
#implementation Connection
#pragma mark - Connection groundwork
-(void)fireWithSuccess:(void (^)(NSURLSessionDataTask *operation, NSArray* returnArray, id originalResponse))success failure:(void (^)(NSURLSessionDataTask *operation, NSError *error))failure {
self.manager = [AFHTTPSessionManager manager];
[self.manager urlString:self.urlString withMethod:self.requestMethodType parameters:self.paramaters success:^(NSURLSessionDataTask *operation, id responseObject) {
success(operation,#[responseObject],nil);
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
failure(operation,error);
}];
}
#end
And I have a category calling right method inside AFNetworking. To simplify it look like this:
-(void)urlString:(NSString*)urlString withMethod:(RequestMethodType)method parameters:(NSDictionary*)parameters success:(void (^)(NSURLSessionDataTask *operation, id responseObject))success failure:(void (^)(NSURLSessionDataTask *operation, NSError *error))failure {
switch (method) {
case GET: {
[self getWithURLString:urlString parameters:parameters success:^(NSURLSessionDataTask *operation, id responseObject) {
success(operation,responseObject);
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
failure(operation,error);
}];
break;
}
}
And when I want to make request for example in my ViewController I make it like this:
[ResponseOrginizer getSth:^(NSURLSessionDataTask *operation, NSArray *locales, id plainObject) {
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
}];
And when I run it in instrument I'm always getting:
And it here doesn't matter it will land on success/failure block, it always cause a leak. I extract everything from this and put it on github as simple as possible. Github link:
https://github.com/JakubMazur/SO41343532/
The leak appears here:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
It seems that the reason is the same (or similar) as discussed here - NSURLSession holds a retained reference to the delegate.
Change you code in Connection.m like this to avoid the leak:
-(void)fireWithSuccess:(void (^)(NSURLSessionDataTask *operation, NSArray* returnArray, id originalResponse))success failure:(void (^)(NSURLSessionDataTask *operation, NSError *error))failure {
AFHTTPSessionManager *manager = [Connection manager];
[manager urlString:self.urlString withMethod:self.requestMethodType parameters:self.paramaters success:^(NSURLSessionDataTask *operation, id responseObject) {
success(operation,#[responseObject],nil);
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
failure(operation,error);
}];
}
+ (AFHTTPSessionManager*) manager
{
static dispatch_once_t onceToken;
static AFHTTPSessionManager *manager = nil;
dispatch_once(&onceToken, ^{
manager = [AFHTTPSessionManager manager];
});
return manager;
}
If you need to handle multiple sessions, you may use another approach: call -[AFHTTPSessionManager invalidateSessionCancelingTasks:] when you're done with the session, e.g.:
-(void)fireWithSuccess:(void (^)(NSURLSessionDataTask *operation, NSArray* returnArray, id originalResponse))success failure:(void (^)(NSURLSessionDataTask *operation, NSError *error))failure {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager urlString:self.urlString withMethod:self.requestMethodType parameters:self.paramaters success:^(NSURLSessionDataTask *operation, id responseObject) {
success(operation,#[responseObject],nil);
[manager invalidateSessionCancelingTasks:YES];
} failure:^(NSURLSessionDataTask *operation, NSError *error) {
failure(operation,error);
[manager invalidateSessionCancelingTasks:YES];
}];
}
(Note: pass YES if you want to cancel pending tasks, NO otherwise).

Wait for a request to succeed \ fail before firing up the next request (one request queue)

I have a few requests that needs to fire one by one while depending on the previous response.
That was pretty straight forward with NSOperation and trying to figure out
what's the best approach here with Sessions & AFNetworking >= 3.0
-(void)startGet
{
NSString *urlStr = [NSString stringWithFormat:#"https://test.com/test?%ld",(long)test];
NSURL *URL = [NSURL URLWithString:urlStr];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSLog(#"NUMBER: %ld",(long)test);
[manager GET:URL.absoluteString parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(#"task: %#",task.currentRequest.URL.absoluteString);
} failure:^(NSURLSessionTask *operation, NSError *error) {
//NSLog(#"Error: %#", error);
}];
}
- (void)viewDidLoad {
[super viewDidLoad];
for (int i=0; i<10; i++)
{
test = i;
[self startGet];
}
}
The log I want to get is:
https://test.com/test?0
https://test.com/test?1
https://test.com/test?2
https://test.com/test?3
https://test.com/test?4
https://test.com/test?5
https://test.com/test?6
...
Things I've tried:
...
dispatch_group_t serviceGroup = dispatch_group_create();
...
-(void)startGet
{
NSString *urlStr = [NSString stringWithFormat:#"https://test.com/test?%ld",(long)test];
NSURL *URL = [NSURL URLWithString:urlStr];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSLog(#"NUMBER: %ld",(long)test);
dispatch_group_enter(serviceGroup);
[manager GET:URL.absoluteString parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(#"task: %#",task.currentRequest.URL.absoluteString);
dispatch_group_leave(serviceGroup);
} failure:^(NSURLSessionTask *operation, NSError *error) {
dispatch_group_leave(serviceGroup);
//NSLog(#"Error: %#", error);
}];
dispatch_group_wait(serviceGroup,DISPATCH_TIME_FOREVER);
}
Although the request came out in the right order I would still get mixed responses like so:
https://test.com/test?4
https://test.com/test?7
https://test.com/test?1
https://test.com/test?3
I'm not sure if something is wrong with the code or I totally misunderstood the purpose of dispatch_group_t in that case.
I've digged around and saw a comment by matt from AFNetworking about integrating a simple solution using Operations & session in AF and it is soon to be public but it was over 2 years ago.
I'm trying to solve this without using nested requests or NSOperations
Thanks
You can try out 2 approaches
Semaphore
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSLog(#"NUMBER: %ld",(long)test);
[manager GET:URL.absoluteString parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
dispatch_semaphore_signal(semaphore);
NSLog(#"task: %#",task.currentRequest.URL.absoluteString);
} failure:^(NSURLSessionTask *operation, NSError *error) {
//NSLog(#"Error: %#", error);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
WaitUntillFinished
[operation waitUntilFinished];
You can check for detailed implementation at below link:
https://github.com/AFNetworking/AFNetworking/issues/1804

iOS: AFHTTPSession manager response data

in my app I'm using the new AFN 3.0 and I have
AFHTTPSessionManager *manager
instead of
AFHTTPRequestOperation *operation
my problem is that before I was able to get some data from RequestOperation as:
NSURL *url = operation.request.URL;
//or
NSNumber statusCode = operation.response.statusCode;
//or
NSData *responseData = operation.responseData;
and how can I get this elements with AFHTTPSessionManager?
thanks
in v2 you were getting AFHTTPRequestOperation for the request
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://example.com/resources.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
But in the v3 you will get NSURLSessionTask
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager GET:#"http://example.com/resources.json" parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(NSURLSessionTask *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
So based on that you can get the details the from the NSURLSessionTask like the currentRequest , response etc
For more changes and details, you can refer to the migration guide of AFNetworking
AFNetworking Migration Guide
For NSURLSessionTask Reference : NSURLSessionTask

iOS Block issue with AFNetworking

I'm currently learning blocks, and I want to use them with AFNetworking. I made method in Singletone:
- (void)getAllPlayers:(void (^)(NSArray *playerz, bool succeed))blockPlayers {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"https://api.myjson.com/bins/530re" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
I called this method in viewDidLoad:
[[Manager sharedInstance]getAllPlayers:^(NSArray *playerz, bool succeed) {
if (succeed == YES) {
self.allClubs = playerz;
[self.tableView reloadData];
}
}];
But nothing is downloaded.
Yo forgot to call the blockPlayers Completion handler.
- (void)getAllPlayers:(void (^)(NSArray *playerz, bool succeed))blockPlayers {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"https://api.myjson.com/bins/530re" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSarray *returnedPLayerz = [NSArray array];
//Here treat operation and response Object to extract playerz and assing it to returnedPlayerz
blockPLayers(returnedPlayerz, YES);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
blockPlayers(nil, NO);
NSLog(#"Error: %#", error);
}];
}
You got data from server in response object. After that you need to parse it and return in block:
- (void)getAllPlayers:(void (^)(NSArray *playerz, bool succeed))blockPlayers {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"https://api.myjson.com/bins/530re" parameters:nil
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray* players = [self getPlayersFromJson:responseObject];
blockPlayers(players, YES);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
blockPlayers(nil, NO);
}];
}
So you need to create parser method getPlayersFromJson

Cancel Post request in Afnetworking 2.0

Hi I am making post request using AFnetworking 2.0.
My request looks like this.
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFXMLParserResponseSerializer serializer];
[manager.requestSerializer setValue:#"some value" forHTTPHeaderField:#"x"];
[manager POST:url parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
//doing something
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// error handling.
}];
How can i cancel this request???
POST method return the AFHTTPRequestOperation operation. You can cancel it by calling cancel.
AFHTTPRequestOperation *post =[manager POST:nil parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
//doing something
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// error handling.
}];
//Cancel operation
[post cancel];
Tried [manager.operationQueue cancelAllOperations] ?

Resources