Le code ci-dessous crée une pile flexible de type "last in-first out" qui est traitée en arrière-plan à l'aide de Grand Central Dispatch. La classe SYNStackController est générique et réutilisable, mais cet exemple fournit également le code pour le cas d'utilisation identifié dans la question : rendre les images des cellules du tableau de manière asynchrone, et s'assurer que lorsque le défilement rapide s'arrête, les cellules actuellement affichées sont les prochaines à être mises à jour.
Félicitations à Ben M. dont la réponse à cette question a fourni le code initial sur lequel il s'est basé. (Sa réponse fournit également du code que vous pouvez utiliser pour tester la pile.) L'implémentation fournie ici ne nécessite pas d'ARC, et utilise uniquement Grand Central Dispatch plutôt que performSelectorInBackground. Le code ci-dessous stocke également une référence à la cellule actuelle en utilisant objc_setAssociatedObject qui permettra à l'image rendue d'être associée à la cellule correcte, lorsque l'image sera chargée ultérieurement de manière asynchrone. Sans ce code, les images rendues pour les contacts précédents seront incorrectement insérées dans les cellules réutilisées, même si elles affichent maintenant un contact différent.
J'ai attribué la prime à Ben M. mais je marque cette réponse comme étant la réponse acceptée puisque ce code est plus complètement travaillé.
SYNStackController.h
//
// SYNStackController.h
// Last-in-first-out stack controller class.
//
@interface SYNStackController : NSObject {
NSMutableArray *stack;
}
- (void) addBlock:(void (^)())block;
- (void) startNextBlock;
+ (void) performBlock:(void (^)())block;
@end
SYNStackController.m
//
// SYNStackController.m
// Last-in-first-out stack controller class.
//
#import "SYNStackController.h"
@implementation SYNStackController
- (id)init
{
self = [super init];
if (self != nil)
{
stack = [[NSMutableArray alloc] init];
}
return self;
}
- (void)addBlock:(void (^)())block
{
@synchronized(stack)
{
[stack addObject:[[block copy] autorelease]];
}
if (stack.count == 1)
{
// If the stack was empty before this block was added, processing has ceased, so start processing.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
[self startNextBlock];
});
}
}
- (void)startNextBlock
{
if (stack.count > 0)
{
@synchronized(stack)
{
id blockToPerform = [stack lastObject];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
[SYNStackController performBlock:[[blockToPerform copy] autorelease]];
});
[stack removeObject:blockToPerform];
}
[self startNextBlock];
}
}
+ (void)performBlock:(void (^)())block
{
@autoreleasepool {
block();
}
}
- (void)dealloc {
[stack release];
[super dealloc];
}
@end
Dans le view.h, avant @interface :
@class SYNStackController;
Dans la section view.h @interface :
SYNStackController *stackController;
Dans le fichier view.h, après la section @interface :
@property (nonatomic, retain) SYNStackController *stackController;
Dans le view.m, avant @implementation :
#import "SYNStackController.h"
Dans la vue.m viewDidLoad :
// Initialise Stack Controller.
self.stackController = [[[SYNStackController alloc] init] autorelease];
Dans la vue.m :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Set up the cell.
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
else
{
// If an existing cell is being reused, reset the image to the default until it is populated.
// Without this code, previous images are displayed against the new people during rapid scrolling.
[cell setImage:[UIImage imageNamed:@"DefaultPicture.jpg"]];
}
// Set up other aspects of the cell content.
...
// Store a reference to the current cell that will enable the image to be associated with the correct
// cell, when the image subsequently loaded asynchronously.
objc_setAssociatedObject(cell,
personIndexPathAssociationKey,
indexPath,
OBJC_ASSOCIATION_RETAIN);
// Queue a block that obtains/creates the image and then loads it into the cell.
// The code block will be run asynchronously in a last-in-first-out queue, so that when
// rapid scrolling finishes, the current cells being displayed will be the next to be updated.
[self.stackController addBlock:^{
UIImage *avatarImage = [self createAvatar]; // The code to achieve this is not implemented in this example.
// The block will be processed on a background Grand Central Dispatch queue.
// Therefore, ensure that this code that updates the UI will run on the main queue.
dispatch_async(dispatch_get_main_queue(), ^{
NSIndexPath *cellIndexPath = (NSIndexPath *)objc_getAssociatedObject(cell, personIndexPathAssociationKey);
if ([indexPath isEqual:cellIndexPath]) {
// Only set cell image if the cell currently being displayed is the one that actually required this image.
// Prevents reused cells from receiving images back from rendering that were requested for that cell in a previous life.
[cell setImage:avatarImage];
}
});
}];
return cell;
}