I have a leak in the following code which uses GCD. Note: I am not using ARC. The leak is exactly at
__block NSMutableArray *newImages = [NSMutableArray new];
dispatch_async(serialQueue, ^{
for (NSDictionary *imageData in results) {
NSURL *url = [NSURL URLWithString:[imageData objectForKey:#"url"]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSError *error = nil;
NSHTTPURLResponse *response = nil;
NSData *imageData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error == nil && imageData != nil && response.statusCode == 200) {
UIImage *image = [UIImage imageWithData:imageData];
[newImages addObject:image];
}
else {
self.errorLabel.text = #"An error has occured downloading some images.";
[self.spinner stopAnimating];
}
}
});
According to instruments, I am leaking at: [newImages addObject:image];. Why is this leaking? newImages is an autoreleased object as shown above.
newImages is not autoreleased. new returns an owning reference -- it's equivalent to [[SomeClass alloc] init].
Related
About every single tutorial and example on the internet I see shows how to fetch JSON from some url and show it in Tableview. This is not my problem I know how to do that with AFNetworking framework or with native APIs.
My problem is that after I have downloaded the JSON, I want to show some of it in my UIView labels. I have actually succeeded doing this when I was trying to find a way around NSURLSession inability to cache in iOS 8. But I didn't realize that it was synchronous.
Factory.m
+ (Factory *)responseJson
{
static Factory *shared = nil;
shared = [[Factory alloc] init];
NSHTTPURLResponse *response = nil;
NSString *jsonUrlString = [NSString stringWithFormat:#"http://urltojson.com/file.json"];
NSURL *url = [NSURL URLWithString:[jsonUrlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSError *error = nil;
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:10.0];
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error) {
NSLog(#"error");
} else {
//-- JSON Parsing
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:nil];
//NSLog(#"Result = %#",result);
shared.responseJson = result;
}
return shared;
}
My question is that is it possible to use for example AFNetwoking to do the same thing? Am I missing some method that I need to call like in case of a TableView
[self.tableView reloadData];
I would like to use that framework because I need to check Reachability and it seems to implement it already.
Edit as asked to show more code
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self factoryLoad];
[self setupView];
}
- (void)factoryLoad
{
Factory *shared = [Factory responseJson];
self.titles = [shared.responseJson valueForKeyPath:#"data.title"];
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}
- (void)setupView
{
self.issueTitleLabel.text = [self.titles objectAtIndex:0];
}
There are a couple oddities in the code you posted.
Factory, which appears to be a singleton class, should be instantiated inside a dispatch_once to ensure thread safety.
In ViewController.m, you are calling factoryLoad on the main thread, which is subsequently calling sendSynchronousRequest on the main thread. Apple's NSURLConnection Documentation warns against calling this function on the main thread as it blocks the thread, making your application unresponsive to user input.
You should not be passing in nil as the error parameter in NSJSONSerialization JSONObjectWithData:.
In your case I would recommend separating the fetching of data from the construction of your singleton object.
Factory.m
+(Factory *)sharedFactory {
static Factory *sharedFactory = nil;
dispatch_once_t onceToken;
dispatch_once(&onceToken, {
sharedFactory = [[Factory alloc] init];
});
}
-(void)fetchDataInBackgroundWithCompletionHandler:(void(^)(NSURLResponse *response,
NSData *data,
NSError *error)
completion {
NSHTTPURLResponse *response = nil;
NSString *jsonUrlString = [NSString stringWithFormat:#"http://urltojson.com/file.json"];
NSURL *url = [NSURL URLWithString:[jsonUrlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:10.0];
NSOperationQueue *downloadQueue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request
queue:downloadQueue
completionHandler:completion];
}
Now you should be able to create a reference to the data with a guarantee that the download request has finished and thus the data will exist.
ViewController.m
-(void)factoryLoad {
[[Factory sharedFactory] fetchDataInBackgroundWithCompletionHandler:^(void)(NSURLResponse *response, NSData *data, NSError *error){
if(!error) {
NSError *error2;
NSDictionary *serializedData = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error2];
if(error2){ /* handle error */ }
self.titles = [serializedData valueForKeyPath:#"data.title"];
[Factory sharedFactory].responseJSON = serializedData;
}
else {
// handle error
}
}];
}
This will guarantee that the download has completed before you try to access any of the downloaded information. However, I've left a few things out here, including any sort of activity indicator displaying to the user that the app is doing something important in the background. The rest is, uh, left as an exercise to the reader.
Ok I took a deeper investigation into Morgan Chen's answer and how to block.
The example code took some modification but I think It works as it should and is better code.
In Factory.m
+ (Factory *) sharedInstance
{
static Factory *_sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
-(void)fetchDataInBackgroundWithCompletionHandler: (void(^)(BOOL success, NSDictionary *data, NSError *error)) block
{
NSString * baseURL = #"http://jsonurl.com/file.json";
AFHTTPRequestOperationManager * manager = [[AFHTTPRequestOperationManager alloc] init];
__weak AFHTTPRequestOperationManager *weakManager = manager;
NSOperationQueue *operationQueue = manager.operationQueue;
[manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(#"internet!");
[weakManager.requestSerializer setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[operationQueue setSuspended:NO];
break;
case AFNetworkReachabilityStatusNotReachable:
NSLog(#"no internet");
[weakManager.requestSerializer setCachePolicy:NSURLRequestReturnCacheDataElseLoad];
[operationQueue setSuspended:YES];
break;
default:
break;
}
}];
[manager.reachabilityManager startMonitoring];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager GET:baseURL parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
if (responseObject && [responseObject isKindOfClass:[NSDictionary class]]) {
block(YES, responseObject, nil);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { // invalid request.
NSLog(#"%#", error.localizedDescription);
block(NO, nil, error);
}];
}
In ViewController.m I call this method on viewDidLoad
-(void)factoryLoad
{
[[Factory sharedInstance] fetchDataInBackgroundWithCompletionHandler:^(BOOL success, NSDictionary *data, NSError *error) {
if (success) {
NSLog(#"we have stuff");
self.responseData = data;
self.titles = [self.responseData valueForKeyPath:#"data.title"];
[self setupView];
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}
}];
}
i can't figure out one memory leak. I will add screen shot with code and marked line where this happens.
Maybe some could help me and take a look.
Thanks.
- (AVAudioPlayer*)getSpeachSoundObject{
NSString *objectIDString = [NSString stringWithFormat:#"%i", jmObject.objectID];
NSString * __weak textPlaySource = [DataController getMediaUrlStringForObjectID:objectIDString parentType:PARENT_TYPE_ITEM_AUDIO];
NSError * error = nil ;
if (textPlaySource) {
//NSURL *soundURL = [[NSURL alloc] initFileURLWithPath:textPlaySource];//[NSURL fileURLWithPath:textPlaySource];
NSData * data = [NSData dataWithContentsOfFile:textPlaySource options:NSDataReadingMapped error:&error ] ;
textPlaySource = nil;
NSError *error;
//speechSound = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:&error];
AVAudioPlayer *lspeechSound = data ? [[AVAudioPlayer alloc] initWithData:data error:&error ] : nil ;
data = nil;
if (error) {
WLog([NSString stringWithFormat:#"Error creating sound file:%#", error]);
}
return lspeechSound;
//soundURL = nil;
}
return nil;
}
I attempted to make -[NSString stringWithContentsOfURL:encoding:error:] asynchronous, by running it a-synchronically from a background thread:
__block NSString *result;
dispatch_queue_t currentQueue = dispatch_get_current_queue();
void (^doneBlock)(void) = ^{
printf("done! %s",[result UTF8String]);
};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
result = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://www.google.com/"] encoding:NSUTF8StringEncoding error:nil];
dispatch_sync(currentQueue, ^{
doneBlock();
});
});
Its working fine, and most importantly, its asynchronous.
My question is if it's safe to do this, or could there be any threading problems etc.?
Thanks in advance :)
That should be safe, but why reinvent the wheel?
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.google.com"]];
[NSURLConnection sendAsynchronousRequest:req queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// etc
}];
You can also use:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSError *error = nil;
NSString *searchResultString = [NSString stringWithContentsOfURL:[NSURL URLWithString:searchURL]
encoding:NSUTF8StringEncoding
error:&error];
if (error != nil) {
completionBlock(term,nil,error);
}
else
{
// Parse the JSON Response
NSData *jsonData = [searchResultString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *searchResultsDict = [NSJSONSerialization JSONObjectWithData:jsonData
options:kNilOptions
error:&error];
if(error != nil)
{
completionBlock(term,nil,error);
}
else
{
//Other Work here
}
}
});
But yes, it should be safe. I've been told though to use NSURLConnection instead due to error calls and such when communicating via the internet. I'm still doing research into this.
-(void)loadappdetails:(NSString*)appid {
NSString* searchurl = [#"https://itunes.apple.com/lookup?id=" stringByAppendingString:appid];
[self performSelectorInBackground:#selector(asyncload:) withObject:searchurl];
}
-(void)asyncload:(NSString*)searchurl {
NSURL* url = [NSURL URLWithString:searchurl];
NSError* error = nil;
NSString* str = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if (error != nil) {
NSLog(#"Error: %#", error);
}
NSLog(#"str: %#", str);
}
My callbacks aren't being called. I'm not that familiar with dispatch_async so any thoughts? Point being, I never see a Response.
+ (void)asyncRequest:(NSURLRequest *)request
success:(void(^)(NSData *, NSURLResponse *))successBlock_
failure:(void(^)(NSData *, NSError *))failureBlock_
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
NSString *result = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(#"-----------------result %#", result);
if (error) {
failureBlock_(data,error);
} else {
successBlock_(data,response);
}
[pool release];
});
}
Why are you implementing your own sendAsync method?
You should look into using the provided one instead, NSURLClassReference
EDIT
Assuming you're developing for OSX, not iOS. Evidently this method is not available for iOS.
I getting the leak in this method even the allocated nsstring is released.
Now I am taken stringWithFormat, but still it is showing the leak at "NSData *returnData=...." line
-(BOOL)getTicket:(NSString*)userName passWord:(NSString*)aPassword isLogin:(BOOL)isLogin
{
NSString* str=#"";
if (isLogin == YES)
{
str =[NSString stringWithFormat:#"AGENT=true&LOGIN_ID=%#&PASSWORD=%#",[self _encodeString:userName],[self _encodeString:aPassword]];
}
else if (isLogin == NO)
{
str =[NSString stringWithFormat:#"AGENT=true&LOGIN_ID=%#&PASSWORD=%#",[self _encodeString:userName],[self _encodeString:aPassword]];
}
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:str]
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:25.0];
[request setHTTPMethod: #"POST"];
NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];
printf("\n returnString in getticket:%s",[returnString UTF8String]);
NSRange textRange;
textRange =[returnString rangeOfString:#"TICKET"];
if(textRange.location != NSNotFound)
{
NSArray* splitValues = [returnString componentsSeparatedByString:#"TICKET="];
NSString* str1 = [splitValues objectAtIndex:1];
NSArray* splitValues1 = [str1 componentsSeparatedByString:#"RESULT"];
NSString* ticket1 = [splitValues1 objectAtIndex:0];
self.ticket = ticket1;
self.isCorrectLogin = YES;
[returnString release];
return YES;
}
else
{
self.isCorrectLogin = NO;
[returnString release];
return NO;
}
return NO;
}
Please help me out of this problem.
Macbirdie is correct in that composing a string using stringByAppendingString: is horribly inefficient. I would suggest using +stringWithFormat: instead.
As for the leak, if the leak is where you say it is, then it might be a leak in the underlying framework (and it might be a false positive). Post the backtrace of the leaked object (which can be had from the Allocations instrument).