在UITableView中显示大图时出现的内存问题

7
我有一个系统,从Web加载许多大图像并在自定义表格单元格中显示它们。在旧设备上,内存警告很快发生,因此我实现了一个删除表格中某些内容的系统来尝试解决这个问题,但效果不佳(删除了许多图像影响了UI)。
所以我想把所有图像都加载到设备的缓存中,然后再从缓存中加载它们 - 我已经实现了SDWebImage。这很不错,但我仍然没有解决内存分配问题,因为图像仍然一直显示着,因此保留在内存中会导致崩溃。
我认为我需要实现一个系统,如果正在显示单元格,则显示来自缓存的图像,并在单元格未显示时隐藏它 - 我只是不知道如何构建这样一个系统。
或者这不会起作用吗?通过删除其表格单元格中的图像,您真的可以保持应用程序的内存较低(并避免其出现内存警告/崩溃)吗?还是我只需要继续使用先前的解决方案,并删除图像/单元格,直到内存警告停止? 附带代码更新

TableViewController.m

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{   
    if (indexPath.section == 0)
    {
    currentIndexPath = indexPath;

    ImageTableCell *cell = (ImageTableCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    ImageDownloader *download = [totalDownloads objectAtIndex:[indexPath row]];

    if (cell == nil) 
    {
        cell = [[[ImageTableCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: CellIdentifier] autorelease];
    }

    cell.imageView.image = download.image;

    return cell;
}
return nil;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{   
    int t = [totalDownloads count];   
    return t;
}

ImageTableCell.m - 自定义单元格

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) 
{
    self.frame = CGRectMake(0.0f, 0.0f, 320.0f, 0.0f);
    self.contentView.frame = CGRectMake(0.0f, 0.0f, 320.0f, 0.0f);

    self.autoresizingMask = (UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth);
    self.contentMode = UIViewContentModeScaleToFill;
    self.autoresizesSubviews = YES;

    self.contentView.autoresizingMask = (UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth);
    self.contentView.contentMode = UIViewContentModeScaleToFill;
    self.contentView.autoresizesSubviews = YES;

    [self.imageView drawRect:CGRectMake(0.0f, 0.0f, 320.0f, 0.0f)];
    self.imageView.contentMode = UIViewContentModeScaleAspectFill;
    self.imageView.autoresizingMask = (UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth);
    self.imageView.opaque = YES;
}
return self;
}

ImageDownloader (实现SDWebImageManagerDelegate)

    -(void) downloadImage // Comes from Model class
    {    
    if (image == nil)
    {
        NSURL *url = [NSURL URLWithString:self.urlString];

        SDWebImageManager *manager = [SDWebImageManager sharedManager];

        // Remove in progress downloader from queue
        [manager cancelForDelegate:self];

        if (url)
        {   
            [manager downloadWithURL:url delegate:self retryFailed:YES];
        }
    }
    }

    - (void)cancelCurrentImageLoad
    {
        [[SDWebImageManager sharedManager] cancelForDelegate:self];
    }

    - (void)webImageManager:(SDWebImageManager *)imageManager didFinishWithImage:(UIImage *)_image
    {      
        self.image = _image;

        if ([self.delegate respondsToSelector:@selector(addImageToModel:)]) [self.delegate addImageToModel:self];
    }
    - (void)webImageManager:(SDWebImageManager *)imageManager didFailWithError:(NSError *)error;
    {
        if ([self.delegate respondsToSelector:@selector(badImage)]) [self.delegate badImage];
    }

你能否只贴出 cellForRowAtIndexPath 方法? - Ilanchezhian
1
需要注意的一点是,标准单元格类已经内置了imageView。通过创建自己的imageView并将其添加到contentView中,您会失去Apple已经提供的许多自动优化功能。除非您有一个非常好的理由使用自己的imageView,否则请考虑放弃它并利用您已经可以访问的属性。 - Greg Combs
非常感谢Greg,我已经更改为使用内置的了。 - daihovey
5个回答

2

不要使用[UIImage imageNamed:@""],尝试使用[[[UIImage alloc] initWithContentsOfFile:@""] autorelease];

编辑:

好的。我已经浏览了SDWebImage。 在发现新线程被生成时,请使用NSAutoreleasePool

另外一个解决方案是,在保存到缓存之前调整图像大小。


ImageNamed 可以为您提供一些免费的优化,并且如果有可用的 retina 图像,它会自动拉取。 - Greg Combs
我正在使用SDWebImage,它不使用这两者之一,它使用initWithData。 - daihovey

2

在下载完图片后,不要将大图保留在内存中。只需创建一个小尺寸的图像(缩略图)来在表视图中显示,并将较大的图像写入某个目录。

您可以使用以下代码创建图像的缩略图。

            CGSize size = CGSizeMake(32, 32);
            UIGraphicsBeginImageContext(size);
            [yourImage drawInRect:CGRectMake(0, 0, 32, 32)];
            yourImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();

图片需要铺满整个屏幕(如果它们太宽,我会调整它们的大小)。 - daihovey

2

基本上,一旦图像被下载,它就会留在它的SDWebImage实例中,并且永远不会被释放。您应该使用以下方法将图像保存到iPhone的磁盘中:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
NSString *documentsPath = [paths objectAtIndex:0];
NSString *imagePath = [documentsPath stringByAppendingPathComponent:[NSString stringWithFormat:@"image%d.jpg", rowNumber]; // you should have this set somewhere
[UIImageJPEGRepresentation(_image, 1.0) writeToFile:imagePath atomically:YES];

你可以将图片的路径保存在SDWebImage实例中,然后在-cellForRowAtIndexPath:方法中,不要执行以下操作:

cell.imageView.image = download.image;

您应该这样做:
UIImage *image = [[UIImage alloc] initwithContentsOfFile:download.imagePath];
cell.imageView.image = image;
[image release];

这将始终从磁盘加载图像,由于cell.imageView.image是一个保留的属性,一旦它被赋值为nil或者被重用,它将清理内存中的图像。


0
我想了解您是如何加载图片的,您是否使用自定义单元格?如果是,请查看苹果公司的UITableView编程指南。他们清楚地告诉我们如何加载图片。其中他们说我们应该需要绘制图片以避免内存问题。 苹果公司的示例代码LazyTableImages提供了加载图像的最佳示例,请也参考一下。

我的应用程序与LazyTables有不同的问题,它们无法处理大型图像和内存问题。 - daihovey

0

我使用了 Kingfisher SDK 并将服务器图片调整到了自定义的 cell 尺寸,这使得我的工作变得轻松愉快。

extension NewsCollectionViewCell {
  func configure(with news: Articles) {
    KF.url(URL(string:news?.urlToImage ?? ""), cacheKey: "\(news?.urlToImage ?? "")-").downsampling(size: imgNews.frame.size).cacheOriginalImage().set(to: imgNews)
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接