Melany Melany - 20 days ago 11
iOS Question

Asynchronously download images for UITableView using NSURLConnection

I have a TableView with customCells, when user press Start button on some cell the loading starts. There are many such cells, so I need to implement this downloading in parallel (asynchronously).
For image downloading and updating the cell in Table view I use next code:

#define myAsyncQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)


I include this method into the async queue, that I supposed should enable parallel downloading of images.
- (void)didClickStartAtIndex:(NSInteger)cellIndex withData:

(CustomTableViewCell*)data
{
dispatch_async(myAsyncQueue, ^{
self.customCell = data;
self.selectedCell = cellIndex;
ObjectForTableCell* tmp =[self.dataDictionary objectForKey:self.names[cellIndex]];

NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:tmp.imeageURL]
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:60.0];
self.connectionManager = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
});
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
self.urlResponse = response;

NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
NSDictionary *dict = httpResponse.allHeaderFields;
NSString *lengthString = [dict valueForKey:@"Content-Length"];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSNumber *length = [formatter numberFromString:lengthString];
self.totalBytes = length.unsignedIntegerValue;

self.imageData = [[NSMutableData alloc] initWithCapacity:self.totalBytes];
}

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.imageData appendData:data];
self.customCell.progressView.progress = ((100.0/self.urlResponse.expectedContentLength)*self.imageData.length)/100;
float per = ((100.0/self.urlResponse.expectedContentLength)*self.imageData.length);
self.customCell.realProgressStatus.text = [NSString stringWithFormat:@"%0.f%%", per];

}

I tried to set this block to queue - main queue - cause its the place where image is already downloaded,

-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
dispatch_async(dispatch_get_main_queue(), ^{
self.customCell.realProgressStatus.text = @"Downloaded";

UIImage *img = [UIImage imageWithData:self.imageData];
self.customCell.image.image = img;
self.customCell.tag = self.selectedCell;
});
[self.savedImages setObject:img forKey:self.customCell.nameOfImage.text];
NSNumber *myNum = [NSNumber numberWithInteger:self.selectedCell];
[self.tagsOfCells addObject:myNum];
}


Without all queues(when I comment it)all works properly - but just 1 downloading at a ones.
But when I tried to implement code with queues as a result it doesn't download anything. I understand that I did smh wrong but I can't define it.

Thanks a lot for any help in advance.

Answer

If your looking out for starting it form basics I guess you should start with NSURLSession as NSURLConnection most of implementation had been deprecated and won't be available after iOS 9. For complete reference URL Session Programming Guide and tutorial

Coming back to your question you should do something similar to this took it from tutorial

// 1
NSURLSessionDownloadTask *getImageTask =
[session downloadTaskWithURL:[NSURL URLWithString:imageUrl]

    completionHandler:^(NSURL *location,
                        NSURLResponse *response,
                        NSError *error) {
        // 2
        UIImage *downloadedImage =
          [UIImage imageWithData:
              [NSData dataWithContentsOfURL:location]];
      //3
      dispatch_async(dispatch_get_main_queue(), ^{
        // do stuff with image
        _imageWithBlock.image = downloadedImage;
      });
}];

// 4
[getImageTask resume];

But my personal recommendation is go for AFNetworking which is best for iOS networking and widely used/tested in iOS app world.

For image download using AFNetworking

[_imageView setImageWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://i.imgur.com/fVhhR.png"]]
                      placeholderImage:nil
                               success:^(NSURLRequest *request , NSHTTPURLResponse *response , UIImage *image ){
                                   NSLog(@"Loaded successfully: %d", [response statusCode]);
                               }
                               failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error){
                                   NSLog(@"failed loading: %@", error);
                               }
    ];

EDIT : Async downloading using concurrency

// get main dispact queue
dispatch_queue_t queue = dispatch_get_main_queue();
// adding downloading task in queue using block
dispatch_async(queue, ^{
  NSData* imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]];
  UIImage* image = [[UIImage alloc] initWithData:imageData];
  // after download compeletes geting main queue again as there can a possible crash if we assign directly
  dispatch_async(dispatch_get_main_queue(), ^{
    _imageWithBlock.image = image;
  });
});