I have recently been experimenting with swift property wrappers and wondered if there was any way of composing them together in order to achieve a more modular architecture. E.g:
@WrapperOne @WrapperTwo var foo: T
Looking through the documentation yielded nothing. The only reference to how to do this is on this GitHub page (https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md) (quotes below) which seems to say it is possible. Other articles have said that they are difficult to compose but have not explained how to go about doing this. However, I can't make heads or tails of it and would appreciate it if someone could show me some example code on how to implement this (see bottom of post).
When multiple property wrappers are provided for a given property, the wrappers are composed together to get both effects. For example, consider the composition of
DelayedMutable
andCopying
:@DelayedMutable @Copying var path: UIBezierPath
Here, we have a property for which we can delay initialization until later. When we do set a value, it will be copied via
NSCopying
's copy method. Composition is implemented by nesting later wrapper types inside earlier wrapper types, where the innermost nested type is the original property's type. For the example above, the backing storage will be of typeDelayedMutable<Copying<UIBezierPath>>
and the synthesized getter/setter for path will look through both levels of .wrappedValue:private var _path: DelayedMutable<Copying<UIBezierPath>> = .init() var path: UIBezierPath { get { return _path.wrappedValue.wrappedValue } set { _path.wrappedValue.wrappedValue = newValue } }
Note that this design means that property wrapper composition is not commutative, because the order of the attributes affects how the nesting is performed: @DelayedMutable @Copying var path1: UIBezierPath // _path1 has type DelayedMutable> @Copying @DelayedMutable var path2: UIBezierPath // error: _path2 has ill-formed type Copying> In this case, the type checker prevents the second ordering, because
DelayedMutable
does not conform to theNSCopying
protocol. This won't always be the case: some semantically-bad compositions won't necessarily by caught by the type system. Alternatives to this approach to composition are presented in "Alternatives considered."
Ideally, I would like to implement something like the following:
@propertyWrapper
struct Doubled {
var number: Int
var wrappedValue: Int {
get { (value * 2) }
set { value = Int(newValue / 2) }
}
init(wrappedValue: Int) {
self.number = wrappedValue
}
}
and
@propertyWrapper
struct Tripled {
var number: Int
var wrappedValue: Int {
get { (value * 3) }
set { value = Int(newValue / 3) }
}
init(wrappedValue: Int) {
self.number = wrappedValue
}
}
so that this could be achieved:
@Tripled @Doubled var number = 5
I understand that this example is a somewhat silly reason to implement property wrapper composition but this is merely for simplicity's sake when learning a new feature.
Any help would be greatly appreciated.