In Objective-C, we may want to initialize some properties lazily as they may only be accessed at a future time, we don't want to block the init() call with a long-running operation, or when a required instance property will only be accessible at a later time.
In Swift, we are blessed by the presence of the lazy keyword.
Let's start with this code and see how we can improve it:
class MyView: NSView {
let image: NSImage!
let imageView = NSImageView(frame: .zero)
init(imageName: NSImage.Name, frame: NSRect) {
image = NSImage(named: imageName)
super.init(frame: frame)
}
func setup() {
addSubview(imageView)
imageView.frame = bounds.insetBy(dx: 5.0, dy: 5.0)
imageView.image = image
imageView.imageAlignment = .alignCenter
}
}
The previous code isn't completely bad, but we can refactor it so we have better performance, readability, and maintainability.
The first thing we may want to do is to leverage lazy for the image property. There's probably no need to perform it at initialization:
class MyView: NSView {
private let imageName: NSImage.Name
lazy var image = NSImage(named: imageName)
let imageView = NSImageView(frame: .zero)
init(imageName: NSImage.Name, frame: NSRect) {
self.imageName = imageName
super.init(frame: frame)
}
/* original code */
}
Now, when you create a new instance of MyView, the image is not initialized; it will be when setup() is called.
We can also fix imageView:
class MyView: NSView {
private let imageName: NSImage.Name
lazy var image = NSImage(named: imageName)
lazy var imageView = NSImageView(frame: self.bounds.insetBy(dx: 5.0, dy: 5.0))
init(imageName: NSImage.Name, frame: NSRect) {
self.imageName = imageName
super.init(frame: frame)
}
func setup() {
addSubview(imageView)
imageView.image = image
imageView.imageAlignment = .alignCenter
}
}
This is getting better! The last step would be to completely configure imageView at initialization time:
class MyView: NSView {
private let imageName: NSImage.Name
lazy var image = NSImage(named: imageName)
lazy var imageView: NSImageView = {
let frame = self.bounds.insetBy(dx: 5.0, dy: 5.0)
let imageView = NSImageView(frame: frame)
imageView.image = self.image
imageView.imageAlignment = .alignCenter
return imageView
}()
init(imageName: NSImage.Name, frame: NSRect) {
self.imageName = imageName
super.init(frame: frame)
}
func setup() {
addSubview(imageView)
}
}
Now, our setup() method is nice and clean and, within the imageView property, it becomes fully initialized. This is probably one of my favorite initialization patterns in Swift as it lets you configure a property completely, before it's consumed by the called.
private func getImageView(image: NSImage, bounds: NSRect) -> NSImageView { /* */ }
let imageView = getImageView(image: self.image, bounds: self.bounds)