использование NSInvocationOperation для UITableViewCell заставляет некоторые UIImageViews отображать неправильное изображение?Возможен ли повторный вход? - PullRequest
0 голосов
/ 11 ноября 2011

Я пытаюсь обеспечить плавную прокрутку UITableView, просматривая около 700 изображений, которые загружаются из Интернета, кэшируются (во внутреннее хранилище) и отображаются в каждой ячейке таблицы.Мой код пока выглядит хорошо, насколько прокручивается производительность.Тем не менее, я заметил, что иногда, если соединение прерывается или если я прокручиваю очень быстро, ячейка будет отображать неправильное изображение (изображение другой ячейки) в течение примерно 1/2 секунды, а затем обновлять изображение, которое предполагается

На данный момент я подозреваю 2 вещи:

A- У меня может возникнуть проблема с повторным входом с того момента, когда мои NSInvocationOperation перезванивают в основной поток с [self performSelectorOnMainThread:] наТочка, в которой выполняется селектор в основном потоке.Хотя на самом деле я не вижу общих переменных.

B - Какая-то гонка между основным потоком и NSInvocationOperation?Например:

1 вызов основного потока cacheImageFromURL

2 внутри этого вызова UIImage охватывает рабочий поток

3 рабочий поток почти завершен и получает вызов performSelectorOnMainThread

4 соответствующая ячейка исключена для повторного использования в этой точке, поэтому основной поток снова вызывает cahceImageFromURL для нового изображения.

5 внутри этого вызова UIImage останавливает NSOPerationQueue, что приводит к смерти предыдущего потока NSInvocationOperation.

6 НО, поток уже вызвал performSelectorOnMainThread

7, поэтому селектор возбуждается, вызывая загрузку старого изображения.

8 сразу после этого, недавно появившийсяпоток завершает выборку нового изображения и снова вызывает performSelectorOnMainThread, вызывая обновление правого изображения.

Если это так, то мне нужно установить флаг при входе в cacheImageFromURL метод, чтобы код рабочего потока не вызывал performSelectorOnMainThread, если внутри cacheImageFromURL?

уже есть другой поток (основной). Вот мой код для моего UIImageView подкласса, каждая ячейка которого находится втаблица использует:

@implementation UIImageSmartView

//----------------------------------------------------------------------------------------------------------------------
@synthesize defaultNotFoundImagePath;
//----------------------------------------------------------------------------------------------------------------------
#pragma mark - init
//----------------------------------------------------------------------------------------------------------------------
- (void)dealloc
{ 
  if(!opQueue)
  {
    [opQueue cancelAllOperations];
    [opQueue release];
  }
  [super dealloc];
}
//----------------------------------------------------------------------------------------------------------------------
#pragma mark - functionality
//----------------------------------------------------------------------------------------------------------------------
- (bool)cacheImageFromURL:(NSString*)imageURL
{
  /*  If using for the first time, create the thread queue and keep it 
      around until the object goes out of scope*/
  if(!opQueue)
    opQueue = [[NSOperationQueue alloc] init];
  else
    [opQueue cancelAllOperations];

  NSString *imageName = [[imageURL pathComponents] lastObject];
  NSString* cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
  NSString *cachedImagePath = [cachePath stringByAppendingPathComponent:imageName];

  /*  If the image is already cached, load it from the local cache dir.
      Else span a thread and go get it from the internets.*/
  if([[NSFileManager defaultManager] fileExistsAtPath:cachedImagePath])
    [self setImage:[UIImage imageWithContentsOfFile:cachedImagePath]];
  else
  {
    [self setImage:[UIImage imageWithContentsOfFile:self.defaultNotFoundImagePath]];
    NSMutableArray *payload = [NSMutableArray arrayWithObjects:imageURL, cachedImagePath, nil];

    /*  Dispatch thread*/
    concurrentOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadURI:) object:payload];
    [opQueue addOperation: concurrentOp];
    [concurrentOp release];
  }

  return YES;
}
//----------------------------------------------------------------------------------------------------------------------
/*  Thread code*/
-(void)loadURI:(id)package
{
  NSArray *payload = (NSArray*)package;
  NSString *imageURL = [payload objectAtIndex:0];  
  NSString *cachedImagePath = [payload objectAtIndex:2];

  /*  Try fetching the image from the internets.
      If we got it, write it to disk. If fail, set the path to the not found again.*/
  UIImage *newThumbnail = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];  
  if(!newThumbnail)
    cachedImagePath = defaultNotFoundImagePath;
  else  
    [UIImagePNGRepresentation(newThumbnail) writeToFile:cachedImagePath atomically:YES];

  /*  Call to the main thread - load the image from the cache directory
      at this point it'll be the recently downloaded one or the NOT FOUND one.*/
  [self performSelectorOnMainThread:@selector(updateImage:) withObject:cachedImagePath waitUntilDone:NO];
}
//----------------------------------------------------------------------------------------------------------------------
- (void)updateImage:(NSString*)cachedImagePath
{
  [self setImage:[UIImage imageWithContentsOfFile:cachedImagePath]];
}
//----------------------------------------------------------------------------------------------------------------------
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
  // Return YES for supported orientations
  return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

