Memory leak when setting a UIImage in an NSOperation - ios

I'm having an issue where relatively large images never seem to get released from memory (1MB~5MB in size). This following block of code gets called while a user scrolls through a set of images. After about 15 images the application will crash. Sometimes "didReceiveMemoryWarning" gets called, and sometimes it doesn't--the application will just crash, stop, quit debugging, and not halt on any line of code--nothing. I assume this is what happens when the device runs out of memory? Another concern is that 'dealloc' never seems to get called for the subclassed 'DownloadImageOperation'. Any ideas?
Getting and setting the image:
//Calling this block of code multiple times will eventually cause the
// application to crash
//Memory monitor shows real memory jumping 5MB to 20MB increments in instruments.
//Allocations tool shows #living creeping up after this method is called.
//Leaks indicate something is leaking, but in the 1 to 5 kb increments. Nothing huge.
DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:imageFilePath];
[imageOp setCompletionBlock:^(void){
//Set the image in a UIImageView in the open UIViewController.
[self.ivScrollView setImage:imageOp.image];
}];
//Add operation to ivar NSOperationQueue
[mainImageQueue addOperation:imageOp];
[imageOp release];
DownloadImageOperation Definition:
.h file
#import <Foundation/Foundation.h>
#interface DownloadImageOperation : NSOperation {
UIImage * image;
NSString * downloadURL;
NSString * downloadFilename;
}
#property (retain) UIImage * image;
#property (copy) NSString * downloadURL;
#property (copy) NSString * downloadFilename;
- (id) initWithURL:(NSString *)url localPath:(NSString *)filename;
#end
.m file
#import "DownloadImageOperation.h"
#import "GetImage.h"
#implementation DownloadImageOperation
#synthesize image;
#synthesize downloadURL;
#synthesize downloadFilename;
- (id) initWithURL:(NSString *)url localPath:(NSString *)filename {
self = [super init];
if (self!= nil) {
[self setDownloadURL:url];
[self setDownloadFilename:filename];
[self setQueuePriority:NSOperationQueuePriorityHigh];
}
return self;
}
- (void)dealloc { //This never seems to get called?
[downloadURL release], downloadURL = nil;
[downloadFilename release], downloadFilename = nil;
[image release], image = nil;
[super dealloc];
}
-(void)main{
if (self.isCancelled) {
return;
}
UIImage * imageProperty = [[GetImage imageWithContentsOfFile:downloadFilename andURL:downloadURL] retain];
[self setImage:imageProperty];
[imageProperty release];
imageProperty = nil;
}
#end
Get Image Class
.m file
+ (UIImage *)imageWithContentsOfFile:(NSString *)path andURL:(NSString*)urlString foundFile:(BOOL*)fileFound {
BOOL boolRef;
UIImage *image = nil;
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
if (image==nil) {
boolRef = YES;
image = [UIImage imageWithContentsOfFile:[[AppDelegate applicationImagesDirectory] stringByAppendingPathComponent:[path lastPathComponent]]];
}
if (image==nil) {
boolRef = YES;
image = [super imageWithContentsOfFile:path];
}
if (image==nil) {
//Download image from the Internet
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setTimeOutSeconds:120];
[request startSynchronous];
NSData *responseData = [[request responseData] retain];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
NSData *rdat = [[NSData alloc] initWithData:responseData];
[responseData release];
NSError *imageDirError = nil;
NSArray *existing_images = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[path stringByDeletingLastPathComponent] error:&imageDirError];
if (existing_images == nil || [existing_images count] == 0) {
// create the image directory
[[NSFileManager defaultManager] createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:NO attributes:nil error:nil];
}
BOOL write_success = NO;
write_success = [rdat writeToFile:path atomically:YES];
if (write_success==NO) {
NSLog(#"Error writing file: %#",[path lastPathComponent]);
}
image = [UIImage imageWithData:rdat];
[rdat release];
}
return image;
}
Apologies for this huge block of code. I really have no idea where the problem might be here so I tried to be as inclusive as possible. Thanks for reading.

