Untraceable AFNetworking memory leak - ios

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).

Related

AFNetworking - stuck after posting http data

I've inherited some Objective-C code from 2013: so old that it used AFNetworking 1.0!
#implementation AFClaimNotificationAPIClient
+ (AFClaimNotificationAPIClient *)sharedClient {
static AFClaimNotificationAPIClient *_sharedClient = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedClient = [[AFClaimNotificationAPIClient alloc] initWithBaseURL:[NSURL URLWithString:kClaimNotificationURL]];
});
return _sharedClient;
}
- (void) submit:(ClaimNotification *) claimNotification
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
NSMutableURLRequest *request = [self multipartFormRequestWithMethod:#"POST" path:kClaimNotificationURL parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[self populateFormDataForJson:formData andClaimNotification:claimNotification];
[self populateFormDataWithAttachemnts:formData andClaimNotification:claimNotification];
}];
AFJSONRequestOperation *operation = [[AFJSONRequestOperation alloc] initWithRequest:request];
DDLogVerbose(#"%#", request.HTTPBody);
[operation setCompletionBlockWithSuccess:success failure:failure];
[operation start];
}
In the corresponding header file for this class, the AFClaimNotificationAPIClient is defined thus:
#interface AFClaimNotificationAPIClient : AFHTTPClient
and AFHTTPClient no longer exists. It was dropped in AFNetworking 2.0, which came out shortly after this code was written.
After much forum searching, I've actually managed to get it partially working again by upgrading to AFNetworking 2.0, and redefining AFClaimNotificationAPIClient as an AFHTTPSessionManager:
#interface AFClaimNotificationAPIClient : AFHTTPSessionManager
My submit button code now looks like this:
- (void) submit:(ClaimNotification *) claimNotification
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
NSURLSessionDataTask *request = [self POST:kClaimNotificationURL parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[self populateFormDataForJson:formData andClaimNotification:claimNotification];
[self populateFormDataWithAttachemnts:formData andClaimNotification:claimNotification];
} success:^(NSURLSessionDataTask *task, id responseObject) {
DDLogVerbose(#"Post success");
// handle success
} failure:^(NSURLSessionDataTask *task, NSError *error) {
DDLogVerbose(#"Post error");
}];
// [operation start];
[request resume]; // [request start] doesn't work
}
I chose AFHTTPSessionManager as the new type for my class because it's the only one that I could find that contains the constructingBodyWithBlock definition, and I was trying to keep the code as close to the original as possible.
Amazingly enough, my reworked code actually posts data to the server and gets a reply. However, the app hangs at that point because the calling code (not shown here) is not receiving a success (or failure) message. I can see that I've removed a whole step from the original code - the setting up of the operation variable and then the triggering of its setCompletionBlockWithSuccess method.
You need to execute the block according to the response passing a responseObject or an error with the corresponding operation
try this
- (void) submit:(ClaimNotification *) claimNotification
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure {
NSURLSessionDataTask *request = [self POST:kClaimNotificationURL parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[self populateFormDataForJson:formData andClaimNotification:claimNotification];
[self populateFormDataWithAttachemnts:formData andClaimNotification:claimNotification];
} success:^(NSURLSessionDataTask *task, id responseObject) {
DDLogVerbose(#"Post success");
// handle success
success(nil,responseObject);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
DDLogVerbose(#"Post error");
failure(nil, error);
}];
// [operation start];
[request resume]; // [request start] doesn't work
}
You can use AFHTTPRequestOperation as well and this will return both parameters needed in your callbacks

How to return value in completion block

I have class that manages connections with AFNetworking.
So I want to call my function like NSDictionary *dict = [ServerManager requestWithURL:#"https://someurl.com"];
And that's the function in the other class:
- (NSDictionary *) requestWithURL:(NSString *)requestURL {
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];
[manager GET:requestURL parameters:nil progress:nil
success:^(NSURLSessionDataTask *operation, id responseObject){
return responseObject;
}
failure:^(NSURLSessionDataTask *operation, NSError *error) {
}];
}
I know that is incorrect to do that. So what should I do to return responseObject back to NSDictionary *dict? I'd like to get the basic idea of asynchronous development with blocks.
Since the networking request completes long after its is launched, the only way to handle the result is with a block passed to your request method...
// when request completes, invoke the passed block with the result or an error
- (void)requestWithURL:(NSString *)requestURL completion:(void (^)(NSDictionary *, NSError *))completion {
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];
[manager GET:requestURL parameters:nil progress:nil success:^(NSURLSessionDataTask *operation, id responseObject){
if (completion) completion((NSDictionary*)responseObject, nil);
}, failure:^(NSURLSessionDataTask *operation, NSError *error) {
if (completion) completion(nil, error);
}];
}
Make it public in a ServerManager.h
- (void)requestWithURL:(NSString *)requestURL completion:(void (^)(NSDictionary *, NSError *))completion;
Elsewhere, call it:
[ServerManager requestWithURL:#"http://someurl.com" completion:^(NSDictionary *dictionary, NSError *error) {
// check error and use dictionary
}];

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

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!

How can we pass the parameters along with block methods in objecitve-c

I want to pass the 'NSString' as a parameter for as URLString in below method, how can we do that
-(void)makeServiceCallSuccess:(void (^)(NSDictionary *response))success
failure:(void (^)(NSError *error))failure {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:URLString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
response = (NSDictionary *)responseObject;
success(response);
NSLog(#"JSON: %#", response);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
NSLog(#"Error: %#", error);
}];
}
simply add parameter in method, as usually we are doing like
-(void)makeServiceCallSuccessWithURLString:(NSString*)urlString withResponseHandler:(void (^)(NSDictionary *response))success
failure:(void (^)(NSError *error))failure
There is no wat to pass a paramether in that method as it, you can edit it and add a parameter or read a value from aproperty
-(void)makeServiceCallSuccess:(void (^)(NSDictionary *response))success
failure:(void (^)(NSError *error))failure {
NSString* myString = self.myProperty;
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:myString parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
response = (NSDictionary *)responseObject;
success(response);
NSLog(#"JSON: %#", response);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
failure(error);
NSLog(#"Error: %#", error);
}];
}

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

Resources