Why is NSURLSession dataTaskWithURL significantly slower vs NSData dataWithContentsOfURL? - ios

I'm trying to download a 94KB image file from my server. I've tried two ways to do this: using NSURLSession dataTaskWithURL and NSData dataWithContentsOfURL.
NSURLSession:
Globals *global = [Globals getInstance];
// GET request to /mobile/image
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/mobile/image/%#", [global rootUrl], self.photoId]];
NSURLSession * session = [global session];
NSDate *start = [NSDate date];
[[session dataTaskWithURL:url completionHandler:^(NSData *imgData, NSURLResponse *response, NSError *error) {
NSLog(#"Time taken: %f", [[NSDate date] timeIntervalSinceDate:start]);
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
if ([httpResponse statusCode] != 200) {
// error
} else {
dispatch_async(dispatch_get_main_queue(), ^ {
UIImage *image = [UIImage imageWithData:imgData];
CGFloat width = self.photoImageView.frame.size.width;
CGFloat height = image.size.height / image.size.width * width;
self.imageHeight.constant = height;
self.photoImageView.image = image;
});
}
}] resume];
NSData:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
Globals *global = [Globals getInstance];
// GET request to /mobile/image
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/mobile/image/%#", [global rootUrl], self.photoId]];
NSDate *start = [NSDate date];
NSData *imgData = [NSData dataWithContentsOfURL:url];
NSLog(#"Time taken: %f", [[NSDate date] timeIntervalSinceDate:start]);
UIImage *image = [UIImage imageWithData:imgData];
CGFloat width = self.photoImageView.frame.size.width;
CGFloat height = image.size.height / image.size.width * width;
dispatch_async(dispatch_get_main_queue(), ^{
self.imageHeight.constant = height;
self.photoImageView.image = image;
});
});
Here is the output for my NSLog for how long they took to complete:
NSURLSession:
Time taken: 6.585221
Time taken: 3.619189
Time taken: 4.408179
Time taken: 9.931350
Time taken: 3.689192
NSData:
Time taken: 0.157747
Time taken: 0.135785
Time taken: 0.576947
Time taken: 0.462661
Time taken: 0.337266
Taking 3~10 seconds to download a simple <100KB image file is unacceptable, so I'm currently just sticking with NSData, but I need to use NSURLSession later for another similar image download task because I need login sessions.
I'm wondering if I've accidentally discovered a weird bug with NSURLSession, if it's just supposed to be much slower because it has all the overhead of sessions, or if I'm doing something wrong.
Edit:
I figured it out, thanks for all your help!
I created a whole new project with all the connections of this one, and it performed fine.
Problem: The previous VC that before this was calling dataTask multiple times. I was completely baffled why that would affect this VC. Turned out that the class was called ViewController (default name), and this VC was inheriting ViewController instead of UIViewController.
Therefore, calling [super viewDidLoad] from the viewDidLoad of this ViewController was actually calling the previous VC and running all those dataTasks all over again.
When in doubt, check all the other connections in the session.

You're having a threading problem. Do not run that code (the NSURLSession code) in a background thread! Run it on the main thread. Don't worry, this won't block the main thread; the whole point of NSURLSession is that it operates asynchronously so that you don't have to.

Related

Need to download 50,000 images or more

