TL;DR No solution, explicit key
is the way to go.
Thinking out loud:
For #function
to capture the name of the property, if should be used in something which is used from the synthesised getter or setter. In case of the property wrappers, there are two things which get called from the getter and/or setter: property wrappedValue
and static subscript(_enclosingInstance:wrapped:storage:)
, but not the initializer. Properties cannot have params. So we are left only with the subscript.
Using Xcode 12.1 and swift 5.3, the following does not work:
@propertyWrapper
struct Observable<Value> {
private var stored: Value
init(wrappedValue: Value) {
self.stored = wrappedValue
}
@available(*, unavailable, message: "must be in a class")
var wrappedValue: Value {
get { fatalError("called wrappedValue getter") }
set { fatalError("called wrappedValue setter") }
}
static subscript<EnclosingSelf>(
_enclosingInstance observed: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>,
caller: String = #function
) -> Value {
get {
print(caller)
return observed[keyPath: storageKeyPath].stored
}
set {
print(caller)
observed[keyPath: storageKeyPath].stored = newValue
}
}
}
class Foo {
@Observable var bar: Int = 99 // error: 'wrappedValue' is unavailable: must be in a class
}
Another idea:
In the subscript we still have storage key path, which uniquely identifies our property, but does not give its name. On the other side, using GitHub - wickwirew/Runtime: A Swift Runtime library for viewing type info, and the dynamic getting and setting of properties. or similar library, one can get a property name. In GitHub - wickwirew/Runtime: A Swift Runtime library for viewing type info, and the dynamic getting and setting of properties. there is PropertyInfo.offset
. If we can also get offset from the key path, we could match PropertyInfo
with the key path and get a name.
To get an offset from the key path, there is a method MemoryLayout.offset(of:)
, but it works only for structs, while static subscript(_enclosingInstance:wrapped:storage:)
works only for classes.
Technically it should be possible to dig into the key path encoding using unsafe pointers and find there offset inside the class, but this is challenging and may break if encoding format changes in the future.
In the summary, explicitly passing the key as a string is the simplest solution.
Regarding the C# example - do you call UpdateProperty()
explicitly in C#? If that works for you in Swift, you can still do same:
class ViewModel {
func updateProperty<T>(field: inout T, value: T, caller: String = #function) {
print(caller)
}
private var _foo: String = ""
var foo: String {
get { _foo }
set { updateProperty(field: &_foo, value: newValue) }
}
}