我看到很多关于堆栈溢出的帖子,指出控制器的 viewDidLoad 方法仅在第一次访问控制器时调用,不一定每次都调用,但总是至少一次。
这根本不是我所看到的!我整理了一个简单的测试来强调这一点:https://github.com/imuz/ViewDidLoadTest
似乎对于导航控制器 segue 和模态视图视图总是调用 DidLoad。唯一不调用它的时间是在选项卡之间切换时。
我能找到的对viewDidLoad的每个解释都与此相矛盾:
- 何时调用 viewDidLoad?
- UIViewController viewDidLoad vs. viewWillAppear:什么是适当的分工?
- http://www.manning-sandbox.com/thread.jspa?threadID=41506
苹果自己的文档表明,只有在内存不足时才会卸载视图。
我目前正在 viewDidLoad 中进行初始化,假设每次 segue 转换都会调用它。
我在这里错过了什么吗?
Phillip Mills的答案是正确的。 这只是它的增强。
系统工作正常,记录如下。
您看到的是 viewDidLoad,因为被推送到导航控制器上的视图控制器是一个新实例。 它必须调用 viewDidLoad。
如果您进一步调查,您会发现这些视图控制器中的每一个在弹出时都会被释放(只需在 dealloc 中放置断点或 NSLog)。 此释放与视图控制器容器无关...它不控制它使用的控制器的寿命...它只是对它有强烈的引用。
当控制器从导航控制器堆栈中弹出时,导航控制器会释放其引用,并且由于没有其他引用,因此视图控制器将解除分配。
导航控制器仅包含对其活动堆栈中的视图控制器的强引用。
如果要重用同一控制器,则负责重用它。 使用情节提要 segue 时,您(在很大程度上)放弃了该控件。
假设您有一个push
的续集来查看控制器Foo
,因为点击某个按钮。 点击该按钮时,"系统"将创建Foo
的实例(目标视图控制器),然后执行 segue。 控制器容器现在保存对该视图控制器的唯一强引用。 一旦完成,VC将解除分配。
由于它每次都会创建一个新控制器,因此每次显示该控制器时都会调用viewDidLoad
。
现在,如果要更改此行为,并缓存视图控制器以供以后重用,则必须专门执行此操作。 如果不使用情节提要 segue,这很容易,因为您实际上是将 VC 推送/弹出到导航控制器。
但是,如果您使用故事板 segue,那就更麻烦了。
有很多方法可以做到这一点,但都需要某种形式的黑客攻击。 情节提要本身负责实例化新的视图控制器。 一种方法是覆盖instantiateViewControllerWithIdentifier
。 这是当 segue 需要创建视图控制器时调用的方法。 即使对于未向其提供标识符的控制器,也会调用它(如果您不分配标识符,系统会提供虚构的唯一标识符)。
请注意,我希望这主要是出于教育目的。 我当然不是建议这是解决你的问题的最佳方式,无论它们是什么。
像...
@interface MyStoryboard : UIStoryboard
@property BOOL shouldUseCache;
- (void)evict:(NSString*)identifier;
- (void)purge;
@end
@implementation MyStoryboard
- (NSMutableDictionary*)cache {
static char const kCacheKey[1];
NSMutableDictionary *cache = objc_getAssociatedObject(self, kCacheKey);
if (nil == cache) {
cache = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, kCacheKey, cache, OBJC_ASSOCIATION_RETAIN);
}
return cache;
}
- (void)evict:(NSString *)identifier {
[[self cache] removeObjectForKey:identifier];
}
- (void)purge {
[[self cache] removeAllObjects];
}
- (id)instantiateViewControllerWithIdentifier:(NSString *)identifier {
if (!self.shouldUseCache) {
return [super instantiateViewControllerWithIdentifier:identifier];
}
NSMutableDictionary *cache = [self cache];
id result = [cache objectForKey:identifier];
if (result) return result;
result = [super instantiateViewControllerWithIdentifier:identifier];
[cache setObject:result forKey:identifier];
return result;
}
@end
现在,您必须使用此故事板。 不幸的是,虽然 UIApplication 保留了主情节提要,但它不会公开 API 来获取它。 但是,每个视图控制器都有一个方法,storyboard
获取创建它的情节提要。
如果要加载自己的故事板,则只需实例化 MyStoryboard。 如果您使用的是默认情节提要,则需要强制系统使用您的特殊情节提要。 同样,有很多方法可以做到这一点。 一种简单的方法是重写视图控制器中的情节提要访问器方法。
你可以使 MyStoryboard 成为将所有内容转发到 UIStoryboard 的代理类,或者你可以对主情节提要进行 isa 切换,或者你可以让本地控制器从其情节提要方法返回一个。
现在,请记住,这里有一个问题。 如果在堆栈上多次推送相同的视图控制器怎么办? 使用缓存时,将多次使用完全相同的视图控制器对象。 这真的是你想要的吗?
如果没有,那么您现在需要管理与控制器容器本身的交互,以便它们可以检查它们是否已经知道此控制器,在这种情况下,需要一个新实例。
因此,有一种方法可以在使用默认情节提要 segue 时获取缓存的控制器(实际上有很多方法)...... 但这不一定是一件好事,当然也不是你默认得到的。
Apple 文档描述了视图控制器未被解除分配的情况。 如果使用 segue,则会导致新目标控制器的实例化,并且作为新对象,它需要加载视图。
在基于 xib 的应用程序中,我有时会缓存一个控制器对象,我知道我可能会经常重用该对象。 在这些情况下,它们在何时必须加载视图方面与文档保持一致。
编辑:在阅读您包含的链接时,我没有看到它们有任何矛盾。 他们也在谈论视图控制器对象生命周期内发生的事情。
每次从头开始加载控制器的视图(即请求但尚不可用)时都会调用它。 如果解除分配控制器并且视图随之而来,则下次实例化控制器时(例如,当您创建控制器以模式或通过 segue 推送它时),将再次调用它。 选项卡中的视图控制器不会解除分配,因为选项卡控制器会保留它们。