I am getting the following memory leak when using NSURL. I use this method in quite a few different places and receive memory leaks all the time using the Leaks instruments.
Object Management:
self.objManager = [[HJObjManager alloc] init];
NSString *cacheDirectory = [NSHomeDirectory() stringByAppendingPathComponent:#"/Library/Caches/App"];
HJMOFileCache *fileCache = [[[HJMOFileCache alloc] initWithRootPath:cacheDirectory] autorelease];
self.objManager.fileCache = fileCache;
fileCache.fileCountLimit = 100;
fileCache.fileAgeLimit = 60*60*24;
[fileCache trimCacheUsingBackgroundThread];
Where it's used:
HJManagedImageV *mi = [[[HJManagedImageV alloc] initWithFrame:CGRectMake(self.myHeaderView.profilePictureImageView.bounds.origin.x,
self.myHeaderView.profilePictureImageView.bounds.origin.y,
self.myHeaderView.profilePictureImageView.bounds.size.width,
self.myHeaderView.profilePictureImageView.bounds.size.height)] autorelease];
mi.layer.masksToBounds = YES;
mi.layer.cornerRadius = 8.0;
mi.layer.borderColor = [[UIColor blackColor] CGColor];
mi.layer.borderWidth = 1.0;
mi.url = [NSURL URLWithString:profilePictureUrl];
[mi showLoadingWheel];
[self.myHeaderView.profilePictureImageView addSubview:mi];
[self.objManager manage:mi];
Dealloc:
- (void)viewDidUnload {
self.tv = nil;
self.friends = nil;
self.sortedFriends = nil;
self.detailView = nil;
self.profileView = nil;
self.objManager = nil;
[super viewDidUnload];
}
- (void)dealloc {
[tv release];
[friends release];
[sortedFriends release];
[detailView release];
[profileView release];
[objManager release];
[super dealloc];
}
You can use stack trace to determine the place of leak.
Edit:
make sure you are releasing the mi.url in dealloc method:
-(void) dealloc {
//some other releases
self.url = nil;
[super dealloc];
}
Related
I have a little problem with memory management in my iOS App. I load an XML and set all Values in this XML to Spezific Objects. Now my problem is when i reload the XML every 15 - 20 reloads of this XML my app Crash on Parsing here is a sample of my parser.
EDIT: Here ist the ERROR when NSZombie is Enabled if NSZombie is disabled I didn't get an ERROR message.
-[CFNumber retain]: message sent to deallocated instance
thanks for help.
EDIT:
the beginning of my Code:
- (id)init
{
self = [super init];
if (self) {
TheObjects *theObjects = [[TheObjects alloc] init];
[self parse];
}
return self;
}
- (void) reload{
reload = YES;
TheObjects *theTmpObjects = [[TheObjects alloc] init];
[self parse];
}
- (void)parse{
for (id xmlOBject in xmlObjects){
MyObject *object = [[MyObject alloc] init];
object.number1 = [NSNumber numberWithInt:1];
object.number2 = [NSNumber numberWithInt:2];
object.number3 = [NSNumber numberWithInt:3];
if (reload)
[theTmpObjects.objects addObject:object];
else
[theObjects.objects addObject:object];
[object release];
}
//later in my code
TheObjects *localTheTmpObjects = nil;
if (reload){
localTheTmpObjects = theObjects;
theObjects = theTmpObjects;
}
if ([delegate respondsToSelector:#selector(finished:)]){
[delegate performSelector:#selector(finished:) withObject:theObjects];
}
if(reload)
[localTheTmpObjects release];
}
remove the line [localTheTmpObjects release]
you don't own the object
at the end, call the `[localTheTmpObjects autorelease];`
this is because if you release array, all its objects are released and hence may cause crash, when your array may in use
- (id)init
{
self = [super init];
if (self) {
TheObjects *obbjects = [[TheObjects alloc] init];
theObjects = objects;
[objects releas];
[self parse];
}
return self;
}
- (void) reload{
reload = YES;
TheObjects *obbjects = [[TheObjects alloc] init];
thetmpObjects = objects;
[objects releas]; [self parse];
}
- (void)parse{
for (id xmlOBject in xmlObjects){
MyObject *object = [[MyObject alloc] init];
object.number1 = [NSNumber numberWithInt:1];
object.number2 = [NSNumber numberWithInt:2];
object.number3 = [NSNumber numberWithInt:3];
if (reload)
[theTmpObjects.objects addObject:object];
else
[theObjects.objects addObject:object];
[object release];
}
//later in my code
TheObjects *localTheTmpObjects = nil;
if (reload){
localTheTmpObjects = theObjects;
theObjects = theTmpObjects;
}
if ([delegate respondsToSelector:#selector(finished:)]){
[delegate performSelector:#selector(finished:) withObject:theObjects];
}
}
i got code like this
#import "UIWebImageView.h"
#interface UIWebImageView (hiddenMethods)
- (void) initDefaults;
#end
#implementation UIWebImageView (hiddenMethods)
- (void) initDefaults
{
self.showActivityIndicator = NO;
self.activityIndicatorStyle = UIActivityIndicatorViewStyleWhite;
self.activityIndicatorSize = CGSizeMake(20.0, 20.0);
//data = [[NSMutableData alloc] init];
}
#end
#implementation UIWebImageView
#synthesize showActivityIndicator;
#synthesize activityIndicatorStyle;
#synthesize activityIndicatorSize;
- (id) init
{
if (self = [super init])
{
[self initDefaults];
}
return self;
}
- (void) loadFromURL:(NSString *) url
{
[self.image release];
self.image = nil;
request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:30.0];
if (connection == nil)
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
// activity indicator start
indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:activityIndicatorStyle];
indicator.frame = CGRectMake((self.frame.size.width - activityIndicatorSize.width)/2, (self.frame.size.height - activityIndicatorSize.height)/2,
activityIndicatorSize.width, activityIndicatorSize.height);
[self addSubview:indicator];
[indicator startAnimating];
}
- (void) dealloc
{
[data release];
[indicator release];
[connection release];
[super dealloc];
}
#pragma mark -
#pragma mark connection delegate
- (void) connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData
{
if (data == nil)
data = [[NSMutableData alloc] initWithCapacity:2048];
[data appendData:incrementalData];
}
- (void) connectionDidFinishLoading:(NSURLConnection *)theConnection
{
self.image = [UIImage imageWithData:data];
[indicator removeFromSuperview];
//data = nil;
[data release], data = nil;
[connection release], connection = nil;
[indicator release], indicator = nil;
}
- (void) connection:(NSURLConnection *)theConnection didFailWithError:(NSError *)error
{
self.image = [UIImage imageNamed:#"icon.jpg"];
[indicator removeFromSuperview];
//data = nil;
[data release], data = nil;
[connection release], connection = nil;
[indicator release], indicator = nil;
}
I'm using this for downloading images from web. one image for every cell in in table. and it works fine when image is not going out of the screen. BUT when u scrolling tableView fast and some images did not finished loading while they are on screen there huge memory leaks.
i know where is the leak and why its leaking. but i can't find solution.
any thoughts ?
thank you
PS sorry for my english
UPDATE
here is a code for adding images into tableView
UIWebImageView *tmpImageView = [[UIWebImageView alloc] initWithFrame:CGRectMake(0, 0, 57, 76)];
tmpImageView.showActivityIndicator = YES;
tmpImageView.contentMode = UIViewContentModeScaleAspectFit;
tmpImageView.activityIndicatorStyle = UIActivityIndicatorViewStyleGray;
[tmpImageView loadFromURL:[[tableArray objectAtIndex:indexPath.row] objectForKey:#"picurl"]];
[cell addSubview:tmpImageView];
[tmpImageView release];
and I'm repeating = ) its leaking only when it not finished loading and went off the screen while scrolling
The leaks are happening due to the allocation of UIWebImageView objects recursively (considering you are using reusable cells).
you should change your code to:
UIWebImageView *tmpImageView = [cell viewWithTag:2011];
if(!tmpImageView)
{
tmpImageView = [[UIWebImageView alloc] initWithFrame:CGRectMake(0, 0, 57, 76)];
tmpImageView.showActivityIndicator = YES;
tmpImageView.tag = 2011;
tmpImageView.contentMode = UIViewContentModeScaleAspectFit;
tmpImageView.activityIndicatorStyle = UIActivityIndicatorViewStyleGray;
[cell addSubview:tmpImageView];
[tmpImageView release];
}
[tmpImageView loadFromURL:[[tableArray objectAtIndex:indexPath.row] objectForKey:#"picurl"]];
you have to handle for the showActivityIndicator thing some how...based upon your code but the above mentioned change will remove your memory leaks.
Autorelase your NSURLConnection with this syntax
connection = [NSURLConnection connectionWithRequest:request delegate:self];
and delete your [connection release], connection = nil;
Incorrect decrement of the reference count of an object that is not owned at this point by the caller on iPhone. It is happening with NSString which I clearly init and release within the for loop. I have tried to do the same as an autoreleases string but I get leaks. I assume the culprit is the stringbytrimming call. Any suggestions, by the way this does not leak, but I get the warning in build and analyze. Everything also works fine and the app does not crash.
for(int i=0;i<storyQuantity;i++) {
NSString *imageString = [[NSString alloc] init];
imageString = [[[storiesArray objectAtIndex:i] objectForKey: #"image"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; // must add trimming to remove characters
imageLoader *imageOperation = [[imageLoader alloc] initWithImageURL:imageString target:self action:#selector(didImageLoad:) number:i];
AppDelegate_iPad *appDelegate = [[UIApplication sharedApplication] delegate];
[appDelegate.queue_ addOperation:imageOperation];
[imageOperation release];
[imageString release];
}
UPDATE - added my imageLoader class, which to the best of my knowledge does not have a leak
- (id)initWithImageURL:(NSString *)url target:(id)target action:(SEL)action number:(int)number {
if(self = [super init]) {
_action = action;
_target = target;
_number = number;
if(url == nil) {
return nil;
} else {
_imgURL = [[NSURL alloc] initWithString:[url copy]];
}
}
return self;
}
- (id)main {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
if ([self isCancelled]) {
NSLog(#"OPERATION CANCELLED");
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[pool drain];
return nil;
} else {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSData *imgData = [[NSData alloc] initWithContentsOfURL:_imgURL];
UIImage *image = [[UIImage alloc] initWithData:imgData];
[imgData release];
if ([self isCancelled]) {
NSLog(#"OPERATION CANCELLED");
[image release];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[pool drain];
return nil;
} else {
NSNumber *tempNumber = [NSNumber numberWithInt:_number];
NSDictionary *tempDict = [NSDictionary dictionaryWithObjectsAndKeys:tempNumber, #"number", image, #"image", nil];
[image release];
if([_target respondsToSelector:_action])
[_target performSelectorOnMainThread:_action withObject:tempDict waitUntilDone:NO];
}
}
[pool drain];
return nil;
}
- (void)dealloc {
[_imgURL release];
[super dealloc];
}
Since you are reassigning the imageString variable, the reference to the original object is lost. Why allocate an empty string anyway? Just change the code to
NSString *imageString = [[[storiesArray objectAtIndex:i] objectForKey: #"image"]
stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
and remove the [imageString release] and you're good to go.
Don't track reference counts as a way into understanding memory management. It's only going to confuse you. Things manipulate your objects' reference counts from deep in the framework, and if you watch those numbers jump around for (apparently) no reason, you'll just go insane and post a series of increasingly crazy questions here, which we'll then have to deal with. Believe me--we've seen it before.
So just ignore the reference count number, and make sure you're retaining and releasing objects properly.
the image that is being displayed in this code is leaking but I cant figure out how. What I have a tableview that displays images to be displayed. Each time a user selects an image, it should remove the old image, download a new one, then add it to the scroll view. But the old image is not being released and I cant figure out why...
-(void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[imageView removeFromSuperview];
self.imageView = nil;
NSUInteger row = [indexPath row];
NSString *tempC = [[NSString alloc]initWithFormat:#"http://www.website.com/%#_0001.jpg",[pdfNamesFinalArray objectAtIndex:row] ];
chartFileName = tempC;
pdfName = [pdfNamesFinalArray objectAtIndex:row];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *tempString = [[[NSString alloc]initWithFormat:#"%#/%#.jpg",docsPath,pdfName]autorelease];
NSData *data = [NSData dataWithContentsOfFile:tempString];
if (data != NULL){
self.imageView = nil;
[imageView removeFromSuperview];
self.imageView = nil;
UIImageView *tempImage = [[[UIImageView alloc]initWithImage:[UIImage imageWithData:data]]autorelease];
self.imageView = tempImage;
[data release];
scrollView.contentSize = CGSizeMake(imageView.frame.size.width , imageView.frame.size.height);
scrollView.maximumZoomScale = 1;
scrollView.minimumZoomScale = .6;
scrollView.clipsToBounds = YES;
scrollView.delegate = self;
[scrollView addSubview:imageView];
scrollView.zoomScale = .37;
}
else {
[data release];
self.imageView = nil;
[imageView removeFromSuperview];
self.imageView = nil;
activityIndicator.hidden = NO;
getChartsButton.enabled = NO;
chartListButton.enabled = NO;
saveChartButton.enabled = NO;
[NSThread detachNewThreadSelector:#selector(downloadImages) toTarget:self withObject:nil];
}
chartPanel.hidden = YES;
}
-(void) downloadImages {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
self.imageView = nil;
[imageView removeFromSuperview];
NSURL *url = [[[NSURL alloc]initWithString:chartFileName]autorelease];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImageView *tempImage = [[[UIImageView alloc]initWithImage:[UIImage imageWithData:data]]autorelease];
self.imageView = tempImage;
tempImage = nil;
scrollView.contentSize = CGSizeMake(imageView.frame.size.width , imageView.frame.size.height);
scrollView.maximumZoomScale = 1;
scrollView.minimumZoomScale = .37;
scrollView.clipsToBounds = YES;
scrollView.delegate = self;
[scrollView addSubview:imageView];
scrollView.zoomScale = .6;
activityIndicator.hidden = YES;
getChartsButton.enabled = YES;
chartListButton.enabled = YES;
saveChartButton.enabled = YES;
[pool drain];
[pool release];
}
This looks wrong:
self.imageView = nil;
[imageView removeFromSuperview];
You are setting imageView to nil before removing it from the superview, so the 2nd statement is really just [nil removeFromSuperview];, which isn't going to do anything.
I think the leak is what David Gelhar said, but I just wanted to add that you shouldn't access UI stuff from threads other than the main thread (so for instance, don't do [imageView removeFromSuperview] under a separate thread). This can cause very odd problems, including mysterious leaks. Try putting all that stuff in a separate method on the main thread that you call with [self performSelectorOnMainThread:] and see if it still leaks.
Also (although this wouldn't cause the leak), [pool drain] releases the autorelease pool, so you shouldn't invoke [pool release] after it--it might release the pool on the main thread, possibly causing a crash somewhere down the line (since you could over-release the pool).
I'm hoping someone can help me with this. I'm struggling to find an answer to what should be an easy question. By the way, this is my first major obj-c project after years of using c and c# so feel free to point out things I'm failing on.
I have an iPhone app designed to load an album of photos into a UIScrollView. It also has a random image function which uses the same process but only displays a single image. It works like so:
Read an external XML feed (stored on ruby-on-rails website) which contains a path to a random url of photo once parsed.
Content of URL is downloaded using NSURLConnection to NSData.
ScrollView is created and pushed
Subclassed UIView allocs an UIImageView, allocs a UIImage with the NSData which, inits the UIImageView with the UIimage and finally adds the imageview to the UIView.
The parent view then adds the UIView to the UIScrollView which is then pushed to the front.
This process occurs again when when the next random image is required. It also uses the same process when displaying an entire album of images except several UIViews are added to the UIScrollView.
The problem is, despite using release and delloc where appropriate, the activity tool indicates that the memory used by NSURLConnection and UIImage isn't being released from memory when the next image is requested. This is further proven by building the app to the iPhone. Requesting several images in a row causes the app the crash presumably from memory consumption.
Below is some of the code as I'm unable to post the entire project due to contractual agreements.
URLDownload class (uses DataDownload)
#implementation URLDownload
-(NSData *)GetURL:(NSURL *)strURL
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
DataDownload *request = [DataDownload alloc];
request.strURL = strURL;
[request init];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
while (request.isLoading && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
[pool release];
return [request urlData];
[request release];
}
DataDownload class
#implementation DataDownload
#synthesize urlData, connection, strURL, isLoading;
- (id)init
{
if(self = [super init])
{
self.isLoading = YES;
NSURLRequest *dataRequest = [NSURLRequest requestWithURL:self.strURL
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:60];
/* establish the connection */
self.connection = [[NSURLConnection alloc]
initWithRequest:dataRequest
delegate:self
];
if (connection == nil) {
self.urlData = nil;
} else {
self.urlData = [NSMutableData data];
}
}
return self;
}
- (void)dealloc {
[connection cancel];
[connection release];
[urlData release];
[strURL release];
[super dealloc];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
return nil;
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse*)response
{
[urlData setLength:0];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData*)incrementalData
{
[self.urlData appendData:incrementalData];
}
-(void)connectionDidFinishLoading:(NSURLConnection*)connection
{
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
self.isLoading = NO;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
self.isLoading = NO;
}
#end
ImageView subclass
#implementation ImageView
#synthesize strURL, imvImageView, image, currentlyRotated, imgOptionsView, startDate;
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.imvImageView = [UIImageView alloc];
if (self.strURL != nil){
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
NSData *datImageData = [NSData dataWithContentsOfURL: [NSURL URLWithString:self.strURL]];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
self.image = [UIImage imageWithData: datImageData];
CGSize imgSize = image.size;
CGFloat fltWidth = imgSize.width;
CGFloat fltHeight = imgSize.height;
[imvImageView initWithImage:image];
// If landscape rotate image
if (fltWidth > fltHeight){
imvImageView.frame = CGRectMake(-80.0, 80.0, 480.0, 320.0);
CGAffineTransform rotate = CGAffineTransformMakeRotation(-1.57079633);
[imvImageView setTransform:rotate];
self.currentlyRotated = YES;
}else{
imvImageView.frame = CGRectMake(0.0, 0.0, 320.0, 480.0);
self.currentlyRotated = NO;
}
[image release];
}else{
self.image = [UIImage alloc];
[imvImageView initWithImage:image];
[image release];
}
[self addSubview:imvImageView];
}
[imvImageView release];
return self;
}
- (void)drawRect:(CGRect)rect {
// Drawing code
}
- (void)dealloc {
[strURL release];
[imvImageView release];
[image release];
[imgOptionsView release];
[startDate release];
[super dealloc];
}
Sample code of how images are being displayed
- (void)displayImage:(NSString *)strURL {
NSString *strFullURL = #"http://www.marklatham.co.uk";
strFullURL = [strFullURL stringByAppendingString:strURL];
self.scroller = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 480.0)];
[scroller setContentSize:CGSizeMake(320.0, 540.0)];
[self.view addSubview:scroller];
CGRect rect = CGRectMake(0.0, 0.0, 320.0, 480.0);
self.imageView = [ImageView alloc];
imageView.strURL = strFullURL;
[imageView initWithFrame:rect];
[scroller addSubview:imageView];
[scroller release];
[imageView release];
}
The following images show all allocations to illustrate what's happening. 1 shows alloc on startup, 2 shows after first image is loaded and 3 after after second image.
http://www.gretanova.com/misc/iphone/1.png & 2.png & 3.png
Thanks everyone,
Lee
A couple of things stick out. For example within the URLDownload class the call to [request release] will never be reached as its placed after the return statement
return [request urlData];
[request release];