与GCD的最后进入堆叠? [英] Last In-First Out Stack with GCD?

查看:75
本文介绍了与GCD的最后进入堆叠?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个UITableView,显示与每行中的联系人关联的图像。在一些情况下,这些图像在地址簿联系人图像的第一显示器上被读取,并且在没有一个的情况下,他们是基于存储的数据呈现的化身。我现在使用GCD在后台线程上更新这些图像。但是,这会按照请求的顺序加载图像,这意味着在快速滚动期间队列变得冗长,当用户停止滚动时,当前单元格是 last 以进行更新。在iPhone 4上,这个问题并不是很明显,但我很想支持旧硬件并在iPhone 3G上进行测试。延迟是可以忍受的,但非常明显。

I have a UITableView that displays images associated with contacts in each row. In some cases these images are read on first display from the address book contact image, and where there isn't one they are an avatar rendered based on stored data. I presently have these images being updated on a background thread using GCD. However, this loads the images in the order they were requested, which means during rapid scrolling the queue becomes lengthy and when the user stops scrolling the current cells are the last to get updated. On the iPhone 4, the problem isn't really noticeable, but I am keen to support older hardware and am testing on an iPhone 3G. The delay is tolerable but quite noticeable.

令我觉得Last In-First Out堆栈似乎很可能在很大程度上解决了这个问题,就像用户停止滚动那些将更新下一个要更新的单元格,然后更新当前屏幕外的其他单元格。 Grand Central Dispatch有可能这样吗?或者不太繁琐以实现其他方式?

It strikes me that a Last In-First Out stack would seem likely to largely resolve this issue, as whenever the user stopped scrolling those cells would be the next to be updated and then the others that are currently off-screen would be updated. Is such a thing possible with Grand Central Dispatch? Or not too onerous to implement some other way?

请注意,顺便提一下,我正在使用带有SQLite存储的Core Data而我没有使用NSFetchedResultsController 因为为了加载此视图的数据而必须遍历多对多关系。 (据我所知,这排除了使用NSFetchedResultsController。) [我发现NSFetchedResultsController可以与多对多关系一起使用,尽管官方文档似乎在说。但是我还没有在这种情况下使用它。]

Note, by the way, that I am using Core Data with a SQLite store and I am not using an NSFetchedResultsController because of a many-to-many relationship that has to be traversed in order to load the data for this view. (As far as I am aware, that precludes using an NSFetchedResultsController.) [I've discovered an NSFetchedResultsController can be used with many-to-many relationships, despite what the official documentation appears to say. But I'm not using one in this context, yet.]

添加:请注意,虽然主题是我如何使用GCD创建最后进入第一个堆栈,实际上我只是想解决上面列出的问题,并且可能有更好的方法来实现它。我对Timthetoolman提出的另一种解决问题的建议持开放态度;如果这样的建议最终是我使用的,我将认识到原始问题的最佳答案以及我最终实施的最佳解决方案......:)

Addition: Just to note that while the topic is "How do I create a Last In-First Out Stack with GCD", in reality I just want to solve the issue outlined above and there may be a better way to do it. I am more than open to suggestions like timthetoolman's one that solves the problem outlined in another way; if such a suggestion is finally what I use I'll recognize both the best answer to the original question as well as the best solution I ended up implementing... :)

推荐答案

下面的代码创建了一个灵活的后进先出堆栈,使用Grand Central Dispatch在后台处理。 SYNStackController类是通用的并且可重用,但是此示例还提供了问题中标识的用例的代码,异步呈现表格单元格图像,并确保在快速滚动停止时,当前显示的单元格是下一个要更新的单元格。

The code below creates a flexible last in-first out stack that is processed in the background using Grand Central Dispatch. The SYNStackController class is generic and reusable but this example also provides the code for the use case identified in the question, rendering table cell images asynchronously, and ensuring that when rapid scrolling stops, the currently displayed cells are the next to be updated.

感谢 Ben M. 对此问题的回答这是基于的初始代码。 (他的回答还提供了可用于测试堆栈的代码。)此处提供的实现不需要ARC,仅使用Grand Central Dispatch而不是performSelectorInBackground。下面的代码还使用objc_setAssociatedObject存储对当前单元格的引用,当随后异步加载图像时,该对象将使渲染图像与正确的单元格相关联。如果没有此代码,为以前的联系人呈现的图像将被错误地插入重用的单元格中,即使它们现在显示的是不同的联系人。

Kudos to Ben M. whose answer to this question provided the initial code on which this was based. (His answer also provides code you can use to test the stack.) The implementation provided here does not require ARC, and uses solely Grand Central Dispatch rather than performSelectorInBackground. The code below also stores a reference to the current cell using objc_setAssociatedObject that will enable the rendered image to be associated with the correct cell, when the image is subsequently loaded asynchronously. Without this code, images rendered for previous contacts will incorrectly be inserted into reused cells even though they are now displaying a different contact.

我已将此奖金授予Ben M.但我将此标记为已接受的答案,因为此代码已完全通过。

I've awarded the bounty to Ben M. but am marking this as the accepted answer as this code is more fully worked through.

SYNStackController.h

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

//
//  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之前:

In the view.h, before @interface:

@class SYNStackController;

在view.h @interface部分:

In the view.h @interface section:

SYNStackController *stackController;

在view.h中,在@interface部分之后:

In the view.h, after the @interface section:

@property (nonatomic, retain) SYNStackController *stackController;

在view.m中,在@implementation之前:

In the view.m, before @implementation:

#import "SYNStackController.h"

In view.m viewDidLoad:

In the view.m viewDidLoad:

// Initialise Stack Controller.
self.stackController = [[[SYNStackController alloc] init] autorelease];

在view.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;
}

这篇关于与GCD的最后进入堆叠?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