11

我有一个大的 UIScrollView,我在其中放置了 3-4 个相当大(320x1500 像素左右)的 UIImageView 图像图块。我将这些 UIImageViews 添加到我的 NIB 文件内的滚动视图中。我的控制器上有一个插座,那就是 UIScrollView。我为此使用了一个属性(非原子,保留),并对其进行了综合。

我的问题是:当我在内存监视器中观察到这一点时,我可以看到,当加载了所有这些图像的视图时(如预期的那样),使用的内存增加了很多。但是当我离开视图时,它和它的控制器被释放了,但似乎并没有放弃它们占用的内存附近的任何地方。当我将其中一个视图(我的应用程序中有几个)缩减为 1-3 个 320x460 的图像并保持其他所有内容相同时,它可以很好地重新捕获内存。

使用这么大的图像有什么问题吗?我在这段代码中做错了什么(粘贴在下面)?

这是导致问题的 viewController 的片段。

- (CGFloat)findHeight 
{
    UIImageView *imageView = nil;
    NSArray *subviews = [self.scrollView subviews];

    CGFloat maxYLoc = 0;
    for (imageView in subviews)
    {
            if ([imageView isKindOfClass:[UIImageView class]])
            {
                    CGRect frame = imageView.frame;

                    if ((frame.origin.y + frame.size.height) > maxYLoc) {
                            maxYLoc  = frame.origin.y;
                            maxYLoc += frame.size.height;
                    }
            }
    }
    return maxYLoc;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.scrollView setContentSize:CGSizeMake(320, [self findHeight])];

    [self.scrollView setCanCancelContentTouches:NO];
    self.scrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
    self.scrollView.clipsToBounds = YES;                
    self.scrollView.scrollEnabled = YES;

    self.scrollView.pagingEnabled = NO;
}

- (void)dealloc {
    NSLog(@"DAY Controller Dealloc'd");
    self.scrollView = nil;
    [super dealloc];
}

更新:我注意到另一个奇怪的现象。如果我不使用视图上的滚动,它似乎会挂在内存上。但是,如果我滚动一堆并确保所有 UIImageViews 在某一时刻都可见,它将释放并重新获得它丢失的大部分内存。

UPDATE2:我问这个的原因是我的应用程序实际上由于内存不足而崩溃。我不介意它是否只是缓存和使用额外的内存,但它似乎永远不会释放它 - 即使在 didReceiveMmoryWarning 条件下

4

7 回答 7

15

我已经解开了这个谜团——我很确定这是苹果方面的一个错误。

正如 Kendall 所建议的(谢谢!),问题在于 InterfaceBuilder 如何从 NIB 文件加载图像。当你 initFromNib 时,所有 UIImageViews 都将使用 UIImage 的 imageNamed: 方法初始化一个 UIImage。此调用对图像使用缓存。通常,这就是您想要的。但是,对于非常大的图像以及滚动远离可见区域的图像,它似乎没有遵守内存警告并转储此缓存。这是我认为苹果方面的一个错误(如果您同意/不同意,请发表评论 - 如果其他人同意,我想提交)。正如我上面所说,如果用户滚动到足以使其全部可见,这些图像使用的内存似乎确实被释放了。

我发现的解决方法(也是 Kendall 的建议)是在 NIB 文件中将图像名称留空。因此,您可以正常布置 UIImageView 元素,但不要选择图像。然后在您的 viewDidLoad 代码中,您进入并使用 imageWithContentsOfFile: 加载图像。此方法不会缓存图像,因此不会导致保留大图像时出现任何内存问题。

当然,imageNamed: 更容易使用,因为它默认为包中的任何内容,而不必查找路径。但是,您可以使用以下内容获取捆绑包的路径:

NSString *fullpath = [[[NSBundle mainBundle] bundlePath];

把所有这些放在一起,这就是代码中的样子:

NSString *fullpath = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:[NSString stringWithFormat:@"/%@-%d.png", self.nibName, imageView.tag]];
UIImage *loadImage = [UIImage imageWithContentsOfFile:fullpath];
imageView.image = loadImage;