The main issue in the operation not getting deallocated is that you have a retain cycle, caused by the reference of imageOp in the completion block. Consider your code that says:
DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:imageFilePath];
[imageOp setCompletionBlock:^(void){
//Set the image in a UIImageView in the open UIViewController.
[self.ivScrollView setImage:imageOp.image];
}];
In ARC, you would add a __weak qualifier to the operation and use that rather than imageOp within the completionBlock, to avoid the strong reference cycle. In manual reference counting, you can avoid the retain cycle through the use the __block qualifier to achieve the same thing, namely to keep the block from retaining imageOp:
DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:filename];
__block DownloadImageOperation *blockImageOp = imageOp;
[imageOp setCompletionBlock:^(void){
//Set the image in a UIImageView in the open UIViewController.
[self.imageView setImage:blockImageOp.image];
}];
I think if you do that, you'll see your operation getting released correctly. (See the "Use Lifetime Qualifiers to Avoid Strong Reference Cycles" section of Transitioning to ARC Release Notes. I know you're not using ARC, but this section describes both the ARC and manual reference counting solutions.)
If you don't mind, I had other observations regarding your code:
You shouldn't be updating the UI from the completionBlock without dispatching it to the main queue ... all UI updates should happen on the main queue:
DownloadImageOperation * imageOp = [[DownloadImageOperation alloc] initWithURL:imageURL localPath:filename];
__block DownloadImageOperation *blockImageOp = imageOp;
[imageOp setCompletionBlock:^(void){
//Set the image in a UIImageView in the open UIViewController.
UIImage *image = blockImageOp.image;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.imageView setImage:image];
}];
}];
You're using accessor methods in your init method. As a matter of good practice, you really shouldn't. See Don’t Use Accessor Methods in Initializer Methods and dealloc in the Advanced Memory Management Programming Guide.
While we may have fixed the problem of the operation not getting released, I would suspect that unless you've already coded your UIScrollViewDelegate calls to release images that have scrolled off the visible screen, that you'll continue to have memory issues. Having said that, you may have already tackled this issue, and if so, I apologize for even mentioning it. I only raise the issue as it would just be easy to fix this NSOperation problem, but then neglect to have the scroll view release images as they scroll off the screen.
I'm not sure your subclassed NSOperation will support concurrency as you're missing some of the key methods discussed in Defining a Custom Operation in the Concurrency Programming Guide. Perhaps you have done this already, but just omitted it for brevity. Alternatively, I think it's easier if you use one of the existing NSOperation classes (e.g. NSBlockOperation) which take care of this stuff for you. Your call, but if you pursue concurrency, you'll want to make sure you set the queue's maxConcurrentOperationCount to something reasonable, like 4.
You code has some redundant retain statements. Having said that, you have the necessary release statements, too, so you've ensured that you won't have problems, but it's just a little curious. Clearly, ARC gets you out of the weeds on that sort of stuff, but I appreciate that this is a big step. But when you get a chance, take a look at ARC as it saves you from having to worry about a lot of this.
You should probably run your code through the static analyzer ("Analyze" on the "Product" menu), as you have some dead stores and the like.

Related

How to wait for delegate method execution before return statement?

I have a model object that has a class method that checks if the model object already exists, and if it does it returns it, or if it doesn't it creates it and then returns it. This class makes use of the VLC framework for generating data about video files and to generate a thumbnail. This is where I'm having trouble.
The VLCThumbnailer returns the thumbnail via a delegate method once it's fetchthumbnail method is called. The problem is that the delegate method doesn't get returned until AFTER my class-creation method reaches it's return function. Here's a code example.
-(AnimuProfile*)createnewProfileforFilename:(NSString*)filename{
NSURL *fileURL = [NSURL fileURLWithPath:filename];
VLCMedia *media = [VLCMedia mediaWithURL:fileURL];
FilenameParser *parser = [[FilenameParser alloc]init];
NSArray *parsedFilename = [parser parseFilename:[filename lastPathComponent]];
NSArray *mediaArray = [media tracksInformation];
if (mediaArray.count != 0) {
NSDictionary *videoTrackinfo = [mediaArray objectAtIndex:0];
_fansubGroup = parsedFilename[0];
_seriesTitle = parsedFilename[1];
_episodeNumber = parsedFilename[2];
_filename = [filename lastPathComponent];
_filepathURL = fileURL;
_filepathString = filename;
_watched = NO;
_progress = [VLCTime timeWithInt:0];
_length = [[media length]stringValue];
NSNumber *resolution = [videoTrackinfo valueForKey:#"height"];
_resolution = [NSString stringWithFormat:#"%#p",resolution];
VLCMediaThumbnailer *thumbnailer = [VLCMediaThumbnailer thumbnailerWithMedia:media andDelegate:self];
[thumbnailer fetchThumbnail];
NSString *libPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *profileName = [[_filename lastPathComponent] stringByAppendingPathExtension:#"prf"];
NSString *pathandProfileName = [libPath stringByAppendingPathComponent:profileName];
[NSKeyedArchiver archiveRootObject:self toFile:pathandProfileName];
return self;
}
And then the delegate methods:
#pragma mark VLC Thumbnailer delegate methods
- (void)mediaThumbnailerDidTimeOut:(VLCMediaThumbnailer *)mediaThumbnailerP{
NSLog(#"Thumbnailer timed out on file %#",_filename);
UIImage *filmstrip = [UIImage imageNamed:#"filmstrip"];
_thumbnail = UIImagePNGRepresentation(filmstrip);
}
- (void)mediaThumbnailer:(VLCMediaThumbnailer *)mediaThumbnailer didFinishThumbnail:(CGImageRef)thumbnail{
UIImage *image = [UIImage imageWithCGImage:thumbnail];
_thumbnail = UIImagePNGRepresentation(image);
}
I know it's a nono to lock the main thread waiting for the delegate method to be called so what should be done in this instance?
I know it's a nono to lock the main thread waiting for the delegate
method to be called so what should be done in this instance?
Those delegate methods are being called on VLC's video processing thread. They aren't the main thread and, therefore, you shouldn't be calling random UIKit API directly in the return blocks.
You need to process the results when they are available. If VLC were implemented using modern patterns, it would be using completion blocks. But it isn't, so...
- (void)mediaThumbnailer:(VLCMediaThumbnailer *)mediaThumbnailer didFinishThumbnail:(CGImageRef)thumbnail{
{
dispatch_async(dispatch_get_main_queue(), ^{ ... process thumbnail and update UI accordingly here ...});
}
That is, your createnewProfileforFilename: method should start the processing, but not expect it to be finished until sometime later. Then, when that sometime later happens, you trigger the updating of the UI with the data that was processed in the background.
And, as you state, you should never block the main queue/thread.
I was able to solve it by creating a separate class to be the delgate, make thumbnail fetch requests and then handle them.
#property NSMutableArray *queue;
#end
#implementation ThumbnailWaiter
+(id)sharedThumbnailWaiter{
static ThumbnailWaiter *singletonInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singletonInstance = [[self alloc] init];
});
return singletonInstance;
}
-(id)init{
self = [super init];
if (self) {
NSMutableArray *queue = [NSMutableArray array];
_queue = queue;
}
return self;
}
-(void)requestThumbnailForProfile:(AnimuProfile*)profile{
VLCMedia *media = [VLCMedia mediaWithURL:profile.filepathURL];
VLCMediaThumbnailer *thumbnailer = [VLCMediaThumbnailer thumbnailerWithMedia:media andDelegate:self];
[_queue addObject:profile];
[thumbnailer fetchThumbnail];
}
#pragma mark VLC Thumbnailer delegate methods
- (void)mediaThumbnailerDidTimeOut:(VLCMediaThumbnailer *)mediaThumbnailerP{
}
- (void)mediaThumbnailer:(VLCMediaThumbnailer *)mediaThumbnailer didFinishThumbnail:(CGImageRef)thumbnail{
UIImage *image = [UIImage imageWithCGImage:thumbnail];
AnimuProfile *profile = _queue.firstObject;
profile.thumbnail = UIImagePNGRepresentation(image);
[profile saveProfile];
[_queue removeObjectAtIndex:0];
}
Seems almost silly to have to do it this way but it seems to be working.

Displaying remote images in an UIImageview downloading progressively or like lazyloading [duplicate]

I have been trying to display large image from server, but I have to display it progressively.
I used subclass of UIView and in that I have taken UIImage object, in which I used NSURLConnection and its delegate methods, I also used
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
in which I am appending data and converting it to UIImage object, and drawing rect using the drawInRect: method of UIImage.
Everything is working fine, but the problem is, when image is being drawn on context, I cannot click anywhere else on screen until entire image is being drawn on to screen.
Is there any good solution, where I can click anywhere else even if image is being drawn on screen?
Any help will be appreciable.
Edit:
Is there any efficient way of drawing image blurry progressively in didReceiveData? so drawInRect does not take too much time to draw. Or If anyone has custom drawRect method which efficiently displays image progressively as data received in didReceiveData.
I have used NYXImagesKit for something similar, downloading images while not blocking the main thread and showing the image progressively. Ive written a really quick and dirty example to illustrate the basic workings. I load the image in a UITableview to show that it doesn't block the User Interface(Main Thread). You can scroll the tableview while the image is loading. Don't forget to add the correct Frameworks, there are a few. Heres the link to the project on Github:
https://github.com/HubertK/ProgressiveImageDownload
It's really easy to use,create a NYXProgressiveImageView object, set the URL and it will do all the work for you when you call:
loadImageAtURL:
It's a subclass of UIImageView, Works like magic! Here's a link to the developers site:
http://www.cocoaintheshell.com/2012/01/nyximageskit-class-nyxprogressiveimageview/
I suggest pulling the image data in an asynchronous manner and then applying a correction in order to obtain a valid conversion from partially downloaded NSData to an UIImage:
NSURLRequest *theRequest = [NSURLRequest requestWithURL:
[NSURL URLWithString: imageRequestString]
cachePolicy: NSURLRequestReloadIgnoringCacheData
timeoutInterval: 60.0];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest: theRequest
delegate: self];
if (theConnection)
receivedData = [[NSMutableData data] retain];
.......
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[receivedData appendData: data];
NSInvocationOperation *operation =
[[NSInvocationOperation alloc] initWithTarget: self
selector: #selector(loadPartialImage)
object: nil];
[[[NSOperationQueue alloc] init] autorelease] addOperation: operation];
[operation release];
}
- (void)loadPartialImage {
// This is where you would call the function that would "stitch up" your partial
// data and make it appropriate for use in UIImage's imageWithData
NSData *validPartialData =
[self validImageRepresentationFromPartialImageData: receivedData];
UIImage *partialImage = [UIImage imageWithData: validPartialData];
[imageView performSelectorOnMainThread: #selector(setImage:)
withObject: partialImage
waitUntilDone: NO];
}
+ (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
UIImage *fullImage = [UIImage imageWithData: receivedData];
imageView.image = fullImage;
}
Note that I did not provide the code for validImageRepresentationFromPartialImageData, as, at the moment, I have no clear, specific idea, on how to implement such a correction, or if the [UIImage imageWithData:] wouldn't actually accept partial data as input by default. As you can see, the coercion and UIImage creation would happen on a different thread, while the main thread would only display the updates as they come.
If you are receiving too frequent updates and they are still blocking the interface, you could:
a. Make the image requests on a different thread as well.
b. Reduce the frequency of the UIImageView's updates, by only calling setImage once in 10 or 100 updates, according to the zise of your image.
I usually use a really simple GCD pattern for async image loading:
Create a GCD queue in which you load the image data form your web server
Set the image data in your main queue
Example:
dispatch_queue_t image_queue = dispatch_queue_create("com.company.app.imageQueue", NULL);
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(image_queue, ^{
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[record imageURLString]];
dispatch_async(main_queue, ^{
[imageView setImage:[UIImage imageWithData:imageData]];
});
});
Probably didReceiveData is called too often! Just use a NSTimerand update the image regularly in 1-2second steps. That should work more efficiently.
Also you can use performSelectorInBackgroundto convert your NSData to an UIImage;
And then call performSelectorOnMainThreadto set the image into the UIImage View. So the converting stuff won't block the main thread.
Have you considered chopping up your images into smaller chunks on the server, then redrawing whenever a complete chunk has been received? This would give you control over the "progressiveness" of the load and the frequency of redraws by changing the chunk size. Not sure this is the kind of progressive load you're after, though.
If you have control of the server, split the image into tiles and also create a low res image. Display the low res version first in the lowest layer, and load the tiles on top drawing them as they load?
You can create a subclass of UIImageView with the URL of the image and a startDownload method.
It's a basic sample it must be improved.
#property (nonatomic, strong) NSURL *imageURL;
- (void)startDownload;
#implementation ImgeViewSubClass
{
NSURLConnection *connection;
NSMutableData *imageData;
}
The start download method:
- (void)startDownload
{
NSURLRequest *request = [NSURLRequest requestWithURL:imageURL];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
imageData = [NSMutableData data];
}
Delegate method from NSURLConnectionDataDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
#synchronized(imageData)
{
[imageData appendData:data];
}
// this part must be improved using CGImage instead of UIImage because we are not on main thread
UIImage *image = [UIImage imageWithData:imageData];
if (image) {
[self performSelectorOnMainThread:#selector(setImage:) withObject:image waitUntilDone:NO];
}
});
}
The Answer is in ImageIO.framework , its very simple actually
first you create a CGImageSourceRef mySource ,instantiate it using CGImageSourceCreateIncremental() .
setup and start an NSURLConnection with the image Url.
in connection:didReceiveData: , append the received data to your placeholder data , and update the image source by calling
CGImageSourceUpdateData(imageSource, (CFDataRef)imageData, NO);
then load the partially loaded part of the image to your UIImageView
self.image = [UIImage imageWithCGImage:CGImageSourceCreateImageAtIndex(imageSource, 0, nil)];
in connectionDidFinishLoading: finalise by calling
CGImageSourceUpdateData(imageSource, (CFDataRef)imageData, YES);
self.image = [UIImage imageWithCGImage:CGImageSourceCreateImageAtIndex(imageSource, 0, nil)];
CFRelease(imageSource);
imageData = nil;
here is a sample code i wrote :
https://github.com/mohammedDehairy/MDIncrementalImageView
Why don't you use ASIHTTPRequest request:
#import "ASIHTTPRequest.h"
This will help to load/draw in background, can perform other task too.
Try this one:
#import "ASIHTTPRequest.h"
[self performSelectorInBackground:#selector(DownLoadImageInBackground:)
withObject:YOUR IMAGE ARRAY];
-(void) DownLoadImageInBackground:(NSArray *)imgUrlArr1
{
NSURL * url = [Image URL];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
-(void)requestFailed:(ASIHTTPRequest *)request
{
NSLog(#"URL Fail : %#",request.url);
NSError *error = [request error];
// you can give here alert too..
}
-(void)requestFinished:(ASIHTTPRequest *)request
{
/////////// Drawing Code Here////////////////////
NSData *responseData = [request responseData];
UIImage *imgInBackground = [[UIImage alloc] initWithData:responseData];
[imageView setImage: imgInBackground];
}
I am not sure how the other parts of your code(reg this module) is implemented but give the following a try,
Try to use this selector with the run loop mode set to NSDefaultRunLoopMode
[self performSelectorOnMainThread:#selector(processImage:)
withObject:objParameters
waitUntillDone:NO
modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]]
This execution will free up your UI interactions, let me know if it helped please.
For more info : APPLE DOCS
//JImage.h
#import <Foundation/Foundation.h>
#interface JImage : UIImageView {
NSURLConnection *connection;
NSMutableData* data;
UIActivityIndicatorView *ai;
}
-(void)initWithImageAtURL:(NSURL*)url;
#property (nonatomic, retain) NSURLConnection *connection;
#property (nonatomic, retain) NSMutableData* data;
#property (nonatomic, retain) UIActivityIndicatorView *ai;
#end
//JImage.m
#import "JImage.h"
#implementation JImage
#synthesize ai,connection, data;
-(void)initWithImageAtURL:(NSURL*)url {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[self setContentMode:UIViewContentModeScaleToFill];
if (!ai){
[self setAi:[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]];
[ai startAnimating];
[ai setFrame:CGRectMake(27.5, 27.5, 20, 20)];
[ai setColor:[UIColor blackColor]];
[self addSubview:ai];
}
NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData {
if (data==nil)
data = [[NSMutableData alloc] initWithCapacity:5000];
[data appendData:incrementalData];
NSNumber *resourceLength = [NSNumber numberWithUnsignedInteger:[data length]];
NSLog(#"resourceData length: %d", [resourceLength intValue]);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"Connection error...");
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[ai removeFromSuperview];
}
- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[self setImage:[UIImage imageWithData: data]];
[ai removeFromSuperview];
}
#end
//Include the definition in your class where you want to use the image
-(UIImageView*)downloadImage:(NSURL*)url:(CGRect)frame {
JImage *photoImage=[[JImage alloc] init];
photoImage.backgroundColor = [UIColor clearColor];
[photoImage setFrame:frame];
[photoImage setContentMode:UIViewContentModeScaleToFill];
[photoImage initWithImageAtURL:url];
return photoImage;
}
//call the function
UIImageView *imagV=[self downloadImage:url :rect];
//you can call the downloadImage function in looping statement and subview the returned imageview.
//it will help you in lazy loading of images.
//Hope this will help

NSURLConnection sendAsynchronousRequest never free up the memory

I am working on an iOS app which dispatch quite a number of tasks to my serial queue. The task is to download images from my web server, save it to disk, and later displayed on UIImageView. However, [NSURLConnection sendAsynchrousRequest] will keep eating up more and more memory until iOS kill my process.
The downloader method looks like this:
// dispatch_queue_t is created once by: m_pRequestQueue = dispatch_queue_create( "mynamespace.app", DISPATCH_QUEUE_SERIAL);
- (void) downloadImageInBackgroundWithURL:(NSString*) szUrl {
__block typeof(self) bSelf = self;
__block typeof(m_pUrlRequestQueue) bpUrlRequestQueue = m_pRequestQueue;
dispatch_async( m_pRequestQueue, ^{
NSAutoreleasePool *pAutoreleasePool = [[NSAutoreleasePool alloc] init];
NSURLRequest *pRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:szUrl]
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:URL_REQUEST_TIMEOUT];
[NSURLConnection sendAsynchronousRequest:pRequest queue:bpUrlRequestQueue completionHandler:^(NSURLResponse *pResponse, NSData *pData, NSError *pError) {
NSAutoreleasePool *pPool = [[NSAutoreleasePool alloc] init];
if ( pError != nil ) {
} else {
// convert image to png format
UIImage *pImg = [UIImage imageWithData:pData];
NSData *pDataPng = UIImagePNGRepresentation(pImg);
bool bSaved = [[NSFileManager defaultManager] createFileAtPath:szCacheFile contents:pDataPng attributes:nil];
}
__block typeof(pDataPng) bpDataPng = pDataPng;
__block typeof(pError) bpError = pError;
dispatch_sync( dispatch_get_main_queue(), ^ {
NSAutoreleasePool *autoreleasepool = [[NSAutoreleasePool alloc] init];
UIImage *pImage = [[UIImage alloc] initWithData:bpDataPng];
// display the image
[pImage release];
// NSLog( #"image retain count: %d", [pImage retainCount] ); // 0, bad access
[autoreleasepool drain];
});
}
[pPool drain];
}]; // end sendAsynchronousRequest
[pAutoreleasePool drain];
}); // end dispatch_async
} // end downloadImageInBackgroundWithURL
I am quite sure it is something inside [NSURLConnection sendAsynchronousRequest] as the profiler is showing that the function is the one eating up all the memory...
However, I am also not very sure about the dispatch_*** and block things, I've always used C and C++ code with pthread before, but after reading from Apple's documentation on migrating away from thread, I decided to give GCD a try, objective-c is so troublesome and I'm not sure how to release the NSData *pData and NSURLResponse *pResponse as it crash whenever I do it.
Please advice... really need help to learn and appreciate objective-c...
ADDITIONAL EDIT:
Thanks to #robhayward, I put the pImg and pDataPng outside as __block variable, use his RHCacheImageView way of downloading data ( NSData initWithContentOfURL )
Thanks as well to #JorisKluivers, the first UIImage can actually be reused to display as UIImageView recognized both jpg and png format, just my later processing requires png format and I am reading from the disk later just when required
I would firstly put it down to the image and data objects that you are creating:
UIImage *pImg = [UIImage imageWithData:pData];
NSData *pDataPng = UIImagePNGRepresentation(pImg);
Which might be hanging around too long, perhaps put them outside the block, as they are probably being created/released on different threads:
__block UIImage *pImg = nil;
__block NSData *pDataPng = nil;
[NSURLConnection sendAsynchronousRequest..
(Also consider using ARC if you can)
I have some code on Github that does a similar job without this issue, feel free to check it out:
https://github.com/robinhayward/RHCache/blob/master/RHCache/RHCache/Helpers/UIImageView/RHCacheImageView.m
First of all try simplifying your code. Things I did:
Remove the outer dispatch_async. This is not needed, your sendAsynchronousRequest is async already. This also removes the need another __block variable on the queue.
You create an image named pImg from the received pData, then convert that back to NSData of type png, and later create another image pImage from that again. Instead of converting over and over, just reuse the first image. You could even write the original pData to disk (unless you really want the png format on disk).
I didn't compile the code below myself, so it might contain a few mistakes. But it is a simpler version that might help solve the leak.
- (void) downloadImageInBackgroundWithURL:(NSString*)szUrl
{
__block typeof(self) bSelf = self;
NSAutoreleasePool *pAutoreleasePool = [[NSAutoreleasePool alloc] init];
NSURLRequest *pRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:szUrl]
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:URL_REQUEST_TIMEOUT];
[NSURLConnection sendAsynchronousRequest:pRequest queue:m_pRequestQueue completionHandler:^(NSURLResponse *pResponse, NSData *pData, NSError *pError) {
NSAutoreleasePool *pPool = [[NSAutoreleasePool alloc] init];
if (pError) {
// TODO: handle error
return;
}
// convert image to png format
__block UIImage *pImg = [UIImage imageWithData:pData];
// possibly just write pData to disk
NSData *pDataPng = UIImagePNGRepresentation(pImg);
bool bSaved = [[NSFileManager defaultManager] createFileAtPath:szCacheFile contents:pDataPng attributes:nil];
dispatch_sync( dispatch_get_main_queue(), ^ {
// display the image in var pImg
});
}];
[pAutoreleasePool drain];
}

