尝试缓存图像时,iOS表视图卡在循环中

时间:2013-10-10 18:02:59

标签: iphone ios uitableview

我使用异步来从xml获取图像。解析器工作正常,我可以输出URL。在异步中我试图将图像缓存到可变字典中。我被困在一个循环中,图像将不再输出。这是我坚持的代码。

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

        NSLog(@"Got Here");

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

        NSLog(@"Got Here 2");

        // Configure the cell...

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

        cell.newsTitle.text = item.title;

        NSLog(@"Got Here 3");

        NSMutableDictionary *record = [_records objectAtIndex:[indexPath row]];
        if ([record valueForKey:@"actualImage"]) {
            NSLog(@"Record Found");
            [cell.newsImage setImage:[record valueForKey:@"actualImage"]];
        } else {
            NSLog(@"Record Not Found");
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,(unsigned long)NULL), ^(void)
                   {
                       NSLog(item.image);
                       NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.image]];
                       dispatch_async(dispatch_get_main_queue(), ^(void){
                           [record setValue:[UIImage imageWithData:imageData] forKey:@"actualImage"];
                           [cell.newsImage setImage:[record valueForKey:@"actualImage"]];
                           if (tableView) {
                               [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
                           }
                       });
                   });
        }

        return cell;
    }

先谢谢你的帮助。

3 个答案:

答案 0 :(得分:0)

您可以尝试以下代码。您还应该研究您的问题,并尝试找到其他现有的答案和解释。这是一个非常频繁的问题,无数次回答SO;)

例如:loading images in table using blocks

代码尝试修复最明显的问题,但无法保证它在所有边缘情况下都能正常工作。您需要调试和测试。

原始代码的主要问题是,它尝试在完成块中设置单元格的newsImage属性。当执行该操作时,捕获的单元格可能不再与同一行相关联。请记住:细胞将被重复使用!

因此,在完成块中,更改的代码从在块开始时捕获的indexPath重新评估相应的单元格:

NewsCell* cell2 = [tableView cellForRowAtIndexPath:indexPath]; // cell equals nil, if not visible

返回的单元格可能不可见,在这种情况下,表格视图返回nil。

基本上,同样适用于记录变量。只是出于不同的原因:记录变量是一个具有自动范围的变量,即在方法返回时释放。在块中捕获它可能不是一个好主意。

因此,需要在块中重新评估。这基本上是[self.records objectAtIndex:[indexPath row]];

的简写
- (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;
    NSMutableDictionary *record = [_records objectAtIndex:[indexPath row]];
    if ([record valueForKey:@"actualImage"]) {
        NSLog(@"Record Found");
        [cell.newsImage setImage:[record valueForKey:@"actualImage"]];
    } else {
        NSLog(@"Record Not Found");
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,(unsigned long)NULL), ^(void)
               {
                   NSLog(item.image);
                   NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:item.image]];
                   dispatch_async(dispatch_get_main_queue(), ^(void){
                       NSMutableDictionary *record2 = [_records objectAtIndex:[indexPath row]];
                       [record2 setValue:[UIImage imageWithData:imageData] 
                                                                   forKey:@"actualImage"];
                       NewsCell* cell2 = [tableView cellForRowAtIndexPath:indexPath]; // cell equals nil, if not visible
                       if (cell2) {
                           [cell2.newsImage setImage:[record2 valueForKey:@"actualImage"]];
                           [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
                        }
                   });
               });
    }

    return cell;
}

答案 1 :(得分:0)

创建UITableViewCell子类。当您需要在其中加载图像时,创建一个NSOperation子类来执行实际的网络连接。 (有关示例,请参阅http://www.dribin.org/dave/blog/archives/2009/05/05/concurrent_operations/)。或者,使用处理此问题的第三方软件包,其中有数百万个。我目前最喜欢的是MKNetworkKit。存储对您的操作的引用。在单元格的prepareForReuse中,如果连接尚未完成,请取消连接。如果第一个请求在第二个请求完成后发生,这将阻止您的单元格在滚动时获取错误的图像(更常见的是您会想到)。

答案 2 :(得分:0)

首先替换

[record setValue:[UIImage imageWithData:imageData] forKey:@"actualImage"];

with

[record setObject:[UIImage imageWithData:imageData] forKey:@"actualImage"];

并且您的代码应该可以运行。由于字典总是空的,所以总是转到else块。 打印NSMutableDictionary,你会意识到这一点。

这不是缓存完成的方式。它不仅涉及记忆,还涉及编码实践。

请使用像NSCache或SDWebImage

这样的干净方法

SDWebImage也负责缓存。 使用NSOperation发出请求而不是GCD。避免嵌套块和关注问题。 以下是干净代码的样子

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"cell";
    NewsCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

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

    cell.newsTitle.text = item.title;
     // category from library
    [cell.imageView setImageWithURL:newsItem.imageURL];

    if ([item needsRefresh] )
    {
         cell.newsTitle.text = item.title;

        NewsOperation *operation = [[NewsOperation alloc] initForNewsItem:newItem completion:^(NewsItem *newsItem, NSError *error) {
            if (newsItem)
            {
                [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
            }
        }];
        [[self newsItemQueue] addOperation:operation];
    }

    return cell;
}