0

我有这个简单的 UITableView,每个单元格都有一个与之对应的图像。我所做的只是在每个单元格中显示图像的标题和图像本身。这是我的 cellForRowAtIndexPath:

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

    // this is where the data for each cell is
    NSDictionary *dataForThisCell = cachedData.posts[indexPath.row][@"data"];

    // this creates a new cell or grabs a recycled one, I put NSLogs in the if statement to make sure they are being recycled, they are.
    post *cell = (post *) [self.tableView dequeueReusableCellWithIdentifier:@"postWithImage"];
    if (cell == nil) {
        cell = [[[NSBundle mainBundle]loadNibNamed:@"postWithImage" owner:self options:nil]objectAtIndex:0];
        [cell styleCell];
    }

    // if this cell has an image we need to stick it in the cell
    NSString *lowerCaseURL = [dataForThisCell[@"url"] lowercaseString];
    if([lowerCaseURL hasSuffix: @"gif"] || [lowerCaseURL hasSuffix: @"bmp"] || [lowerCaseURL hasSuffix: @"jpg"] || [lowerCaseURL hasSuffix: @"png"] || [lowerCaseURL hasSuffix: @"jpeg"]) {

        // if this cell doesnt have an UIImageView, add one to it. Cells are recycled so this only runs several times
        if(cell.preview == nil) {
            cell.preview = [[UIImageView alloc] init];
            [cell.contentView addSubview: cell.preview];
        }

        // self.images is an NSMutableDictionary that stores the width and height of images corresponding to cells.
        // if we dont know the width and height for this cell's image yet then we need to know now to store it
        // once the image downloads, and then cause our table to reload so that heightForRowAtIndexPath
        // resizes this cell correctly
        Boolean shouldReloadData = self.images[dataForThisCell[@"name"]] == nil ? YES : NO;

        // download image
        [cell.preview cancelImageRequestOperation];
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString: dataForThisCell[@"url"]]];
        [request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
        [cell.preview setImageWithURLRequest: request
                              placeholderImage: [UIImage imageNamed:@"thumbnailLoading.png"]
                                       success: ^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {

                                           // if we indicated earlier that we didnt know the dimensions of this image until
                                           // just now after its been downloaded, then store the image dimensions in self.images
                                           // and tell the table to reload so that heightForRowAtIndexPath
                                           // resizes this cell correctly
                                           if(shouldReloadData) {
                                               NSInteger imageWidth = image.size.width;
                                               NSInteger imageHeight = image.size.height;
                                               if(imageWidth > [ColumnController columnWidth]) {
                                                   float ratio = [ColumnController columnWidth] / imageWidth;
                                                   imageWidth = ratio * imageWidth;
                                                   imageHeight = ratio* imageHeight;
                                               }
                                               if(imageHeight > 1024) {
                                                   float ratio = 1024 / imageHeight;
                                                   imageHeight = ratio * imageHeight;
                                                   imageWidth = ratio* imageWidth;
                                               }
                                               self.images[dataForThisCell[@"name"]] = @{ @"width": @(imageWidth), @"height": @(imageHeight), @"titleHeight": @([post heightOfGivenText: dataForThisCell[@"title"]]) };
                                               [self.tableView reloadData];

                                           // otherwise we alreaady knew the dimensions of this image so we can assume
                                           // that heightForRowAtIndexPath has already calculated the correct height
                                           // for this cell
                                           }else{

                                               // assign the image we downloaded to the UIImageView within the cell
                                               cell.preview.image = image;

                                               // position the image
                                               NSInteger width = [self.images[dataForThisCell[@"name"]][@"width"] integerValue];
                                               NSInteger height = [self.images[dataForThisCell[@"name"]][@"height"] integerValue];
                                               cell.preview.frame = CGRectMake( ([ColumnController columnWidth] - width)/2 , [self.images[dataForThisCell[@"name"]][@"titleHeight"] integerValue] + 10, width, height);

                                           }

                                       }
                                       failure: ^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {}];


    }

    // set title of the cell
    cell.title.text = [NSString stringWithFormat: @"%@\n\n\n\n\n", dataForThisCell[@"title"]];

    `enter code here`// ask for a restyle
    [cell setNeedsLayout];

    // returns my customized cell
    return cell;

}

发生的事情是一切都按照我想要的方式工作,但是一旦我向下滚动超过大约 100 个单元格左右,我的应用程序的背景就会变黑几秒钟,然后我看到我的主屏幕(我见过一些人称之为HSOD - 死亡主屏幕)。有时在 xcode 的控制台中,我会在崩溃前看到内存警告,有时却没有。

我知道一个事实,无论问题是什么,它都与将图像放入单元格有关。如果我只注释掉这一行:

cell.preview.image = image;

然后一切正常,不再崩溃(但当然图像不会显示在单元格中)。

单元格正在被重用,我知道这是有效的,我将 UIImageView 的图像属性设置为 nil:

- (void) prepareForReuse {
    [super prepareForReuse];
    if(self.preview != nil)
        self.preview.image = nil;
}

在我的 appDelegate 中,我还定义了这个:

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {

    [UIImageView clearAFImageCache];

}

这会删除图像缓存,但这也不能解决问题(无论如何iOS应该在内存警告时自动清除图像缓存)。

我对我的项目运行了分析,它报告没有内存泄漏,这是分析器显示的,以及显示崩溃时的分配:

在此处输入图像描述

除了控制台中出现大约 2/3 的应用程序崩溃时间的偶尔内存警告外,控制台中没有出现其他错误,并且我没有遇到任何断点或异常。

4

2 回答 2

2

所有这些分配都是您在每次请求时创建新的表格视图单元格,而不是重用现有的。reuseIdentifier如果不为从 创建的单元格设置 a UINibdequeueReusableCellWithIdentifier:将始终返回 `nil.

要解决此问题,请添加以下代码(在此问题中引用):

[self.tableView registerNib:[UINib nibWithNibName:@"nibname" bundle:nil] forCellReuseIdentifier:@"cellIdentifier"];

于 2013-05-22T19:08:33.370 回答
1

我找到了一个解决方案,虽然不是一个完美的解决方案:

AFNetworking 库非常棒,我认为问题的原因在于我自己的代码或我对 NSCache 的工作原理缺乏了解。

AFNetworking 使用 Apple 的 NSCache 缓存图像。NSCache 类似于 NSMutableDictionary,但在内存分布较薄时释放对象(请参阅此处的更多信息)。

在 UIImageView+AFNetworking.m 我找到了定义

+ (AFImageCache *)af_sharedImageCache

并将其更改为如下所示:

+ (AFImageCache *)af_sharedImageCache {
    static AFImageCache *_af_imageCache = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _af_imageCache = [[AFImageCache alloc] init];
        _af_imageCache.countLimit = 35;
    });

    return _af_imageCache;
}

这里重要的一行是

_af_imageCache.countLimit = 35;

这告诉 AFNetworking 中使用的 NSCache 对象来缓存图像,它最多只能缓存 35 个东西。

由于我不知道的原因,iOS 没有按应有的方式自动从缓存中清除对象,并且在缓存上调用 removeAllObjects 也不起作用。这个解决方案并不理想,因为它可能没有充分利用缓存,或者可能过度使用缓存,但与此同时,它至少阻止了缓存尝试存储无限数量的对象。

于 2013-05-23T18:00:37.820 回答