【问题标题】:Memory problems with displaying large images in UITableView在 UITableView 中显示大图像的内存问题
【发布时间】:2011-10-14 04:18:21
【问题描述】:

我有一个系统可以从网络加载大量大图像并将它们显示在自定义表格单元格中。在较旧的设备上,内存警告发生得非常快,所以我实现了一个从表中删除一些内容的系统来尝试解决这个问题,但效果不够好(删除了很​​多影响 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的方法贴出来吗?
  • 需要注意的是,标准单元类已经内置了一个 imageView。通过创建自己的然后将其添加到 contentView,您将失去很多 Apple 已经提供的自动优化.除非您有充分的理由使用自己的 imageView,否则请考虑放弃它并使用您已经有权访问的属性。
  • 非常感谢 Greg,我已经更改为使用内置的了。

标签: ios objective-c image uitableview memory-management


【解决方案1】:

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

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

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

【讨论】:

  • 图片需要是屏幕宽的(如果它们太宽,我会调整它们的大小)
【解决方案2】:

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

编辑:

很好。我已经浏览了 SDWebImage。 在发现新线程已生成的任何地方使用NSAutoreleasePool

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

【讨论】:

  • ImageNamed 为您提供一些免费的优化,如果可用,会自动拉入视网膜图像。
  • 我使用的 SDWebImage 不使用这些,它使用 initWithData 离开我的头顶。
【解决方案3】:

所以基本上,一旦图像被下载,它就会留在它的 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 是一个保留属性,一旦它被取消或重用,它将从内存中清理图像。

【讨论】:

    【解决方案4】:

    我想知道您是如何加载图像的,您是否使用自定义单元格?如果是这样,请通过 Apple 的UITableView Programming guide 他们清楚地告诉我们如何加载图像。因为他们说我们应该需要在顶部绘制图像以避免内存问题。 Apple 的示例代码LazyTableImages 中给出了有关如何加载图像的最佳示例,请也通过此操作。

    【讨论】:

    • 我的应用与 LazyTables 有不同的问题,它们不处理大图像和内存问题
    猜你喜欢
    • 2012-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多