【问题标题】:Problem dealloc'ing memory used by UIImageViews with fairly large image in an UIScrollView在 UIScrollView 中有相当大的图像的 UIImageViews 使用的内存释放问题
【发布时间】:2010-09-22 07:22:24
【问题描述】:

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

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

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

这是来自 viewController 的 sn-p 导致问题。

- (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 条件下也是如此

【问题讨论】:

    标签: iphone objective-c cocoa-touch memory-management


    【解决方案1】:
    - (void)dealloc {
        NSLog(@"DAY Controller Dealloc'd");
        [self.scrollView release];
        [super dealloc];
    }
    

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

    【讨论】:

    • 我真的试过这个。按照我的方式进行操作可以保证指针在释放后变为 nil。那是因为它调用了生成的setter,它先保留nil(没有效果),然后释放scrollView,然后将scrollView设置为nil。 (我刚才也运行它来仔细检查:))
    【解决方案2】:

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

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

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

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

        正如 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;
        }
        

        【讨论】:

        • 有点晚了,但是你把这个错误提交给苹果了吗?因为我同意这一点,这是那里的一个错误。它永远不会被删除。使用 imageWithData 时,我也遇到了同样的问题。
        【解决方案5】:

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

        【讨论】:

        • 感谢您提供此信息。此页面是 frustraitionfixer 的宝库。
        • 博文链接是404,使用站内搜索没有运气。
        【解决方案6】:

        与你的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 缓存刷新问题,尽管我自己并没有对此进行广泛测试。

        【讨论】:

        • 我目前遇到了一些内存问题,这可能是由于 imageNamed 缓存图像而导致的,而我真的不需要缓存它们,我也不希望它们被缓存。这就像告诉某人去做 x 到 100 个 y,而不是在完成后将每个 y 留在原处,他们无论如何都会随身携带它们,直到所有 y 的重量压碎它们并且它们是被杀。
        • 所以不,它从 3.1.2 开始不固定。
        • 关于限制图像大小的评论似乎有点奇怪,因为 iPad 本身可以很好地处理大小和更大的图像,对吧?那么,您认为他们在使用什么?
        • @Josh:UIImage 文档指出图像在显示大于 1024x1024 时可能会出现问题。该限制可能更多地适用于 RAM 较少的旧设备。这是我的猜测,但似乎与观察一致。
        • @Sneakyness:你确定你在某个地方没有备用保留吗?我的应用程序处理 100 个(有时 > 1000 个!)imageNamed: 图像,虽然系统确实缓存它们,但它也会在需要更多内存时释放未使用的图像。
        【解决方案7】:

        完成Bdebeez的回答。

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

        代码思路如下:

        @implementation UIImage(imageNamed_Hack)
        
        + (UIImage *)imageNamed:(NSString *)name {
        
            return [UIImage imageWithContentsOfFile:[NSString stringWithFormat:@"%@/%@", [[NSBundle mainBundle] bundlePath], name ] ];
        }
        
        @end
        

        注意: 有了这个覆盖,你不会有任何缓存加载 UIImages,如果你需要这个,你必须实现你自己的缓存。

        【讨论】:

        • 这似乎是一个非常糟糕的主意。
        • 使用类别覆盖默认方法名称可能(并且可能会)导致未定义的行为。如果你真的不想重写这个方法,你应该使用方法调配。然而最好的方法就是写你自己的方法名 imageNamedNoCache ,如果你需要的话。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-13
        • 1970-01-01
        • 1970-01-01
        • 2011-05-05
        • 1970-01-01
        • 2014-07-09
        相关资源
        最近更新 更多