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???
Related
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.
I am struck in a big problem. What I am trying to do is getting data from the server but the server in a single hit give all the data which is very large in quantity and I am doing all my process on the main thread, So there are around 400-500 images in the form of URL which I am saving in document directory in the form of NSData. So in the dubug navigator when the memory consumption reached around 80-90 mb then my application crashed and showing the following error:-
mach_vm_map(size=135168) failed (error code=3)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
2015-01-23 17:10:03.946 ArchaioMobileViewer[853:148470] *** Terminating app due to uncaught exception 'NSMallocException', reason: 'Attempt to allocate 262144 bytes for NS/CFData failed'
I am Using ARC but still I am getting the memory problem. This is my code `-
(void)downloadDocumentsFromServer:(NSDictionary *)documentsList IsUpdate:(BOOL)isUpdate;
{
//Main Target(22)
BusinessLayer* bLL = [[BusinessLayer alloc]init];
FileManager* downloadImages = [FileManager alloc];
for(NSDictionary* inspDocumentResult in documentsList)
{
FloorDocument* floorDocument = [[FloorDocument alloc]init];
floorDocument.docID = [inspDocumentResult objectForKey:#"docID"];
floorDocument.buildingID = selectedBuildingID;
floorDocument.clientID = clientID;
NSDictionary* documentArray = [inspDocumentResult objectForKey:#"Document"];
floorDocument.docType = [[documentArray objectForKey:#"Type"] stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
floorDocument.docScale = [documentArray objectForKey:#"Scale"];
floorDocument.docDescription = [documentArray objectForKey:#"DocDesc"];
//floorDocument.floor = [bLL getFloorNameByDocIDAndBuildingID:selectedBuildingID DocID:floorDocument.docID];
floorDocument.floor = [inspDocumentResult objectForKey:#"Floor"];
NSLog(#"%#",[inspDocumentResult objectForKey:#"hiResImage"]);
[downloadImages downloadInspectionDocuments:[inspDocumentResult objectForKey:#"hiResImage"] ImageName:floorDocument.docID FileType:floorDocument.docType Folder:selectedBuildingID];
NSLog(#"Floor %# - High Res Image copied for %#",floorDocument.floor,floorDocument.docID);
//Download the Low Res Image
NSString* lowResImage = [inspDocumentResult objectForKey:#"lowResImage"];
[downloadImages downloadInspectionDocumentsLowRes:lowResImage ImageName:floorDocument.docID FileType:floorDocument.docType Folder:selectedBuildingID LowResName:#"lowResImage"];
//Copy the Quarter Size File
lowResImage = [lowResImage stringByReplacingOccurrencesOfString:#"LowRes" withString:#"LowRes4"];
[downloadImages downloadInspectionDocumentsLowRes:lowResImage ImageName:floorDocument.docID FileType:floorDocument.docType Folder:selectedBuildingID LowResName:#"lowResImage4"];
NSLog(#"Floor %# - Low Res Images copied for %#",floorDocument.floor,floorDocument.docID);
//Download the tiles
NSArray* tiles = [inspDocumentResult objectForKey:#"lsUrls"];
for(NSString* tile in tiles)
{
#autoreleasepool {
NSArray* tileNameArray = [tile componentsSeparatedByString:#"/"];
if(tileNameArray.count > 0)
{
NSString* destTile = [tileNameArray objectAtIndex:tileNameArray.count-1];
destTile = [destTile stringByReplacingOccurrencesOfString:[NSString stringWithFormat:#".%#",floorDocument.docType] withString:#""];
NSLog(#"TileName:%#",destTile);
[downloadImages downloadInspectionDocumentsTiles:tile ImageName:floorDocument.docID FileType:floorDocument.docType Folder:selectedBuildingID TileName:destTile];
}
}
}
NSLog(#"Floor %# - Tiles Image copied for %#",floorDocument.floor,floorDocument.docID);
NSLog(#"Downloading Documents Tiles For %# Completed at %#",floorDocument.docID,[bLL getCurrentDate]);
[bLL saveFloorDocuments:floorDocument IsUpdate:isUpdate];
// downloadImages=nil;
}
bLL = nil;
}
please help me out in this problem.`
This is the code which I am using inside the DownloadInspectionDocuments:-
-(void)downloadInspectionDocuments:(NSString *)url ImageName:(NSString *)imageName FileType:(NSString*)fileType Folder:(NSString*)folder
{
#autoreleasepool
{
NSString* source =[FileManager getInspectionDocumentsFolder];
//Lets get the destination folder
NSString *destination = [NSString stringWithFormat:#"%#/%#/%#",source,folder,imageName];
[self createFolder:destination CreateSubFolders:true];
NSString *filePath = [NSString stringWithFormat:#"%#/%#.%#",destination,imageName,fileType];
NSFileManager* fm = [[NSFileManager alloc]init];
if(![fm fileExistsAtPath:filePath])
{
NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
[data1 writeToFile:filePath atomically:YES];
}
}
// return [NSString stringWithFormat:#"%#.%#",imageName,fileType];
}
ARC isn't garbage collection: it will insert the memory management code (retain/release) for you but you still need to make sure you are not using up too many resources (in the same way you would in non-ARC code).
You are running this large loop on the main thread, so any memory being consumed is not going to be freed until the next run loop.
You need to break this function down into smaller steps that can be carried out in stages.
For now, if there isn't too much memory consumed for a single iteration of the outer-loop of the function you can add an autorelease pool at that level (I see you have on on the inner loop)
for(NSDictionary* inspDocumentResult in documentsList)
{
#autoreleasepool {
.... remaining code goes here
}
}
and it will at least drain what it can each iteration.
Given that you are downloading a large number of files and will be relying on network connectivity I would recommend performing the downloads asynchronously though. If you haven't already, check out AFNetworking to simplify this. This will give you much more control over your resources than you are getting now with a resource-intensive blocking call on the main thread.
You can save yourself a lot of work by following davbryn's and Andrea's suggestions to use AFNetworking and stream the file. Basically, don't put the whole file in memory and then write it to disk, write to disk as you get the bytes from the network. This should reduce the pressure on memory. For example:
- (void)downloadFile:(NSString *)urlString {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
NSString *destinationPath = [NSDocumentDirectory() stringByAppendingPathComponent:#"some-file-name"];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setOutputStream:[NSOutputStream outputStreamToFileAtPath:destinationPath append:NO]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Super duper awesome!");
// Maybe start another download here?
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error downloading file: %#", error);
}];
[operation start];
}
So then all you need to do is generate the list of things to download and in your success block start downloading another file.
I need to download some images and a video from server:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
for (int i = 0; i < _collectionArr.count; ++i) {
NSURL *URL = [NSURL URLWithString:URL_ADDRESS(portNumber,_collectionArr[i])];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(#"File downloaded to: %#", filePath);
if (i == _collectionArr.count - 1) {
[SVProgressHUD showSuccessWithStatus:#"Done"];
}
}];
[downloadTask resume];
}
I found that it is so slow! The speed is about 200K/s and the time with Android is 2/3 less than iPhone.
When I download a mp4 about 4.6M, it takes 15s.
2014-11-29 13:46:35.071 testDownload[2105:47825] Begin
2014-11-29 13:46:51.740 testDownload[2105:47825] File downloaded to: file:///Users/apple/Library/Developer/CoreSimulator/Devices/259F2CB8-01FD-47C2-A38F-6100A2FF350A/data/Containers/Data/Application/71E553BC-F85D-4BFA-8937-FE9026FDF65C/Documents/VTS_01_audi.mp4
But when I using other app to download movies it can be 2M/s. Am I wrong when I use afnetworking ? How it happens and what can I do to deal with it.
Another question is that I know it's wrong to monitor the last request with if (i == _collectionArr.count - 1){[SVProgressHUD showSuccessWithStatus:#"Done"];} But I don't know the right answer.
Thanks for your help.
A couple of thoughts:
The removal of the HUD when i hits count - 1 is not correct. You actually may remove it before they're all done. If you start two downloads, one huge one and one tiny one, the HUD will be dismissed when the second one finishes, but the other one might not be done yet. These run concurrently, so you have to wait until they're all done, not just when the last submitted one is done.
One way to do this is to use a dispatch group (which you enter as you submit the requests, leave in the completion block, and then add a dispatch group notification that removes the HUD).
You're not doing anything else outside of this code that might be blocking the main thread are you? AFNetworking dispatches its completion blocks to the main queue, and if you block the main thread for anything, it will affect this performance. You could either use Instruments to identify waiting threads, or, for testing purposes only, temporarily change the completionQueue of the manager to be some custom queue of your own creation.
For example, before your for loop, do something like:
manager.completionQueue = dispatch_queue_create("com.example.app.netCompletionQueue", NULL);
That would take main thread blocking out of the equation (though this strikes me as unlikely unless you're doing something extraordinary in the main thread). Once you confirm this is not the issue, though, comment out that line, because you really do want to use the main thread for your completion blocks.
Once you confirm the perf problem is not result on main thread contention issue, I'd suggest doing some benchmarking of alternative techniques (e.g. your own NSURLSession, etc.). This could help diagnose whether the problem is AFNetworking, rather than something like simulator vs device performance, caching, etc. Frankly, I find it unlikely that AFNetworking is, itself, the problem, though.
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];
}
I've been researching and haven't found any answer to this question - sendAsynchronousRequest vs. dataWithContentsOfURL.
Which is more efficient? more elegant? safer? etc.
- (void)loadImageForURLString:(NSString *)imageUrl
{
self.image = nil;
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:imageUrl]];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response, NSData * data, NSError * connectionError)
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
if (data) {
self.image = [UIImage imageWithData:data];
}
}];
}
OR
- (void)loadRemoteImage
{
self.image = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSData * imageData = [NSData dataWithContentsOfURL:self.URL];
if (imageData)
self.image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
if (self.image) {
[self setupImageView];
}
});
});
}
So I've come up with an answer for my own question:
Currently there are 3 main ways to load images async.
NSURLConnection
GCD
NSOperationQueue
Choosing the best way is different for every problem.
For example, in a UITableViewController, I would use the 3rd option (NSOperationQueue) to load an image for every cell and make sure the cell is still visible before assigning the picture. If the cell is not visible anymore that operation should be cancelled, if the VC is popped out of stack then the whole queue should be cancelled.
When using NSURLConnection + GCD we have no option to cancel, therefore this should be used when there is no need for that (for example, loading a constant background image).
Another good advice is to store that image in a cache, even it's no longer displayed, and look it up in cache before launching another loading process.
sendAsynchronousRequest is better, elegant and whatever you call it. But, personally, I prefer creating separate NSURLConnection and listen to its delegate and dataDelegate methods. This way, I can: 1. Set my request timeout. 2. Set image to be cached using NSURLRequest's cache mechanism (it's not reliable, though). 2. Watch download progress. 3. Receive NSURLResponse before actual download begins (for http codes > 400). etc... And, also, it depends on cases like, image size, and some other requirements of your app. Good luck!