Disclaimer: This post is purely experimental based on an idea I had recently, I’m personally not using this in production right now.
Laziness in Swift can be a very powerful tool, but it hasn’t reached its full potential yet. Recently, I came across a situation where I wanted to write something likes this:
class Foo {
lazy let bar: Bar
init(bar: () -> Bar) {
self.bar = bar
}
}
let foo = Foo(bar: { Bar() })
In other words: I wanted to pass the closure for creating a lazy property to the initializer of my class and make it a constant property. This doesn’t work because of two reasons:
With those constraints, our only option is to use existing features to make this work. Here’s what I expect from my implementation:
No 3 will be easy to satisfy by using Generics. Due to No 1 & 2 it will be necessary to use a
class LazyObject<T> {
var object: T {
if _object == nil {
_object = self.getter()
}
return _object!
}
private var _object: T?
private let getter: () -> T
init(getter: @escaping () -> T) {
self.getter = getter
}
}
Using our
let bar = LazyObject() { Bar() }
print(bar) // will be LazyObject<Bar>
print(bar.object) // will be Bar
Only on the last line the closure will actually be evaluated and our
So far so good. What about structs though? Right now, our wrapper doesn’t allow mutating the underlying
class LazyObject<T> {
…
fileprivate var _object: T?
…
}
class MutableLazyObject<T>: LazyObject<T> {
func use(_ block: (inout T) -> Void) {
var object = self.object
block(&object)
_object = object
}
}
By moving the
let bar = MutableLazyObject() { Bar() }
bar.use { $0.message = "hello" }
Unfortunately though, using the
@inline(__always) func lazy<T>(_ getter: @autoclosure @escaping () -> T) -> LazyObject<T> {
return LazyObject(getter: getter)
}
This function allows us to use the shorthand syntax
The full code, including tests, is available on GitHub.