iosswiftnibuinib

Why outlet cannot be init within initWithCoder?


We all know that as soon we drop an outlet inside a View or its ViewController, it is being marked as unwrapped and we all know that Swift wants to initialise all the properties within the initialisation phase and this is the sentence we give to whoever asks us for the first time why an outlet is always in company with the exclamation mark.

Today I was trying to understand why an object that comes from a XIB, cannot be initialised within the initWithCoder: method.

As far as I know, a XIB file just contains all the information about the objects drawn internally at the XIB using an XML file structure. So what we see inside the XIB file is gonna be archived and stored into a file.

When we will call the UINib loadNibNamed:owner:options: class method, it will unarchive the object created previously, look up for all the properties, set them and send the message awakeFromNib to the object...

but due to that exclamation mark that says "During the init phase I'm not able to init you" what I've said above should be incorrect.. But why? Can someone tell me why Nib cannot be init and should be marked as optional?

Here I have some docs from Apple that didn't help me https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html


Solution

  • Your @IBOutlet properties are introduced by your UIViewController subclass.

    Saying that "Swift wants to initialise all the properties within the initialisation phase" is oversimplifying a bit.

    The Swift initialisation rules state that all properties introduced by a subclass must be initialised before calling the superclass initialiser, and the Swift compiler must be able to "see" this initialisation; there must be an explicit assignment. This is "Safety Check 1":-

    Safety check 1

    A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.

    In almost all cases where you are using a XIB or storyboard scene you don't override init(coder:), so the compiler can determine that you haven't explicitly assigned values to these properties.

    If you did override the initialisers and assign values (or even if you simply assigned default values when you declared the properties) then you could make them normal properties rather than implicitly unwrapped optionals, but that would be a bit pointless since you would almost immediately overwrite those values when the XIB is loaded.

    An implicitly unwrapped optional doesn't say "During the init phase I'm not able to init you"; It is more like "I know it looks like this isn't being initialised, but at runtime it will be. Trust me" (Strictly speaking it is just declaring an optional, which is allowed to be nil, so the compiler doesn't complain that it isn't initialised, but implicitly force-unwraps the property whenever it is referenced - hence the name, "implicitly unwrapped optional").

    This works for @IBOutlets because the loading process uses Key Value Coding to assign values at runtime.

    This is the reason why, if you remove an @IBOutlet but forget to update the XIB/Storyboard you get a runtime exception stating that your class is "not key/value compliant for xxx".

    The use of implicitly unwrapped optionals in this way is generally considered acceptable as you will pretty quickly find out if you have a connection problem during your testing (because your app will crash with an "unexpectedly nil") and it saves a lot of conditional unwrapping.