实际上,每次有segue转换时都会调用viewDidLoad

Imr*_*ran 10 iphone initialization uiviewcontroller uikit ios

我已经看到很多关于堆栈溢出的帖子说明控制器的viewDidLoad方法仅在第一次访问控制器时被调用,并不一定每次都被调用,但总是至少一次.

这不是我所看到的!我整理了一个简单的测试来强调这一点:https: //github.com/imuz/ViewDidLoadTest

似乎导航控制器segues和模态视图始终调用viewDidLoad.它没有被调用的唯一时间是在标签之间切换.

我发现viewDidLoad的每一个解释都与此相矛盾:

苹果自己的文档表明,只有在内存不足时才会卸载视图.

我目前正在viewDidLoad中进行初始化,假设它是在每个segue转换时调用的.

我在这里错过了什么吗?

Phi*_*lls 13

我相信Apple文档描述了视图控制器未被释放的情况.如果使用segue,那么您将导致新目标控制器的实例化,并且作为新对象,它需要加载视图.

在基于xib的应用程序中,我有时会缓存一个我知道可能经常重复使用的控制器对象.在这些情况下,它们在必须加载视图时的行为与文档保持一致.

编辑:在阅读您所包含的链接时,我认为它们中没有任何矛盾.他们也在谈论在视图控制器对象的生命周期中发生的事情.


Jod*_*ins 13

Phillip Mills的回答是正确的.这只是它的增强.

系统正在记录中.

您正在看viewDidLoad,因为被推送到导航控制器的视图控制器是一个实例.它必须调用viewDidLoad.

如果进一步调查,您会看到每个视图控制器在弹出时都被释放(只需在dealloc中放置一个断点或NSLog).这种释放与视图控制器容器无关......它不能控制它使用的控制器的寿命......它只是对它有一个强引用.

当控制器弹出导航控制器堆栈时,导航控制器会释放其引用,并且由于没有其他引用,视图控制器将解除分配.

导航控制器仅保存对其活动堆栈中的视图控制器的强引用.

如果你想重复使用相同的控制器,有责任重新使用.当您使用故事板segues时,您放弃该控制(在很大程度上).

假设您有一个pushsegue来查看控制器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
Run Code Online (Sandbox Code Playgroud)

现在,您必须使用此故事板.不幸的是,虽然UIApplication保留在主故事板上,但它并没有公开API来获取它.但是,每个视图控制器都有一个方法,storyboard以获取它创建的故事板.

如果您要加载自己的故事板,那么只需实例化MyStoryboard.如果您使用的是默认情节提要,那么您需要强制系统使用您的特殊故事板.同样,有很多方法可以做到这一点.一种简单的方法是覆盖视图控制器中的storyboard访问器方法.

您可以将MyStoryboard变成一个代理类,将所有内容转发到UIStoryboard,或者您可以调整主故事板,或者您可以让本地控制器从其storyboard方法中返回一个.

现在,请记住,这里有一个问题.如果您多次在堆栈上推送相同的视图控制器怎么办?使用缓存,将多次使用完全相同的视图控制器对象.这真的是你想要的吗?

如果没有,那么您现在需要管理与控制器容器本身的交互,以便他们可以检查它们是否已经知道该控制器,在这种情况下需要新的实例.

因此,有一种方法,而使用默认的故事板塞格斯(实际上有相当多的方式),以获得高速缓存控制器...但是,这并不一定是好事,当然不是你在默认情况下得到的.