Приведенный ниже код создает гибкий стек «последний пришел - первым вышел», который обрабатывается в фоновом режиме с помощью Grand Central Dispatch. Класс SYNStackController является универсальным и может использоваться повторно, но этот пример также предоставляет код для варианта использования, указанного в вопросе, асинхронно отображая изображения ячеек таблицы и гарантируя, что после быстрой прокрутки будут отображаться следующие отображаемые ячейки.
Благодарность Бен М. , чей ответ на этот вопрос предоставил исходный код, на котором это основывалось. (В его ответе также содержится код, который можно использовать для проверки стека.) Представленная здесь реализация не требует ARC и использует только Grand Central Dispatch, а не executeSelectorInBackground. В приведенном ниже коде также хранится ссылка на текущую ячейку с использованием objc_setAssociatedObject, что позволит связать визуализированное изображение с правильной ячейкой, когда изображение впоследствии загружается асинхронно. Без этого кода изображения, созданные для предыдущих контактов, будут неправильно вставляться в повторно используемые ячейки, даже если они теперь отображают другой контакт.
Я вручил награду Бену М., но отмечаю это как принятый ответ, так как этот код более полно проработан.
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
В view.h, перед @interface:
@class SYNStackController;
В разделе view.h @interface:
SYNStackController *stackController;
В view.h, после раздела @interface:
@property (nonatomic, retain) SYNStackController *stackController;
В view.m, до @implementation:
#import "SYNStackController.h"
В view.m viewDidLoad:
// Initialise Stack Controller.
self.stackController = [[[SYNStackController alloc] init] autorelease];
В виде.м .:
- (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;
}