我已经看到很多关于堆栈溢出的帖子,说控制器的viewDidLoad方法只在第一次访问控制器时被调用,不一定每次都被调用,但总是至少一次。
这不是我所看到的!我把一个简单的测试放在一起突出显示: https://github.com/imuz/ViewDidLoadTest
似乎导航控制器segues和模态视图始终调用viewDidLoad。唯一一次没有被调用的是在标签之间切换。
viewDidLoad的每一个解释我都可以发现与此相矛盾:
苹果自己的文档表明只有在内存不足时才会卸载视图。
我目前正在viewDidLoad中进行初始化,假设它是在每次segue过渡时调用的。
我在这里错过了什么吗?
答案 0 :(得分:13)
Phillip Mills的回答是正确的。这只是对它的增强。
系统正在记录中。
您正在看viewDidLoad,因为被推送到导航控制器的视图控制器是 new 实例。 必须调用viewDidLoad。
如果进一步调查,您会看到每个视图控制器在弹出时都被释放(只需在dealloc中放置断点或NSLog)。这种释放与视图控制器容器无关......它不能控制它使用的控制器的寿命......它只是对它有强烈的引用。
当控制器弹出导航控制器堆栈时,导航控制器会释放其引用,并且由于没有其他引用,视图控制器将解除分配。
导航控制器仅保存对其活动堆栈中的视图控制器的强引用。
如果您想重复使用同一个控制器,您负责重复使用它。当你使用故事板segues时,你放弃了这种控制(在很大程度上)。
假设您有一个push
segue来查看控制器Foo
作为点击某个按钮的结果。当点击该按钮时,“系统”将创建Foo
(目标视图控制器)的实例,然后执行segue。控制器容器现在拥有该视图控制器的唯一强引用。一旦完成它,VC就会解除分配。
由于每次都会创建一个新的控制器,每次出现控制器时都会调用viewDidLoad
。
现在,如果要更改此行为,并缓存视图控制器以供以后重用,则必须专门执行此操作。如果您不使用故事板segue,那么很容易,因为您实际上正在将VC推送/弹出到导航控制器。
但是,如果你使用故事板segues,那就麻烦了。
有很多方法可以做到这一点,但都需要某种形式的黑客攻击。故事板本身负责实例化新的视图控制器。一种方法是覆盖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。如果您使用的是默认情节提要,那么您需要强制系统使用您的特殊故事板。同样,有很多方法可以做到这一点。一种简单的方法是在视图控制器中覆盖storyboard访问器方法。
您可以将MyStoryboard变成一个代理类,将所有内容转发到UIStoryboard,或者您可以调整主故事板,或者您可以让本地控制器从其故事板方法中返回一个。
现在,请记住,这里有一个问题。如果您多次在堆栈上推送相同的视图控制器怎么办?使用缓存,将多次使用完全相同的视图控制器对象。这真的是你想要的吗?
如果没有,那么你现在需要管理与控制器容器本身的交互,这样他们就可以检查这个控制器是否已经知道,在这种情况下需要一个新实例。
所以, 是一种在使用默认故事板segue时获取缓存控制器的方法(实际上有很多方法)......但这不一定是好事,当然不是什么你默认得到。
答案 1 :(得分:12)
我相信Apple文档描述了视图控制器未被释放的情况。如果您使用segue,那么您将导致新目标控制器的实例化,并且作为新对象,它需要加载视图。
在基于xib的应用程序中,我有时会缓存一个我知道可能经常重复使用的控制器对象。在这些情况下,他们在必须加载视图时表现与文档保持一致。
编辑: 在阅读您所包含的链接时,我不会发现它们之间存在任何矛盾。他们也在谈论在视图控制器对象的生命周期中发生的事情。
答案 2 :(得分:0)
每次从头开始加载控制器视图时(即已请求但尚未可用),都会调用它。如果取消分配控制器并且视图随之移动,则下次实例化控制器时将再次调用它(例如,当您创建控制器以模态或通过segue推送它时)。选项卡中的视图控制器未被释放,因为选项卡控制器会将它们保留在一起。