菲利普米尔斯的回答是正确的。这只是对它的增强。
系统按记录工作。
您看到 viewDidLoad 是因为被推送到导航控制器上的视图控制器是一个新实例。它必须调用 viewDidLoad。
如果您进一步调查,您会看到这些视图控制器中的每一个在弹出时都被释放(只需在 dealloc 中放置一个断点或 NSLog)。这个释放与视图控制器容器无关......它不控制它使用的控制器的生命......它只是持有对它的强引用。
当控制器从导航控制器堆栈中弹出时,导航控制器释放它的引用,并且由于没有其他对它的引用,视图控制器将释放。
导航控制器仅持有对其活动堆栈中的视图控制器的强引用。
如果你想重用同一个控制器,你有责任重用它。当您使用故事板转场时,您(在很大程度上)放弃了该控制。
假设您通过点击某个按钮push
来查看控制器Foo
。当点击该按钮时,“系统”将创建Foo
(目标视图控制器)的实例,然后执行转场。控制器容器现在拥有对该视图控制器的唯一强引用。完成后,VC 将解除分配。
由于它每次都会创建一个新控制器,viewDidLoad
因此每次呈现该控制器时都会调用它。
现在,如果你想改变这种行为,并缓存视图控制器以供以后重用,你必须专门这样做。如果您不使用故事板转场,这很容易,因为您实际上是将 VC 推送/弹出到导航控制器。
但是,如果您使用故事板转场,那就有点麻烦了。
有很多方法可以做到这一点,但都需要某种形式的黑客攻击。故事板本身负责实例化新的视图控制器。一种方法是覆盖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-swizzle,或者您可以让本地控制器从其故事板方法中返回一个。
现在,请记住,这里有一个问题。如果你将同一个视图控制器多次推送到堆栈上怎么办?使用缓存,完全相同的视图控制器对象将被多次使用。这真的是你想要的吗?
如果不是,那么您现在需要管理与控制器容器本身的交互,以便它们可以检查该控制器是否已为它们所知,在这种情况下,需要一个新实例。
因此,有一种方法可以在使用默认故事板 segues 的同时获取缓存的控制器(实际上有很多方法)......但这不一定是一件好事,当然也不是你默认得到的。