@end

И способ использования этого UIImage находится в контексте cellForRowAtIndexPath, например:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  UIImageSmartView *cachedImage;
  // and some other stuff...

  static NSString *CellIdentifier = @"Cell";
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  if (cell == nil) 
  {
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                   reuseIdentifier:CellIdentifier] autorelease];

    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    cell.selectionStyle = UITableViewCellSelectionStyleGray;

    // some labels and tags stuff..

    cachedImage = [[UIImageSmartView alloc] initWithFrame:CGRectMake(5, 5, 57, 80)];
    cachedImage.contentMode = UIViewContentModeCenter;
    cachedImage.defaultNotFoundImagePath = [[NSBundle mainBundle] pathForResource:@"DefaultNotFound" ofType:@"png"];
    cachedImage.tag = PHOTO_TAG;

    [cell.contentView addSubview:cachedImage];
    [cell.contentView addSubview:mainLabel];
    [cell.contentView addSubview:secondLabel];

  }
  else
  {
    cachedImage = (UIImageSmartView*)[cell.contentView viewWithTag:PHOTO_TAG];
    mainLabel = (UILabel*)[cell.contentView viewWithTag:MAINLABEL_TAG]; 
  }

  // Configure the cell...

  NSString *ImageName = [[[self.dbData objectAtIndex:indexPath.row] objectAtIndex:2] 
                         stringByReplacingOccurrencesOfString:@".jpg" 
                         withString:@"@57X80.png"];

  NSString *imageURL = [NSString stringWithFormat:@"www.aServerAddress.com/%@/thumbnail5780/%@", 
                        self.referencingTable, 
                        ImageName];

  [cachedImage cacheImageFromURL:imageURL];

  mainLabel.text = [[self.dbData objectAtIndex:indexPath.row] objectAtIndex:0]; 
  return cell;
}

Ответы [ 2 ]

1 голос
/ 25 ноября 2011

D33pN16h7 прав в том, что проблема заключалась в повторном использовании ячейки. Однако вместо того, чтобы пытаться сделать indexPath потокобезопасным через NSURLConnection, я решил переопределить все это, переместив NSOperationQueue в код UITableViewController и сделав так, чтобы класс imageView был одновременно надлежащим подклассом NSOperation (поскольку я использовал NSOperationInvocation прежде всего, чтобы попытаться избежать полноценного подкласса NSOperation).

Итак, теперь контроллер таблицы управляет своим собственным NSOperationQueue, операции являются подклассами NSOperation, и я могу отменить их из кода контроллера таблицы, когда табличное представление прокручивается мимо них. И все работает быстро и красиво.

1 голос
/ 11 ноября 2011

Проблема заключается в повторном использовании ячейки, одна ячейка одновременно запрашивает различные изображения и отображается при загрузке каждого из них. Я знаю, что вы отменяете очередь операций, но поскольку вызывающая сторона обработки работает синхронно, операция продолжает выполнение. Я предлагаю попытаться сохранить indexPath запроса и сопоставить его с индексным путем ячейки перед установкой UIImage.

...