简体   繁体   中英

viewDidLoad is in fact called every time there is a segue transition

I have seen a lot of posts on stack overflow stating that the viewDidLoad method of controllers is only called the first time the controller is accessed and not necessarily every time but always at least once.

This is not what I am seeing at all! I put together a simple test to highlight this: https://github.com/imuz/ViewDidLoadTest

It seems for navigation controller segues and modal views viewDidLoad is always called. The only time it is not called is when switching between tabs.

Every explanation of viewDidLoad I can find contradicts this:

And apples own documentation indicate that a view is only unloaded when memory is low.

I am currently doing initialisation in viewDidLoad making the assumption that it is called with every segue transition.

Am I missing something here?

I believe the Apple documentation is describing a situation where the view controller is not being deallocated. If you use a segue, then you are causing the instantiation of a new destination controller and, being a new object, it needs to load a view.

In xib-based apps, I have sometimes cached a controller object that I knew I might re-use frequently. In those cases, they behaved in keeping with the documentation in terms of when a view had to be loaded.

Edit: On reading the links you included, I don't see any contradiction in them. They, too, are talking about things that happen during the lifespan of a view controller object.

Phillip Mills' answer is correct. This is just an enhancement of it.

The system is working as documented.

You are seeing viewDidLoad because the view controller being pushed onto the navigation controller is a new instance. It must call viewDidLoad.

If you investigate a little further, you would see that each of those view controllers are deallocated when they are popped (just put a breakpoint or NSLog in dealloc). This deallocation has nothing to do with the view controller container... it does not control the life of the controller it uses... it is just holding a strong reference to it.

When the controller is popped off the navigation controller stack, the nav controller releases its reference, and since there are no other references to it, the view controller will dealloc.

The navigation controller only holds strong references to view controllers that are in its active stack.

If you want to reuse the same controller, you are responsible for reusing it. When you use storyboard segues, you relinquish that control (to a large extent).

Let's say you have a push segue to view controller Foo as the result of tapping some button. When that button is tapped, "the system" will create an instance of Foo (the destination view controller), and then perform the segue. The controller container now holds the only strong reference to that view controller. Once it's done with it, the VC will dealloc.

Since it creates a new controller each time, viewDidLoad will be called each time that controller is presented.

Now, if you want to change this behavior, and cache the view controller for later reuse, you have to do that specifically. If you don't use storyboard segues, it's easy since you are actually pushing/popping the VC to the nav controller.

If, however, you use storyboard segues, it's a bit more trouble.

There are a number of ways to do it, but all require some form of hacking. The storyboard itself is in charge of instantiating new view controllers. One way is to override instantiateViewControllerWithIdentifier . That is the method that gets called when a segue needs to create a view controller. It's called even for controllers that you don't give an identifier to (the system provides a made-up unique identifier if you don't assign one).

Note, I hope this is mostly for educational purposes. I'm certainly not suggesting this as the best way to resolve your problems, whatever they may be.

Something like...

@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

Now, you have to use this storyboard. Unfortunately, while UIApplication holds onto the main storyboard, it does not expose an API to get it. However, each view controller has a method, storyboard to get the storyboard it was created from.

If you are loading your own storyboards, then just instantiate MyStoryboard. If you are using the default storyboard, then you need to force the system to use your special one. Again, there are lots of ways to do this. One simple way is to override the storyboard accessor method in the view controller.

You can make MyStoryboard be a proxy class that forwards everything to UIStoryboard, or you can isa-swizzle the main storyboard, or you can just have your local controller return one from its storyboard method.

Now, remember, there is a problem here. What if you push the same view controller on the stack more than once? With a cache, the exact same view controller object will be used multiple times. Is that really what you want?

If not, then you now need to manage interaction with the controller containers themselves so they can check to see if this controller is already known by them, in which case a new instance is necessary.

So, there is a way to get cached controllers while using default storyboard segues (actually there are quite a few ways)... but that is not necessarily a good thing, and certainly not what you get by default.

It is called every time the controller's view is loaded from scratch (ie requested but not yet available). If you deallocate the controller and the view goes along with it, then it will be called again the next time you instantiate the controller (for example when you create the controller to push it modally or via segue). View controllers in tabs are not deallocated because the tab controller keeps them around.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM