Reputation: 11880
I need a Swift property that -- if the value has not yet been set -- defaults to another value.
This can be implemented using backing-store private properties. For instance, for a property num
that should default to a global defaultNum
, it would work something like this:
var defaultNum = 1
class MyClass {
var num: Int {
get { _num ?? defaultNum }
set { _num = newValue }
}
private var _num: Int?
}
let c = MyClass()
print("initial \(c.num)") // == 1 ✅
// changing the default changes the value returned
defaultNum = 2
print("dynamic \(c.num)") // == 2 ✅
// once the property is set, returns the stored value
c.num = 5
print("base \(c.num)") // == 5 ✅
That works, but for a common pattern in our code, it's a lot of boilerplate for each such property.
Using Swift property wrappers, is it possible to do this more concisely?
Note that, because we expect the default to be dynamic, static initializers will not work. For example:
var defaultNum = 1
class MyClass {
var num = defaultNum
}
var c = MyClass()
defaultNum = 2
print(c.num) // this == 1, we want the current value of defaultNum, which == 2
Upvotes: 1
Views: 983
Reputation: 11880
You can do this by creating a property wrapper like this:
@propertyWrapper
public struct Default<T> {
var baseValue: T?
var closure: () -> T
// this allows a nicer syntax for single variables...
public init(_ closure: @autoclosure @escaping () -> T) {
self.closure = closure
}
// ... and if we want to explicitly use a closure, we can.
public init(_ closure: @escaping () -> T) {
self.closure = closure
}
public var wrappedValue: T {
get { baseValue ?? closure() }
set { baseValue = newValue }
}
}
You then use the @Default
property wrapper on a property like this:
var defaultNum = 1
class MyClass {
@Default(defaultNum)
var num: Int
}
You'd then see the following in practice:
let c = MyClass()
// if we haven't set the property yet, it uses the closure to return a default value
print("initial \(c.num)") // == 1 ✅
// because we are using a closure, changing the default changes the value returned
defaultNum = 2
print("dynamic \(c.num)") // == 2 ✅
// once the property is set, uses the stored base value
c.num = 5
print("base \(c.num)") // == 5 ✅
Upvotes: 5