使用GCD进行最后进入堆叠?

Dun*_*age 28 iphone objective-c uitableview grand-central-dispatch ios

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

令我感到震惊的是,Last In-First Out堆栈似乎很可能在很大程度上解决了这个问题,因为每当用户停止滚动这些单元格时,下一个将被更新,然后将更新当前屏幕外的其他单元格.Grand Central Dispatch可以做到这样吗?或者没有太繁重的其他方式实施?

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

另外:请注意,虽然主题是"我如何使用GCD创建最后进入第一个堆栈",但实际上我只想解决上面列出的问题并且可能有更好的方法来实现它.我非常愿意接受像timthetoolman那样以另一种方式解决问题的建议; 如果这样的建议最终是我使用的,我会认识到原始问题的最佳答案以及我最终实施的最佳解决方案...... :)

tim*_*man 16

由于设备的内存限制,您应该根据需要和后台GCD队列加载图像.在cellForRowAtIndexPath:方法中,检查您的联系人的图像是否为nil或已被缓存.如果映像在高速缓存中为零,则使用嵌套的dispatch_async从数据库加载映像并更新tableView单元.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
   {
       static NSString *CellIdentifier = @"Cell";
       UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
       if (cell == nil) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
       }
       // If the contact object's image has not been loaded, 
       // Use a place holder image, then use dispatch_async on a background queue to retrieve it.

       if (contact.image!=nil){
           [[cell imageView] setImage: contact.image];
       }else{
           // Set a temporary placeholder
           [[cell imageView] setImage:  placeHolderImage];

           // Retrieve the image from the database on a background queue
           dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
           dispatch_async(queue, ^{
               UIImage *image = // render image;
               contact.image=image;

               // use an index path to get at the cell we want to use because
               // the original may be reused by the OS.
               UITableViewCell *theCell=[tableView cellForRowAtIndexPath:indexPath];

               // check to see if the cell is visible
               if ([tableView visibleCells] containsObject: theCell]){
                  // put the image into the cell's imageView on the main queue
                  dispatch_async(dispatch_get_main_queue(), ^{
                     [[theCell imageView] setImage:contact.image];
                     [theCell setNeedsLayout];
                  });
               }
           }); 
       }
       return cell;
}
Run Code Online (Sandbox Code Playgroud)

WWDC2010会议视频"Introducing Blocks和Grand Central Dispatch"也显示了使用嵌套dispatch_async的示例.

另一个潜在的优化可能是在应用启动时开始在低优先级后台队列上下载图像.即

 // in the ApplicationDidFinishLaunchingWithOptions method
 // dispatch in on the main queue to get it working as soon
 // as the main queue comes "online".  A trick mentioned by
 // Apple at WWDC

 dispatch_async(dispatch_get_main_queue(), ^{
        // dispatch to background priority queue as soon as we
        // get onto the main queue so as not to block the main
        // queue and therefore the UI
        dispatch_queue_t lowPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)
        dispatch_apply(contactsCount,lowPriorityQueue ,^(size_t idx){
               // skip the first 25 because they will be called
               // almost immediately by the tableView
               if (idx>24){
                  UIImage *renderedImage =/// render image
                  [[contactsArray objectAtIndex: idx] setImage: renderedImage];
               }

        });
 });
Run Code Online (Sandbox Code Playgroud)

通过这种嵌套调度,我们将图像呈现在极低优先级队列上.将图像渲染放在后台优先级队列上将允许从上面的cellForRowAtIndexPath方法渲染的图像以更高的优先级呈现.因此,由于队列的优先级不同,你将拥有一个"穷人"LIFO.

祝好运.


Dun*_*age 11

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

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

我已经将这笔奖金授予了Ben M.但是我认为这是一个公认的答案,因为这段代码更加完整.

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

在view.h中,在@interface之前:

@class SYNStackController;
Run Code Online (Sandbox Code Playgroud)

在view.h @interface部分:

SYNStackController *stackController;
Run Code Online (Sandbox Code Playgroud)

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

@property (nonatomic, retain) SYNStackController *stackController;
Run Code Online (Sandbox Code Playgroud)

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

#import "SYNStackController.h"
Run Code Online (Sandbox Code Playgroud)

在view.m viewDidLoad中:

// Initialise Stack Controller.
self.stackController = [[[SYNStackController alloc] init] autorelease];
Run Code Online (Sandbox Code Playgroud)

在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;
}
Run Code Online (Sandbox Code Playgroud)


Ben*_* M. 6

好的,我已经测试了这个并且它有效.该对象只是从堆栈中拉出下一个块并异步执行它.它目前只适用于void返回块,但你可以做一些奇特的事情,比如添加一个具有块的对象和一个委托来将块的返回类型传递回去.

注意:我在这里使用了ARC,所以你需要XCode 4.2或更高版本,对于那些在以后版本中的人来说,只需更改强保留,你应该没问题,但是如果你不添加它会内存泄漏所有内容在发布中.

编辑:为了更具体地说明您的用例,如果您的TableViewCell有一个图像,我将以下列方式使用我的堆栈类来获得您想要的性能,请告诉我它是否适合您.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }

    // Configure the cell...

    UIImage *avatar = [self getAvatarIfItExists]; 
    // I you have a method to check for the avatar

    if (!avatar) 
    {
        [self.blockStack addBlock:^{

            // do the heavy lifting with your creation logic    
            UIImage *avatarImage = [self createAvatar];

            dispatch_async(dispatch_get_main_queue(), ^{
                //return the created image to the main thread.
                cell.avatarImageView.image = avatarImage;
            });

        }];
    }
    else
    {
         cell.avatarImageView.image = avatar;
    }

    return cell;
}
Run Code Online (Sandbox Code Playgroud)

这是测试代码,显示它作为堆栈工作:

WaschyBlockStack *stack = [[WaschyBlockStack alloc] init];

for (int i = 0; i < 100; i ++)
{
    [stack addBlock:^{

        NSLog(@"Block operation %i", i);

        sleep(1);

    }];
}
Run Code Online (Sandbox Code Playgroud)

这是.h:

#import <Foundation/Foundation.h>

@interface WaschyBlockStack : NSObject
{
    NSMutableArray *_blockStackArray;
    id _currentBlock;
}

- (id)init;
- (void)addBlock:(void (^)())block;

@end
Run Code Online (Sandbox Code Playgroud)

还有他们:

#import "WaschyBlockStack.h"

@interface WaschyBlockStack()

@property (atomic, strong) NSMutableArray *blockStackArray;

- (void)startNextBlock;
+ (void)performBlock:(void (^)())block;

@end

@implementation WaschyBlockStack

@synthesize blockStackArray = _blockStackArray;

- (id)init
{
    self = [super init];

    if (self) 
    {
        self.blockStackArray = [NSMutableArray array];
    }

    return self;
}

- (void)addBlock:(void (^)())block
{

    @synchronized(self.blockStackArray)
    {
        [self.blockStackArray addObject:block];
    }
    if (self.blockStackArray.count == 1) 
    {
        [self startNextBlock];
    }
}

- (void)startNextBlock
{
    if (self.blockStackArray.count > 0) 
    {
        @synchronized(self.blockStackArray)
        {
            id blockToPerform = [self.blockStackArray lastObject];

            [WaschyBlockStack performSelectorInBackground:@selector(performBlock:) withObject:[blockToPerform copy]];

            [self.blockStackArray removeObject:blockToPerform];
        }

        [self startNextBlock];
    }
}

+ (void)performBlock:(void (^)())block
{
    block();
}

@end
Run Code Online (Sandbox Code Playgroud)