The difference has to do with the timing of when those variables' values are set up. Both labelOne
and stackView
are instance members on your ViewController, and both will get a value that's returned from a closure (from the surrounding { … }()
syntax).
However, a "regular" property declared this way will run the closure as soon as your ViewController is initialized, while a lazy property will wait to run the closure until the property is first accessed — well after initialization. The practical difference between these is that self
is available to closures run lazily, but self
is not available to closures run at initialization time. (The self
that you'd use is still being initialized!)
The other gotcha here is that it might not even look like you're accessing self
in either closure! However, in Swift, whenever you access a member property, it implicitly uses self
— so your reference to labelOne
inside the second closure is really to self.labelOne
, which is where the requirement that self
be fully initialized comes from. Making that closure lazy
lets self
get ready before it's evaluated, which allows your code to compile.
The usual alternate approach here is to explicitly initialize all your view controller's subviews inside viewDidLoad()
, and declare the properties themselves as implicitly unwrapped optionals (IUOs). This would look something like:
class ViewController: UIViewController {
var labelOne: UILabel!
var stackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
labelOne = …
stackView = …
}
}
This has been called "two-stage initialization," and is implicitly recommended by Apple's View Controller Programming Guide:
Calls the view controller’s viewDidLoad
method.
Use this method to add or remove views, modify layout constraints, and load data for your views.