Dans un UITableView, la meilleure méthode pour annuler les opérations GCD pour les cellules qui ont quitté l'écran?

J'ai un UITableView qui charge les images d'une URL dans les cellules de manière asynchronous en utilisant GCD. Le problème est que si un user parcourt 150 rangées, 150 opérations s'arrêtent et s'exécutent. Ce que je veux, c'est déqueue / annuler ceux qui ont passé et qui sont sortis de l'écran.

Comment puis-je faire cela?

Mon code à ce stade (assez standard):

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath // after getting the cell... dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if (runQ) { NSSsortingng *galleryTinyImageUrl = [[self.smapi getImageUrls:imageId imageKey:imageKey] objectForKey:@"TinyURL"]; NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithSsortingng:galleryTinyImageUrl]]; dispatch_async(dispatch_get_main_queue(), ^{ if (imageData != nil) { UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; cell.imageView.image = [UIImage imageWithData:imageData]; } }); } }); 

runQ est un BOOL ivar I défini sur NO sur viewWillDisappear , ce qui (je pense) a pour effet d' UITableView la queue rapidement lorsque cette UITableView détache du controller de navigation.

Donc, revenons à ma question initiale: comment annuler les opérations de récupération d'image pour les cellules qui ont disparu de l'écran? Merci.

Tout d'abord, ne pas mettre en queue les opérations pendant le défilement. Au lieu de cela, chargez les images uniquement pour les lignes visibles dans viewDidLoad et lorsque l'user arrête de défiler:

 -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { for (NSIndexPath *indexPath in [self.tableView indexPathsForVisibleRows]) { [self loadImageForCellAtPath:indexPath]; } } 

Si vous voulez toujours pouvoir annuler le chargement des cellules invisibles, vous pouvez utiliser NSBlockOperation au lieu de GCD:

 self.operationQueue = [[[NSOperationQueue alloc] init] autorelease]; [self.operationQueue setMaxConcurrentOperationCount:NSOperationQueueDefaultMaxConcurrentOperationCount]; // ... -(void)loadImageForCellAtPath:(NSIndexPath *)indexPath { __block NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ if (![operation isCancelled]) { NSSsortingng *galleryTinyImageUrl = [[self.smapi getImageUrls:imageId imageKey:imageKey] objectForKey:@"TinyURL"]; NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithSsortingng:galleryTinyImageUrl]]; dispatch_async(dispatch_get_main_queue(), ^{ if (imageData != nil) { UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; cell.imageView.image = [UIImage imageWithData:imageData]; } }); } }]; NSValue *nonRetainedOperation = [NSValue valueWithNonretainedObjectValue:operation]; [self.operations addObject:nonRetainedOperation forKey:indexPath]; [self.operationQueue addOperation:operation]; } 

Ici, les operations sont un NSMutableDictionary . Lorsque vous voulez annuler une opération, vous la récupérez par l' indexPath la cellule, l'annulez et la supprimez du dictionary:

 NSValue *operationHolder = [self.operations objectForKey:indexPath]; NSOperation *operation = [operationHolder nonretainedObjectValue]; [operation cancel]; 

Vous ne pouvez pas annuler les opérations GCD après leur envoi. Vous pouvez cependant vérifier dans les blocs dispatch pour voir si le code doit continuer ou non. Dans -tableView: cellForRowAtIndexPath: vous pouvez faire quelque chose comme ça

 UIImage *image = [imageCache objectForKey:imageName]; if(image) { cell.imageView.image = image; else { dispatch_async(globalQueue, ^{ //get your image here... UIImage *image = ... dispatch_async(dispatch_get_main_queue(), ^{ if([cell.indexPath isEqual:indexPath){ cell.indexPath = nil; cell.imageView.image = image; [cell setNeedsLayout]; } }); [imageCache setObject:image forKey:imageName]; }); } 

Fondamentalement, utilisez un cache d'image (NSMutableDictionary) et essayez de saisir l'image. Si vous ne l'avez pas, envoyez un bloc et récupérez-le. Retournez ensuite dans le thread principal, vérifiez le path de l'index, puis définissez l'image et enfin mettez en cache l'image. Cet exemple utilise une cellule tableview personnalisée dont le path d'index est stocké avec elle en tant que propriété.

Donc, c'est un peu de travail supplémentaire, pour que cela fonctionne bien, mais ça vaut le coup.

Si vous utilisez des cellules retirées, la meilleure approche consiste à annuler GCD

– [UITableViewCell prepareForReuse]