UIViewController防止视图被卸载

16
当我的iPhone应用程序收到内存警告时,那些当前不可见的UIViewController的视图将被卸载。在一个特定的控制器中,卸载视图和插座比较致命。
我正在寻找一种方式来防止这个视图被卸载。我认为这种行为相当愚蠢 - 我有一个缓存机制,因此当内存警告出现时 - 我会卸载自己大量的数据并释放足够的内存,但我绝对需要保持这个视图不受影响。
我看到UIViewController有一个名为unloadViewIfReloadable的方法,在内存警告出现时会调用。有人知道如何告诉Cocoa Touch我的视图不可重新加载吗?
还有其他建议如何在内存警告时防止我的视图被卸载吗?
提前感谢。
苹果文档关于视图控制器的视图生命周期如下:

didReceiveMemoryWarning - 默认实现只有在确定安全的情况下才会释放视图

现在...我用空函数重写了didReceiveMemoryWarning,它只是调用NSLog告诉我收到了一个警告。然而 - 视图仍然被卸载了。此外,到底根据什么标准决定视图是否安全卸载...哦!太多问题了!

也许你应该重构你的设计,使得那些不能被释放的部分成为一个单独的持久化对象,而不是视图本身的一部分。 - David Gelhar
嗨,大卫,屏幕上显示了整个视图层次结构,我不想在顶部显示模态视图控制器时将其拆除并重新构建...这不是有些过度kill? - Marin Todorov
2
我喜欢@umpo提供的解决方案,但是,伙计们,这段代码会导致运行时警告,像这样“ MyViewController实现的-viewDidUnload导致视图被重新加载。 这将对系统性能产生不利影响。” - 你是忽略它还是采取了某些措施? - matm
@delirus,我已经有3个应用程序使用了@umpo发布的完全相同的代码片段,但我没有收到这样的警告,我正在构建4.3版本。 - Marin Todorov
哦,那一定是我的问题。谢谢你的回复 :) - matm
5个回答

15
根据文档,didReceiveMemoryWarning:的默认实现会在视图可以安全释放(即superview==nil)时释放视图。
为了防止视图被释放,您可以重写didReceiveMemoryWarning:,但在您的实现中,请不要调用[super didReceiveMemoryWarning]。这是视图被默认释放的地方(如果未显示)。
默认的didReceiveMemoryWarning通过调用[viewcontroller setView:nil]来释放视图,因此您可以重写该方法。

重写didReceiveMemoryWarning对我没有任何影响。 重写setView会导致调试控制台大量抱怨它将对性能产生不利影响。 这会让你无法进入应用商店吗? - jocull

13

在我的情况下,看起来可行的方法是重写setView:以忽略将其设置为nil。这很笨拙,但因为这是一个笨拙的问题,这种方法管用:

-(void)setView:(UIView*)view {
    if(view != nil || self.okayToUnloadView) {
        [super setView:view];
    }
}

哇...这就是我所说的“跳出框架”的思维方式,非常感谢!下次更新我的应用程序时,我会实现这个功能。 - Marin Todorov
1
只是一条通知 - 我已经实现了这个功能 - 现在有3个应用程序在App Store上使用它,对我来说,解决方案很好。 - Marin Todorov
这个问题在于viewDidUnload方法仍然会被调用,如果你不小心的话可能会让你的代码出现混乱。我猜这就是为什么一些人看到了有关性能问题的警告的原因。 - John Stephen
4
你能否提供更多关于self.okayToUnloadView的使用细节? - barfoon

1

因为被接受的解决方案在阻止视图被清除时仍然存在viewDidUnload问题,所以我使用了一种不同但仍然脆弱的方法。系统使用unloadViewForced:消息向控制器卸载视图,因此我拦截该消息以阻止其传递。这可以防止对viewDidUnload的混乱调用。以下是代码:

@interface UIViewController (Private)
- (void)unloadViewForced:(BOOL)forced;
@end

- (void)unloadViewForced:(BOOL)forced {
    if (!_safeToUnloadView) {
        return;
    }
    [super unloadViewForced:forced];
}

这显然存在问题,因为它拦截了UIViewController中未记录的消息。

progrmr在上面发布了一个答案,建议拦截didReceiveMemoryWarning。根据我看到的堆栈跟踪,拦截应该也可以起作用。不过我还没有尝试这种方法,因为我担心可能会阻止其他内存清理(例如导致它不调用带有内存警告消息的子视图控制器)。


你在哪里为 UIViewController 指定类别?是在 .m 文件中吗?由于某些原因,我的重载的 unloadViewForced 从未被调用。 - expert
是的,在 .m 文件中。 - John Stephen
我还没有检查过它是否在最近的iOS更新中停止工作。由于它是一个未记录的API,它可能已经消失或被重命名了。 - John Stephen

1

这会是这么简单吗?

虽然文档中没有提到,但是如果我在viewDidLoad中独占保留我的视图,那么它就不会在内存警告时被释放。我在模拟器中连续尝试了几次警告,似乎一切都很好。

所以...暂时的技巧是在viewDidLoad中“保留”,并在dealloc中进行“释放” - 这样视图控制器就“粘”在视图上,直到需要释放为止。

我会做更多的测试,并写出结果。


这个技巧对我来说似乎没有起作用。dealloc方法被直接调用,所以视图会被卸载。我还尝试使用[self retain];,但结果只是让它无休止地尝试dealloc。不确定为什么会这样。 - jocull

1

我认为这些想法都不可行。我尝试重写[didReceiveMemoryWarning],对于某些手机有效,但发现有一款手机在该方法甚至被调用之前就卸载了视图(可能是极低的内存或其他原因)。覆盖[setView]会产生大量日志警告,因此我不会冒苹果公司的风险。保留视图只会泄漏该视图 - 它将防止崩溃,但并不能真正起作用 - 视图将在下次控制器UI加载时被替换。

所以你真的必须计划在屏幕外时随时卸载视图,这并不理想,但也只能如此。我发现处理这个问题最好的模式是立即提交,这样你的UI始终是最新的,或者进行副本编辑,其中你将模型复制到临时实例中,填充视图并使用即时提交该实例,然后在用户点击“保存”或其他操作时将更改复制回原始模型。


1
对我来说,重写setView:方法完美地运作,不会产生警告,并且我已经有3个应用程序在App Store上使用了这种技术。 - Marin Todorov
不幸的是,这对于当模态视图被解除显示以显示底层空视图时将会迎接用户的空白白屏没有任何作用。 - Oscar

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