When an instance of UIViewController
is created, its view and all the IBOutlet
are not loaded until it's required.
This means that, unlike all the other classes, having a fully initialized instance doesn't imply that all the properties are initialized and loaded.
This is unique behaviour, similar to async loading but without any delegate or callback method upon absolute completion of loading and initialized.
There are no other objects in Cocoa(Touch) that follow this same logic, even loading a UIView
from a nib
is synced.
Therefore is it fair to say the naming of the viewDidLoad method is misleading and/or incorrect convention within the context of Apple's other APIs?
View controller loading is a case of the pattern commonly known as delayed initialization. And this pattern isn't unique to view controllers: it occurs in several places in Cocoa, mostly related to nib/storyboard-based UI. For example, any custom class (such as a UIView
subclass, or NSView
subclass on macOS) that you put in a nib isn't fully "ready" for you until its awakeFromNib
method is called.
When you load a nib or storyboard (or when the view controller system does so on your behalf), Cocoa (or Cocoa touch) initializes objects from their archived forms in the nib, then goes through each setting the values of any IBOutlet
properties. Because that is a two-step process (and has to be, because archiving can't preserve links to other objects in the way that nibs need), there's an intermediate time where objects exist (that is, they've finished their init
methods and super.init
chains) but aren't fully set up to match the configuration you created in Interface Builder.
There seems to be some confusion here about just what "initialized" means. The requirement of the Swift language, and strong recommendation in ObjC, is that all properties / instance variable have a defined value by the time the initializer chain is finished running. To put it another way, if you get the value of a property or instance variable after initialization, by reading your code you should know what that value will be.
This definition stands in contrast to the behavior of non-ARC Objective-C: if you don't assign to instance variables during initialization, their values are undefined. They could be zero, they could be garbage, they could be discarded memory from something else.
ObjC with ARC partially enforces a solution to this issue in making sure that all variables are initialized to zero/nil even if you don't manually assign to them during initialization. And Swift enforces it further by requiring at compile time that you manually provide values in initialization (either by assigning to properties where they're declared, by assigning to them inside your init
definition, or by declaring them as Optionals to make their default value nil).
So, when you're dealing with something that comes from a nib or storyboard, having a fully initialized instance means that all properties are "initialized" per the language definition — that is, their values are known. It doesn't necessarily mean that their values are "what you want them to be".
In particular, view controllers are intended to be used in delayed-initialization scenarios that last longer than just "during storyboard loading". For example, you can set up a whole interconnected network of view controllers (in a navigation stack, tab views, master/detail split views, etc). Initially, those VC instances are initialized and know some of their non-IBOutlet properties (such as their titles, for labeling inactive tabs in a tab bar). But the expensive UI infrastructure doesn't need to load until the system asks for the VC's view (typically when the user is about to see it).