Memory leak while using performSelectorInBackground

I am new to iOS development. I am facing problems while running a background thread. In my code resetUi is running on main UI thread, Now I am starting a background thread to fetch an image data and update my image. Everything works fine, but memory is leaking when call is made to performSelectorInBackground.
Please let me know where I am doing wrong. Also please suggest if there is a better way to update my image while fetching from an URL(dataWithContentsOfURL).
[Update]
Instrument is displaying 2 separate leaks one at perfromSelectorInBackground and other at UIImage imageWithData. I guess something is going terribly wrong with imageupdate(imageWithData)
-(void)updateData{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
profileName.text = oAuthTwitter.screen_name;
if(profilePic.image == nil){
NSString *urlString = #"https://api.twitter.com/1/users/profile_image/";
urlString = [urlString stringByAppendingFormat:oAuthTwitter.screen_name];
urlString = [urlString stringByAppendingFormat:#"?size=bigger"];
profilePic.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]]];
[activityIndicator stopAnimating];
[activityIndicator release];
}
[pool drain];
}
- (void)resetUi{
if (oAuthTwitter.oauth_token_authorized) {
profilePic.hidden = NO;
profileName.hidden = NO;
NSLog(#"Resetting to authorised state");
[self performSelectorInBackground:#selector(updateData) withObject:nil];
}else{
NSLog(#"Resetting Twitter UI to non-authorized state.");
profilePic.hidden = YES;
profileName.hidden = YES;
}
}
I think you should use
[pool release];
rather than
[pool drain];
which is far more better practice.
Also can you try to release activityIndicator in the main thread?
From the code you have given I can't find any other cause for leak.. Have you tried to run your code using leak instrument and static analyzer?

I want to give a priority each background thread and excute gradually

This code generates Low memory warning because background 5 thread is almostly loading images in same time.
I want to give a priority each thread and make lock & unlock.
I would make step by step thread. image 1 loading -> image2 loading -> image3 loading -> image4 loading.
How can I do this?
viewcontroller
-(void)viewDidLoad
{
for(int i=0; i<screenshotcount ; i++)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString * url=[detailItem.mScreenshot objectAtIndex:i];
NSDictionary *args=[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:i], #"screenNum",
[NSString stringWithString:url],#"url",
nil];
[self performSelectorInBackground:#selector(loadImageScreenshot:) withObject:args];
[pool release];
}
}
loading image
-(void) loadImageScreenshot:(NSDictionary *) args
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage * screenshotImage=[UIImage imageWithStringURL:url];
NSDictionary *args2=[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:num], #"screenNum",
screenshotImage,#"image",
nil];
[self performSelectorOnMainThread:#selector(assignImageToScreenshotImageView:) withObject:args2 waitUntilDone:YES];
[pool release];
}
image add
- (void) assignImageToScreenshotImageView:(NSDictionary *)arg
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage * image= [arg objectForKey:#"image"];
UIImageView *imageview=[UIImageView alloc]init];
.
.
imageview.image=image;
[self.mScreenshotSpace addSubview:imageview];
[imageview release];
[pool release];
}
image from url
+(UIImage *)imageWithStringURL:(NSString *)strURL
{
NSURL *url =[NSURL URLWithString:strURL];
NSData * data=[[NSData alloc]initWithContentsOfURL:url options:NSDataReadingUncached error:&error];
UIImage * image=[UIImage imageWithData:data ];
[data release];
return image;
}
maybe I misunderstood your question, but from what you said, what you really want is to "serialise" the threads, that is, ensure they are executed one after the other. If this is the case I do not see a big advantage in having five (or more) threads if they spend most of the time waiting in a sort of "thread queue" :)
My 2 cents: Instead of playing with the threads' priority, maybe you should think about re-design the code to have a queue of files/images to load and a thread that dequeue and load the images one after the other. (the classic consumer/producer scenario)
If you need to speed up things you might think to have another thread doing some pre-fetching (if that make sense in your sw design/architecture)
CiaoCiao
Sergio
Sergio is right - if you can look at GCD and serialized queues, it shouldn't be too hard to migrate to it. Take a look at the videos from Apple for good how-to steps.

Resources