因此,将其添加到我上面的代码中,完整的功能如下所示:

- (CGFloat)findHeight 
{
    UIImageView *imageView = nil;
    NSArray *subviews = [self.scrollView subviews];

    CGFloat maxYLoc = 0;
    for (imageView in subviews)
    {
        if ([imageView isKindOfClass:[UIImageView class]])
        {
            CGRect frame = imageView.frame;

            if ((frame.origin.y + frame.size.height) > maxYLoc) {
                maxYLoc  = frame.origin.y;
                maxYLoc += frame.size.height;
            }

            NSString *fullpath = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:[NSString stringWithFormat:@"/%@-%d.png", self.nibName, imageView.tag]];
            NSLog(fullpath);


            UIImage *loadImage = [UIImage imageWithContentsOfFile:fullpath];
            imageView.image = loadImage;
        }
    }
    return maxYLoc;
}
于 2008-11-14T20:08:11.507 回答
4

它可能是系统缓存对内存中图像的引用,假设您刚刚在 IB 的媒体浏览器中的图像中添加了药物,下面的代码可能正在使用UIImage imageNamed: 方法...

imageWithContentsOfFile您可以尝试使用: 或:加载所有图像,imageWithData看看它的行为是否相同(拖入未填充UIImageViews的 IB 并在 : 中设置内容)viewDidLoad

如果您想了解更多细节,请阅读UIImage类参考,它还描述了缓存哪些方法。

如果是缓存,它可能没问题,因为系统会在需要时释放它(您是否也尝试在模拟器中点击模拟内存警告?)

于 2008-11-14T07:12:26.160 回答
4

与您的 imageNamed 缓存问题分开,您的问题的答案

我有一个大的 UIScrollView,我在其中放置了 3-4 个相当大(320x1500 像素左右)的 UIImageView 图像图块。[...] 使用这么大的图像有什么问题吗?

在 UIImage 文档的标题中:

You should avoid creating UIImage objects that are greater than 1024 x 1024 in size.
Besides the large amount of memory such an image would consume, you may run into 
problems when using the image as a texture in OpenGL ES or when drawing the image 
to a view or layer.

此外,Apple 声称已在 3.0 及更高版本中修复了 imageNamed 缓存刷新问题,尽管我自己并没有对此进行广泛测试。

于 2009-10-23T18:57:36.423 回答
1

完成 Bdebeez 的回答。

一个好主意是覆盖imageNamed:调用imageWithContentsOfFile:.

这是代码的想法:

@implementation UIImage(imageNamed_Hack)

+ (UIImage *)imageNamed:(NSString *)name {

    return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] bundlePath], name ] ];
}

@end

注意: 使用此覆盖,您将不会有任何缓存加载 UIImages,如果需要,您将必须实现自己的缓存。

于 2009-11-10T18:56:45.263 回答
0
- (void)dealloc {
    NSLog(@"DAY Controller Dealloc'd");
    [self.scrollView release];
    [super dealloc];
}

试一试,您的 @property() 定义要求保留它,但您没有明确释放该对象

于 2008-11-14T06:30:22.703 回答
0

从我从它的内存管理行为中了解到的是,除非内存不足,否则视图不会被释放。尝试像 SQLBooks 这样的官方演示: 1. 使用 Leaks Monitor 运行 2. 运行它拥有的每个视图。3. 返回根视图。4. 您会注意到内存使用水平仍然相同。正如肯德尔所说,它可能会被缓存?

我认为您不应该注意这种内存使用模式——因为当新图像指向 UIScrollView 时,旧图像对象将被释放,并且无论如何都会为新图像释放内存。

于 2008-11-14T08:57:31.160 回答
0

有一个已知问题 - imageName 中存在内存泄漏。我找到了一个非常有用的解决方案——在应用程序委托中创建图像现金,这样可以优化我的应用程序的性能和内存使用。 请参阅此博客文章

于 2009-08-26T21:27:47.307 回答