Currently, i am downloading multiple images with below code. In My app need to download 50,000 images or more.
When memory consumption reaches to 600 MB application crash due to memory pressure.
When I download 10K images its working fine.But when I want to download 20K images from a server then after downloading 9-10K images application crash due to memory pressure.
I also try to Instrument to find memory leak but there is no memory leak.
In Debug Session (When download 20K images) :
Memory Consumption : 500-600 MB and After that its crash.
CPU Usage : 130 -160%
Can you please help me what I did wrong in my code?
- (void)Downloadimages:(NSMutableArray *)aMutArray
{
//newchange
SDImageCache *imageCache = [SDImageCache sharedImageCache];
[imageCache clearMemory];
[imageCache clearDisk];
// NSLog(#"DocumentsDirectory Path : %#", DocumentsDirectory);
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] init];
static AFURLSessionManager *sessionManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionManager = [[AFURLSessionManager alloc] initWithSessionConfiguration:sessionConfiguration];
});
__block int iCounter = 0;
for (NSInteger aIndex = 0; aIndex < aMutArray.count; aIndex++)
{
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:aMutArray[aIndex]] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:(aMutArray.count * 120)];
// urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:aMutArray[aIndex]] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:100];
//NSLog(#"%#",urlRequest);
// [urlRequest setTimeoutInterval:(aMutArray.count * 120)];
NSURLSessionDownloadTask *downloadTask = [sessionManager downloadTaskWithRequest:req progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response)
{
return [NSURL fileURLWithPath:[DocumentsDirectory stringByAppendingPathComponent:urlRequest.URL.lastPathComponent]];
}
completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error)
{
iCounter ++;
if (!error){
// NSLog(#"(%ld) SUCCESS : %#",(long)aIndex, aMutArray[aIndex]);
}
else
{
NSLog(#"(%ld) ERROR : %#",(long)aIndex, aMutArray[aIndex]);
// [CommonMethod DeleteImageWithName:filePath.lastPathComponent];
}
[labelSyncProducts setText:[NSString stringWithFormat:#"Syncing Images %d of %lu", iCounter, (unsigned long)aMutArray.count]];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]];
if (aMutArray.count == iCounter)
{
[self controlsEnableDisable:true];
}
}];
[downloadTask resume];
}
}
I will suggest you not to download this many images in the app, this will increase your app size on device.
Even though you want to do, then this is the solution.
As you know, every image you download will store temporary in RAM. As number of images increases RAM occupation increases. At one point RAM will full and app terminates.
For solving this, Download some images and save them. After saving, remove this images from RAM and start downloading some more images and save them.
Continue this process until you are done with all images.
This may solve your problem.
The problem is not in the amount of images to download, Download task does not use memory to hold images, but rather just save the image directly to sandbox which is fine. But the problem here lies in the concurrent NSURLSession that you run. Imagine you have 50,000 NSURLSession tasks in memory. That is what make the app crash.
for (NSInteger aIndex = 0; aIndex < 50000; aIndex++)
{
// NSURLSESSION BLOCK
}
What you need to do is a queued downloads. You need to handle this by yourself. For example, at one time, download 10 images first, then after 10 is done, then download next 10 and so on. But... it is crazy really. Who would want to wait for 50,000 images to be downloaded???

iOS / MapKit cache management issue with MKTileOverlay and PINCache library

