In a tutorial I created last year, I described advantages of programmatic layout (PL) over Interface Builder (IB). I recently became aware of another: PL permits use of non-optional, constant properties of view controllers. IB does not. I share this advantage here for the benefit of readers.
Definitions
Apple’s Swift book calls properties declared with let
“constant” properties and calls properties declared with var
“variable” properties. These two types of properties are immutable and mutable, respectively. I adopt Apple’s terminology here.
One Use of a View-Controller Property
One of the view controllers in my tutorial is BreedDetailVC, shown in the screenshot below. The purpose of this view controller is to show information about a specific cat breed.
Unsurprisingly, BreedDetailVC
’s model is an instance of Breed
, breed
. Here is the declaration BreedDetailVC
’s model property:
private var breed: Breed!
Note that the model is declared as a variable property and as an implicitly unwrapped optional (IUO). In the fullness of time, I have come to realize that IUOs and variable properties (in this case) are problematic.
With respect to the use of a variable property, the code violates the overwhelming preference in the Swift community for immutable state. As described in the blog posts and StackOverflow answer referenced in the preceding sentence, immutable state is easier to reason about and less prone to concurrency issues. Once BreedDetailVC
’s model is set, there is no business reason for it to ever change. But it can. One developer might write code that assumes that the model never will change, but another developer might write code that violates this assumption. In the absence of a business requirement, the cost of this mutability is, in my view, unacceptable.
IUOs are a shorthand for optionals that fatalError()
when accessed before they are set. This shorthand, the IUO, is controversial, as evidenced by the fact that SwiftLint warns about them, albeit not by default. As Paul Hudson put it, “Broadly speaking, you should avoid implicitly unwrapped optionals unless you’re certain they are safe – and even then you should think twice.”
Because of the controversial nature of IUOs, I avoid them in one of my public-facing repos, Conjugar. A use case of IUOs that remains uncontroversial, as far as I can tell, is in the declarations of outlets. The acceptance of this particular use of IUOs by the Swift-iOS-developer community probably stems from the fact that Xcode and IB automatically create IUOs when developers connect outlets from XIBs and storyboards to code. Why do Xcode and IB automatically create IUO outlets? Given the Swift initializer rule, view-controller properties must be IUOs or optionals. Given the controversial nature of IUOs, why not suggest optional outlets? To do so would necessitate guard
ing or force-unwrapping upon every access. Whomever at Apple made the decision to have Xcode and IB create IUOs was respecting the swift-initializer rule and saving developers the trouble of dealing with optional outlets.
Making breed
a variable-property IUO prevents the following compilation error:
The cause of this error would be a related rule of Swift initializers: that non-optional, non-IUO constant properties must be initialized by the end of a type’s initializer. But breed
can’t be initialized by the end of BreedDetailVC
’s initializer because, when the developer uses IB to construct this user interface, the developer doesn’t call BreedDetailVC
’s initializer directly. Instead, the runtime calls the initializer, and the runtime doesn’t know how to initialize developer-defined properties.
This analysis illustrates the PL advantage that is the subject of this post: that, unlike PL, IB prevents use of non-optional, constant view-controller properties. This prevention is in tension with the fact that non-optional, constant properties are sometimes appropriate, given the benefits of immutable state, and idiomatic, given the controversial nature of IUOs. During my initial purge of IUOs from Conjugar, I was not mindful of this advantage, and I just made IUO view-controller properties optionals. Every time I then accessed those properties, I unwrapped the optionals using guard
statements. But I now realize that PL allows view-controller properties to be non-optional. No guard
boilerplate is required. Here is how that looks for BreedDetailVC
:
class BreedDetailVC: UIViewController {
private let breed: Breed
...
init(breed: Breed) {
self.breed = breed
// 1
super.init(nibName: nil, bundle: nil)
}
// 0
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
...
}
For the curious, this is the implementation of this approach in Conjugar.
In the BreedDetailVC
implementation, breed
is a non-optional, constant property, and there is no need for unwrapping or guard
ing to access it. There are still two required bits of boilerplate, noted in the comments // 0
and // 1
. I address them here.
0. Without this initializer, the compiler emits the following error: 'required' initializer 'init(coder:)' must be provided by subclass of 'UIViewController'
. Fortunately, there is a fixit to insert this initializer, and I use it here. FWIW, this is the initializer that the runtime would use if the view controller were being thawed from a XIB or storyboard. I am not entirely pleased with the suggested implementation because this literal String
init(coder:) has not been implemented
would potentially appear in every UIViewController
subclass in the app. This would violate the DRY principle and its goals of preventing typos and facilitating refactoring. In Conjugar, therefore, I created the following UIViewController
extension:
extension UIViewController {
static func fatalErrorNotImplemented() -> Never {
fatalError("init(coder:) has not been implemented")
}
}
The boilerplate initializer now looks like this in Conjugar:
required init?(coder aDecoder: NSCoder) {
UIViewController.fatalErrorNotImplemented()
}
As an aside, the function must be static
because of the rule against calling functions on self
before initialization is complete.
1. This line is required to prevent the following compilation error:
'super.init' isn't called on all paths before returning from initializer
Call for Responses
I ask you, the reader, the following questions. Am I correct about this advantage of PL over IB? Is there a way to have non-optional, non-IUO constant properties of view controllers when using IB? I would like to hear from you and will update this post with your responses.
Colophon
The image at the top of this post has this license and is unchanged from the original.