我有一个iOS应用程序,它使用UINavigationController以相当简单的主/细节方式向前(推)和向后(弹出)移动。
详细视图控制器有2或3个全屏图像,我使用
手动添加layer1 = [[UIImageView alloc] initWithFrame:layer1Rect];//layer1rect is a CGRect
UIImage *img1 = [[UIImage alloc]initWithContentsOfFile:imgName];//imgName is an image in the bundle
layer1.image = img1;
layer1.alpha = 1;
[self.view addSubview:layer1];
img1 = nil;
在我的viewDidUnload中我有
for (UIView *subview in [self.view subviews]) {
[subview removeFromSuperview];
}
layer1 = nil;
layer2 = nil;
layer3 = nil;
我遇到的问题是,在推动和弹出十几次后,我收到内存警告和崩溃。
我正在使用ARC,尽管我从其他地方读到的内容本身不太可能是问题的根源。
当我在乐器中运行应用程序时(在设备上,而不是模拟器上),我看到了一些有趣的结果。
首先,内存分配工具显示内存使用量少于2MB且没有泄漏,直到警告和崩溃。我还可以看到,当我执行pop时,我的详细视图控制器实例会被释放。
但是,如果我查看活动监视器,我看到使用率从大约50MB开始,并且每次推送到详细视图控制器时都会继续增加,直到它崩溃大约250MB。
我认为可能是图像被缓存了,但我没有使用imageName:where,这是缓存的常见原因。
那我错过了什么?当我从堆栈中弹出详细视图控制器时,我可以确保它所使用的所有内存都可以再次使用吗?
答案 0 :(得分:1)
viewDidUnload
。删除视图并在以后重新加载它是UIViewController
在iOS 2-5下处理低内存警告的方式。
在iOS 6下,视图控制器永远不会丢弃其视图。所以你永远不会得到viewDidUnload
。在你的情况下,这意味着每次第一个代码块运行时你都会添加另一个UIImage
(我假设它不在viewDidLoad
?)。旧的不会被取消分配,因为它有一个超级视图;您发布对它的引用没有任何区别。
此外,initWithContentsOfFile:
将更好地表示为[UIImage imageNamed:]
,因为后者显式缓存图像,而前者每次都从磁盘重新加载,从而创建像素内容的新副本。
所以建议的更改是改变这个:
layer1 = [[UIImageView alloc] initWithFrame:layer1Rect];
对此:
[layer1 removeFromSuperview];
layer1 = [[UIImageView alloc] initWithFrame:layer1Rect];
如果您的视图层次结构中存在layer1
,那么当您在initWithFrame:
行隐式释放对它的引用时,它将确保已取消分配。
此外:
UIImage *img1 = [[UIImage alloc]initWithContentsOfFile:imgName];
会更好:
UIImage *img1 = [UIImage imageNamed:imgName];
因为在整个应用程序中多次使用带有该文件名的图像肯定会指向同一个实例,但如果警告需要它,它仍将从内存中删除。
至于诊断,请尝试打开NSZombies。 NSZombies使释放的对象保持活动状态并且通常被使用,以便您可以捕获悬空指针的使用。在这种情况下,你可以做的是打开僵尸,看看它是否会大大改变你的内存占用。如果没有,则确认您实际上没有解除分配。
活动监视器不一定是一个可靠的衡量标准 - iOS框架在适当的地方使用NSCache
,如果没有人愿意,操作系统也不会将过程中的记忆带走,因为这只是一个毫无意义的开支处理。你应该使用乐器,特别是因为它可以按类型和数量分解内存中的内容,让你不仅知道你的总数,还知道你花在记忆上的东西。
这个逻辑解释了为什么imageNamed:
通常是一个更好的选择,尽管你的评论。您之前加载的图像会留在内存中,只要有备用内存来容纳它们。如果收到内存警告并且没有人使用它们,它们就不会停留。只有当您对内存警告的响应非常昂贵时,才需要关注它,并且通常通过确保重用资源具有相同的身份而不是复制来帮助避免内存警告。