I am using MapKit in order to create satellite and radar animation by adding MKTileOverlay over the mapView.
With an UISlider and a PlayButton I was able to create an animation, like a GIF by playing with the alpha of the MKOverlayRenderer (setting them to 0 or 0.75 according to the position of my slider).
The animation is quite smooth, all my satellite and radar tiles are loaded properly over the mapView.
I am encountering one issue with the cache management.
I realized that MapKit didn't use cache for my tileOverlay that's why I used the library PINCache in order to save my tiles so that it doesn't request and download the images each time I'm playing the animation.
My implementation :
I override the method URLForTilePath in order to build my URL to get my tile images.
- (NSURL *)URLForTilePath:(MKTileOverlayPath)path{
double latMin, latMax, longMin, longMax;
path.contentScaleFactor = 1.0;
NSMutableArray *result = [[NSMutableArray alloc] init];
result = getBBoxForCoordinates((int)path.x, (int)path.y, (int)path.z);
longMin = [[result objectAtIndex:0] doubleValue];
latMin = [[result objectAtIndex:1] doubleValue];
longMax = [[result objectAtIndex:2] doubleValue];
latMax = [[result objectAtIndex:3] doubleValue];
NSString *finalURL = self.url;
finalURL = [finalURL stringByReplacingOccurrencesOfString:#"DATE"
withString:_date];
NSString *bbox = [NSString stringWithFormat:#"bbox=%f,%f,%f,%f", longMin, latMin, longMax, latMax];
finalURL = [finalURL stringByReplacingOccurrencesOfString:#"BBOX"
withString:bbox];
return [NSURL URLWithString:finalURL];
}
And the key method that will call URLForTilePath is my implementation of loadTileAtPath :
- (void)loadTileAtPath:(MKTileOverlayPath)path
result:(void (^)(NSData *data, NSError *error))result
{
if (!result)
{
return;
}
NSString *str = self.isRadar == true ? [NSString stringWithFormat:#"Radar%#", self.date] : [NSString stringWithFormat:#"Satellite%#", self.date];
NSData *cachedData = [[PINCache sharedCache] objectForKey:str];
if (cachedData)
{
result(cachedData, nil);
}
else
{
NSURLRequest *request = [NSURLRequest requestWithURL:[self URLForTilePath:path]];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSString *str = self.isRadar == true ? [NSString stringWithFormat:#"Radar%#", self.date] : [NSString stringWithFormat:#"Satellite%#", self.date];
[[PINCache sharedCache] setObject:data forKey:str block:nil];
result(data, connectionError);
}];
}
}
Basically what I'm trying to achieve is :
Check if I have cached Data, if so then get the object.
If not, I make a request in order to download the tile with the URL given by URLForTilePath.
I then set the Object to the Cache.
The string str is my Key for the cache management
I have 2 important values in order to sort and differentiate the tiles, the type (Radar or Satellite, different image, different URL), and the Date.
I can see that the cache management is working, the Overlays are rendering way faster but the main issue I'm encountering is that it doesn't load and build the tiles at the according coordinate.
My mapView is like a puzzle of the world wrongly built.
With my piece of code, can you see something wrong I made ?
I couldn't find the reason to this memory/cache management issues with mapKit.
I decided to try with GoogleMap, it tooks me less than 2 hours to implement it, and the results are amazing.
I wanted to respect the Apple community by using Apple solution, but Google Map provided me so much more performances.

dispatch_get_main_queue() not running perfect well

I am Newbie in iOS Development. I have no knowledge about dispatch_get_main_queue() so I want to get image size from my Server image url like as
First I parse my JSON Data and Get image Size like as
[self.feedArray addObjectsFromArray:[pNotification.userInfo valueForKey:#"items"]];
[self fillHeightArray];
Here I set parse data in my self.feedArray and after that I get Height like as
-(void)fillHeightArray
{
NSMutableArray *requestArray=[[NSMutableArray alloc]init];
NSMutableArray *dataArray=[[NSMutableArray alloc]init];
for (int i=0; i<[self.feedArray count];i++)
{
NSString *urlString = [[self.feedArray objectAtIndex:i]valueForKey:#"photo"];
NSURL *imageFileURL = [NSURL URLWithString:urlString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:imageFileURL];
[requestArray addObject:urlRequest];
}
dispatch_queue_t callerQueue = dispatch_get_main_queue();
dispatch_queue_t downloadQueue = dispatch_queue_create("Lots of requests", NULL);
dispatch_async(downloadQueue, ^{
for (NSURLRequest *request in requestArray) {
[dataArray addObject:[NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil]];
}
dispatch_async(callerQueue, ^{
for (int i=0; i<[dataArray count]; i++)
{
UIImage *imagemain=[UIImage imageWithData:[dataArray objectAtIndex:i]];
UIImage *compimage =[self resizeImage:imagemain resizeSize:CGSizeMake(screenWidth/2-16,180)];
CGSize size = CGSizeMake(screenWidth/2-16,compimage.size.height);
[self.cellHeights addObject:[NSValue valueWithCGSize:size]];
}
[GlobalClass StopSpinner:self.view];
self.cltItem.hidden=FALSE;
[self.cltItem reloadData];
[self.cltItem.collectionViewLayout invalidateLayout];
[[NSUserDefaults standardUserDefaults]setValue:#"1" forKey:Loading];
});
});
}
And resize my image like as
-(UIImage *)resizeImage:(UIImage *)orginalImage resizeSize:(CGSize)size
{
float oldWidth = orginalImage.size.width;
float scaleFactor = size.width / oldWidth;
float newHeight = orginalImage.size.height * scaleFactor;
float newWidth = oldWidth * scaleFactor;
UIGraphicsBeginImageContext(CGSizeMake(newWidth, newHeight));
[orginalImage drawInRect:CGRectMake(0,0,newWidth,newHeight)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
So, from this code i got fine result at first time but when i want to load more data so this code are Running second time then i got not valid image size
I don't understand what the issue is there, but I think my
dispatch_queue_t callerQueue = dispatch_get_main_queue();
dispatch_queue_t downloadQueue = dispatch_queue_create("Lots of requests", NULL);
Have issue when I load more data.
Please help me for that.
You are always adding objects in this line:
[self.cellHeights addObject:[NSValue valueWithCGSize:size]];
When you run the code a second time, the array gets bigger, and the old values are still present in its beginning. This is probably giving you bad results when running the code a second time.
EDIT:
It might work slower, because you've made some retain cycles / have memory leaks. In this scenario, it will work ok the first time, and slower for each extra run. I do not really see anything wrong with your code, besides the self.cellHeights table growing. Check the rest of the procedure for elements that are getting bigger every time, and ensure that the objects that are not going to be used anymore are getting released.
Also, try using the 'build & analyze' [ALT + CMD + B]. This might point you to some memory leaks, or other issues.
Profiling tools are also very efficient in localizing leaks, and you can access them with [CMD + I] on the keyboard.
Another thing you can try is calling the main_queue directly, like:
dispatch_async(dispatch_get_main_queue(), ^(void) {
//do sth
});
You will avoid creating another object, and you only use the main_queue once in the whole snippet anyway.
Try doing this and let me know if you got anything.

Multiple webservice Making UI unresponsive

The problem is that I am calling multiple webservices on my homepage and the webservice is returning me the images and text from the server. During this process the UI become fully unresponsive for say 1-2 minutes which is looking very bad as I cant do anything. I heard about dispatch and tried to implement it but I dont get any results.May be I am doing something wrong.
What I want now that I want to that I want to run this process in background So that a user can interact with the UI during the fetching operation from the server. I am implementing my code just tell me where to use dispatch.
-(void)WebserviceHomeSlider{
if([AppDelegate appDelegate].isCheckConnection){
//Internet connection not available. Please try again.
UIAlertView *alertView=[[UIAlertView alloc] initWithTitle:#"Internate error" message:#"Internet connection not available. Please try again." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alertView show];
return;
}else {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer.acceptableContentTypes = [manager.responseSerializer.acceptableContentTypes setByAddingObject:#"text/html"];
[manager GET:ServiceUrl#"fpMainBanner" parameters:nil success:^(AFHTTPRequestOperation *operation,id responseObject)
{
//NSLog(#"JSON: %#", responseObject);
arrSlider = [responseObject objectWithJSONSafeObjects];
[_slideshow setTransitionType:KASlideShowTransitionSlide];
_slideshow.gestureRecognizers = nil;
[_slideshow addGesture:KASlideShowGestureSwipe];
// [_slideshow addImagesFromResources:[self getImages]]; // Add
// [_slideshow addTextFromResources:[self getText]];
// [slideShow subtextWithImages:[self getsubText]];
[_slideshow addImagesFromResources:[self getImages] stringArray:[self getText] stringsubArray:[self getsubText]];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
}
Just tell me where to use dispatch or edit my code using dispatch if possible. I have gone through some examples but still my concept is not clear. Which dispatch method id best (DEFAULT or BACKGROUND). I will be very thankful to You.
This is the code you are looking for . Just tell me where to edit it using dispatch
-(NSArray *)getText{
NSMutableArray *textArr = [[NSMutableArray alloc] init];
for(int i=0; i<[arrSlider count];i++)
{
texxt=[[arrSlider objectAtIndex:i ]valueForKey:#"title" ];
[textArr addObject:[texxt uppercaseString]];
}
return textArr;
}
-(NSArray *)getsubText{
NSMutableArray *subtext = [[NSMutableArray alloc] init];
for(int i=0; i<[arrSlider count];i++)
{
subbtext=[[arrSlider objectAtIndex:i ]valueForKey:#"tagline_value" ];
if(i==8)
{
subbtext=#"MAKE YOURSELF STAND OUT GET YOUR FREE CARDS!";
}
NSLog(#"subtext is,,.,.,,.,%#.%#",#"k",subbtext);
[subtext addObject:[subbtext uppercaseString]];
}
return subtext;
}
-(NSArray *)getImages
{
NSMutableArray *mArr = [[NSMutableArray alloc] init];
for(int i=0; i<[arrSlider count];i++)
{
pathh=[[arrSlider objectAtIndex:i ]valueForKey:#"filepath" ];
NSString *newString = [pathh stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
NSURL *imageURL = [NSURL URLWithString:newString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *originalImage = [UIImage imageWithData:imageData];
CGSize destinationSize = CGSizeMake(320, 158);
UIGraphicsBeginImageContext(destinationSize);
[originalImage drawInRect:CGRectMake(0,0,destinationSize.width,destinationSize.height)];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// [whotshotimgview setImage:image];
[mArr addObject: img];
}
return mArr;
}
[_slideshow addImagesFromResources:[self getImages] stringArray:[self getText] stringsubArray:[self getsubText]];
The problem here is that you didn't showed implementation of addImagesFromResources method. You probably have to implement GCD in this method because i guess you are fetching images and setting on UI by this your main thread is getting blocked.
You are trying to access the UIView from thread other than the main which causes the UI unresponsiveness.
Please use this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_slideshow setTransitionType:KASlideShowTransitionSlide];
_slideshow.gestureRecognizers = nil;
[_slideshow addGesture:KASlideShowGestureSwipe];
// [_slideshow addImagesFromResources:[self getImages]]; // Add
// [_slideshow addTextFromResources:[self getText]];
// [slideShow subtextWithImages:[self getsubText]];
[_slideshow addImagesFromResources:[self getImages] stringArray:[self getText] stringsubArray:[self getsubText]];
});
What I think is that your UI hangs due to downloading of image.
So you should use SDWebImage library that can help you to cache image and also prevent the UI getting hang. Using SDWebImage you can show the loader for the time the image is not loaded and it will cache the image. So from next time no image will be downloaded and also the UI will not hang. The link for complete reference to SDWebImage is :https://github.com/rs/SDWebImage
This block blocking your main UI to perform tasks so update this block as follow,
for(int i=0; i<[arrSlider count];i++)
{
pathh=[[arrSlider objectAtIndex:i ]valueForKey:#"filepath" ];
NSString *newString = [pathh stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
NSURL *imageURL = [NSURL URLWithString:newString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *originalImage = [UIImage imageWithData:imageData];
CGSize destinationSize = CGSizeMake(320, 158);
UIGraphicsBeginImageContext(destinationSize);
[originalImage drawInRect:CGRectMake(0,0,destinationSize.width,destinationSize.height)];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// [whotshotimgview setImage:image];
[mArr addObject: img];
dispatch_async(dispatch_get_main_queue(), ^{
// If you want to refresh your UI simultaniously, you can write statement for that i.e
// yourImageView.image = img;
// Otherwise remove this queue
});
});
}
The main thread should never be used for long processing. Its there for the UI.
AFNetworking provides asynchronous functionality for downloading files. You should also be careful of thrashing the network layer with too many simulatanous downloads. I think 4 or 5 is the max the last time I tested.
So the flow of execution should be as follows.
The controller sets up the slideshow, sends the downloading of the images to a background thread(automatically handled by AFNetworking), passing a completion block to it. In this completion block you need to dispatch the work back to the main thread using GCD, dispatch_async to dispatch_get_get_main_queue
Keep your main thread's run loop available for user interaction.
It is because the images are loading in another thread and you are populating your slider right after getting the data.
It is better to implement this protocol method of KASlidershow to use the slideshow in a more memory efficient way.
(UIImage *)slideShow:(KASlideShow *)slideShow imageForPosition:(KASlideShowPosition)position
{
pathh=[[arrSlider objectAtIndex:i ]valueForKey:#"filepath" ];
NSString *newString = [pathh stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
NSURL *imageURL = [NSURL URLWithString:newString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *originalImage = [UIImage imageWithData:imageData];
CGSize destinationSize = CGSizeMake(320, 158);
UIGraphicsBeginImageContext(destinationSize);
[originalImage drawInRect:CGRectMake(0,0,destinationSize.width,destinationSize.height)];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// [whotshotimgview setImage:image];
[mArr addObject: img];
return img;
});
}
i didn't try the code but i hope it will work with little changes in variable names. Good luck

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];
}

Resources