Загрузка изображений в виде таблицы из URL в фоновом режиме приводит к дублированию
У меня есть табличное представление, которое заполнено результатами поискового запроса. У многих результатов есть изображения, которые нужно загрузить с URL, но не все. У меня изначально было захват изображения с URL в cellForRowAtIndexPath
метод в главном потоке, который работал отлично, но это сделало прокрутку просмотра таблицы прерывистой, так как она на мгновение "застряла" на каждом изображении.
Итак, я решил попробовать загрузить изображения в фоновом потоке. Вот мой cellForRowAtIndexPath
метод. URL-адреса хранятся в массиве результатов с индексами, соответствующими строке ячейки.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
[sBar resignFirstResponder];
if (indexPath.row != ([resultsArray count]))
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ResultCell"];
UIImageView * bookImage = (UIImageView *)[cell viewWithTag:102];
//set blank immediately so repeats are not shown
bookImage.image = NULL;
//get a dispatch queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
NSData *image = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:[[resultsArray objectAtIndex:indexPath.row] thumbnailURL]]];
//this will set the image when loading is finished
dispatch_async(dispatch_get_main_queue(), ^{
bookImage.image = [UIImage imageWithData:image];
if(bookImage.image == nil)
{
bookImage.image = [UIImage imageNamed:@"no_image.jpg"];
}
});
});
return cell;
}
// This is for the last cell, which loads 10 additional items when touched
// Not relevant for this question, I think, but I'll leave it here anyway
else{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"More"];
UILabel *detailLabel = (UILabel *)[cell viewWithTag:101];
detailLabel.text = [NSString stringWithFormat:@"Showing %d of %d results", [resultsArray count], [total intValue]];
if ([UIApplication sharedApplication].networkActivityIndicatorVisible == NO) {
cell.userInteractionEnabled = YES;
}
return cell;
}
}
Когда просмотр таблицы происходит медленно, все изображения загружаются в соответствующие ячейки, что я могу подтвердить, потому что изображения соответствуют заголовкам ячеек, но я упустил настройку всего, кроме изображения, чтобы сократить его для этого вопроса. Однако, когда я прокручиваю быстрее, особенно когда я симулирую медленное интернет-соединение, изображения начинают загружаться не в те ячейки. Я думаю, что это как-то связано с повторным использованием ячеек, потому что, если я быстро прокручиваюсь к вершине, изображение ячейки, просто покидающей вид, часто оказывается в ячейке, только что входящей. Я думал, что bookImage.image = NULL; линия гарантирует, что этого не произойдет, но я думаю, что нет. Думаю, я не очень хорошо понимаю фоновую многопоточность: когда изображение, наконец, загружается с URL-адреса, оно потеряло след, для какой ячейки оно предназначалось? Вы знаете, что может происходить? Спасибо за любые отзывы!
3 ответа
На предположение:
- Ячейка A загружается и начинает асинхронный запрос A.
- Таблица прокручивается, а ячейка A прокручивается за пределами экрана до завершения запроса.
- Ячейка A перерабатывается и снимается с производства как ячейка B.
- Ячейка B начинает асинхронный запрос B.
- Асинхронный запрос A завершается, обновляя представление изображения ячейки A (теперь ячейка B).
Чтобы исправить это, необходимо сохранить дескриптор асинхронного запроса и отменить его при снятии очереди с ячейки.
Мой подход (опирается на библиотеку AFNetworking). Включите в ваш проект классы из следующего zip-файла:
http://dl.dropbox.com/u/6487838/imageloading.zip
Создайте свои собственные ячейки. Код может выглядеть примерно так (проверьте -setComment:
а также -willMoveToSuperview:
методы):
#import "CommentCell.h"
#import "CommentCellView.h"
#import "MBImageLoader.h"
@interface CommentCell ()
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) CommentCellView *cellView;
@property (nonatomic, assign) CellStyle style;
@end
@implementation CommentCell
@synthesize cellView, style = _style, imageView, comment = _comment, showCounter;
- (id)initWithStyle:(CellStyle)style reuseIdentifier:(NSString *)reuseIdentifier;
{
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
if (self)
{
self.selectionStyle = UITableViewCellSelectionStyleNone;
self.opaque = YES;
self.style = style;
self.showCounter = YES;
CGFloat width = 53.0f;
CGFloat x = (style == CellStyleRight) ? 320.0f - 10.0f - width : 10.0f;
CGRect frame = CGRectMake(x, 10.0f, width, 53.0f);
self.imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.image = [UIImage imageNamed:@"User.png"];
[self.contentView addSubview:imageView];
self.cellView = [[CommentCellView alloc] initWithFrame:self.frame cell:self];
cellView.backgroundColor = [UIColor clearColor];
[self.contentView addSubview:cellView];
}
return self;
}
- (void)dealloc
{
self.comment = nil;
self.cellView = nil;
self.imageView = nil;
}
- (void)willMoveToSuperview:(UIView *)newSuperview
{
[super willMoveToSuperview:newSuperview];
if (!newSuperview)
{
[[MBImageLoader sharedLoader] cancelLoadImageAtURL:_comment.iconURL forTarget:self];
}
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (void)setFrame:(CGRect)newFrame
{
[super setFrame:newFrame];
CGRect bounds = self.bounds;
bounds.size.height -= 1;
cellView.frame = bounds;
}
- (void)setNeedsDisplay
{
[super setNeedsDisplay];
[cellView setNeedsDisplay];
}
- (void)setComment:(MBComment *)comment
{
if (_comment == comment)
return;
_comment = comment;
if (comment.icon == nil)
{
imageView.image = nil;
[[MBImageLoader sharedLoader] loadImageForTarget:self withURL:comment.iconURL success:^ (UIImage *image)
{
comment.icon = image;
imageView.alpha = 0.0f;
imageView.image = image;
[UIView animateWithDuration:0.5f delay:0.0f options:UIViewAnimationOptionAllowUserInteraction animations:^ {
imageView.alpha = 1.0f;
} completion:^ (BOOL finished) {}];
} failure:^ (NSError *error)
{
DLog(@"%@", error);
}];
}
else
{
imageView.image = comment.icon;
}
[self setNeedsDisplay];
}
@end
Как я это сделал
cellForRowAtIndexPath:
метод:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = @"CellIdentificator";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
}
[cell.imageView setImage:nil];
NSData *const cachedImageData = [self.cache objectAtIndex:indexPath.row];
if ([cachedImageData isKindOfClass:[NSData class]]) {
[cell.imageView setImage:[UIImage imageWithData:cachedImageData]];
} else {
[self downloadAndCacheImageForIndexPath:indexPath];
}
return cell;
}
downloadAndCacheImageForIndexPath:
метод:
- (void)downloadAndCacheImageForIndexPath:(NSIndexPath *)indexPath {
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
NSString *const imageStringURL = [NSString stringWithFormat:@"%@%.2d.png", IMG_URL, indexPath.row];
NSData *image = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:imageStringURL]];
[self.cache replaceObjectAtIndex:indexPath.row withObject:image];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:indexPath.row inSection:0]]withRowAnimation:UITableViewRowAnimationNone];
});
[image release];
});
}
self.cache
является NSMutableArray
который я использую для хранения NSData
для загруженных изображений. self.cache
предварительно загружен [NSNull null]
s для количества изображений
BR.
Евгений.