A general piece of advice (that might not apply in your case): have your didSet/willSet/setter just set a “dirty” flag which schedules some work to be done “soon” and handle the expensive updating or invalidation there. This avoids the overhead you’re thinking of if the setter is called several times redundantly.
I wasn't able finding (†) anything better than a good old if a == nil { a = b } to avoid unwanted didSet triggering. Perhaps a macro could do that (didn't try).
† without resorting to hacks
infix operator ?= : AssignmentPrecedence
func ?= <T>(p: UnsafePointer<T?>, value: @autoclosure () -> T?) {
let ptr = UnsafeMutablePointer(mutating: p)
if ptr.pointee == nil {
ptr.pointee = value()
}
}
struct S {
var x: Int? {
didSet {
print("didSet from \(oldValue) to \(x)")
}
}
}
var s = S(x: nil)
&s.x ?= 42
print("result", s.x) // 42, yet didSet wasn't called
That spelling is problematic. Never use! ?= will be interpreted instead as ? = when used without surrounding whitespace.
var x: Int? = nil
x? = 1 // This is rather obviously not using the operator.
x?=1 // This is also not using the operator, but that's not obvious.
#expect(x == nil)
x ?= 1 // This is using the operator.
#expect(x == 1)
I was just bringing up ?= as a good example of what Swift's operators can do (compared to other programming languages). I had no idea that x? = 1 was valid syntax. At least my projects are consistent with always using whitespace between operators, so this never happened to me
Using postfix operators that start with a dot might lead to an interesting form of code.
Such operators could be read as side-effect-free computed properties.
postfix operator .+
postfix operator .-
postfix func .+ (n: Int) -> Int {
return n + 1
}
postfix func .- (n: Int) -> Int {
return n - 1
}
let a = 1
let x = a.+ * 10
let y = 1.+ * 10.-