在可重用的表格单元格中使用带有dispatch_async的NSCache的正确方法是什么?

时间:2013-10-11 19:59:43

标签: iphone ios asynchronous nscache

我一直在寻找一种明确的方法来做到这一点,并没有找到任何可以举例并解释得很好的方法。我希望你能帮助我。

这是我正在使用的代码:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

static NSString *CellIdentifier = @"NewsCell";
NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

// Configure the cell...

NewsItem *item = [newsItemsArray objectAtIndex:indexPath.row];

cell.newsTitle.text = item.title;

NSCache *cache = [_cachedImages objectAtIndex:indexPath.row];

[cache setName:@"image"];
[cache setCountLimit:50];

UIImage *currentImage = [cache objectForKey:@"image"];

if (currentImage) {
    NSLog(@"Cached Image Found");
    cell.imageView.image = currentImage;
}else {
    NSLog(@"No Cached Image");

    cell.newsImage.image = nil;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL), ^(void)
                   {
                       NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.image]];
                       dispatch_async(dispatch_get_main_queue(), ^(void)
                       {
                           cell.newsImage.image = [UIImage imageWithData:imageData];
                           [cache setValue:[UIImage imageWithData:imageData] forKey:@"image"];
                           NSLog(@"Record String = %@",[cache objectForKey:@"image"]);
                       });
                   });
}

return cell;
}

缓存为我返回nil。

2 个答案:

答案 0 :(得分:15)

Nitin回答了关于如何使用缓存的问题。问题是,原始问题和Nitin的答案都存在一个问题,即你正在使用GCD,它(a)不能控制并发请求的数量; (b)派遣的街区不可取消。此外,您使用的是dataWithContentsOfURL,这是不可取消的。

参见WWDC 2012视频Asynchronous Design Patterns with Blocks, GCD, and XPC,第7节“分离控制和数据流”,大约48分钟进入视频,讨论为什么会出现问题,即如果用户快速向下滚动列表到第100项,所有其他99个请求将排队等候。在极端情况下,您可以使用所有可用的工作线程。并且iOS无论如何只允许五个并发网络请求,因此使用所有这些线程没有意义(如果一些调度块启动无法启动的请求,因为有五个以上的请求,一些网络请求将开始失败)。

因此,除了您当前异步执行网络请求和使用缓存的方法之外,您应该:

  1. 使用操作队列,它允许您(a)约束并发请求的数量; (b)开启取消行动的能力;

  2. 也可以使用可取消的NSURLSession。您可以自己执行此操作,也可以使用AFNetworking或SDWebImage等库。

  3. 重复使用单元格时,取消前一单元格的所有待处理请求(如果有)。

  4. 这可以做到,我们可以告诉你如何正确地做到这一点,但它是很多代码。最好的方法是使用许多UIImageView类别中的一个,它们进行缓存,但也处理所有这些其他问题。 SDWebImageUIImageView类别相当不错。它大大简化了您的代码:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        static NSString *cellIdentifier = @"NewsCell";    // BTW, stay with your standard single cellIdentifier
    
        NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier indexPath:indexPath];
    
        NewsItem *item = newsItemsArray[indexPath.row];
    
        cell.newsTitle.text = item.title;
    
        [cell.imageView sd_setImageWithURL:[NSURL URLWithString:item.image]
                          placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
    
        return cell;
    }
    

答案 1 :(得分:10)

你可能会因为NSCache

的每张图片设置相同的密钥而做错了
[cache setValue:[UIImage imageWithData:imageData] forKey:@"image"];

使用此代替以上设置ForKey作为Imagepath item.image并使用 setObject 而不是 setVlaue : -

[self.imageCache setObject:image forKey:item.image];

尝试使用此代码示例: -

.h中的

类: -

@property (nonatomic, strong) NSCache *imageCache;
.m类中的

: -

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.imageCache = [[NSCache alloc] init];

    // the rest of your viewDidLoad
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

     static NSString *cellIdentifier = @"cell";
     NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];

     NewsItem *item = [newsItemsArray objectAtIndex:indexPath.row];
     cell.newsTitle.text = item.title;

    UIImage *cachedImage =   [self.imageCache objectForKey:item.image];;
    if (cachedImage)
    {
        cell.imageView.image = cachedImage;
    }
    else
    {
        cell.imageView.image = [UIImage imageNamed:@"blankthumbnail.png"];

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

               NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.image]];
               UIImage *image    = nil;
                if (imageData) 
                     image = [UIImage imageWithData:imageData];

                if (image)
                {

                     [self.imageCache setObject:image forKey:item.image];
                }
              dispatch_async(dispatch_get_main_queue(), ^{
                        UITableViewCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
                        if (updateCell)
                           cell.imageView.image = [UIImage imageWithData:imageData];
                           NSLog(@"Record String = %@",[cache objectForKey:@"image"]);
                  });
          });            
    }
    return cell;
}