Name of a variable passed as a parameter or a generic type

Hello everybody,

I'm relatively new to Swift development, and thanks for any help. So far I've been in C # and there is CallerMemberName for my problem.

Thank you very much

@propertyWrapper
public struct ChangeValue {
public let viewModelName: String
public let propertyName: String
private var cachedValue: Value?
public init (viewModelName: String = #function , key: String) {
self .viewModelName = viewModelName
**// I would like to know what the name of the generic type is (property from ViewModel! I always only get "String"
self .propertyName = String(describing: Value. self )
}
public var wrappedValue: Value {
mutating get {
if cachedValue == nil { fetch() }
return cachedValue!
}
}
public mutating func fetch() {
cachedValue = nil
}
}

Solution in C # would be [CallerMemberName]*

protected bool UpdateProperty(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(field, value)) { return false; }
field = value;
RaisePropertyChanged(propertyName);
return true;
}

Could you pls format the code (using triple backticks or indentation)? Its really hard to read.
Your comment says you want "name of the generic type", but there are no generic types in the example. My guess is that Value is supposed to be a generic type.

Do you want to get a name of the property or a name of property type? Given struct Foo { var bar: Qux }, do you need bar or Qux?

1 Like

Sorry it was my first post. Next time I will do it.

I want to write a propertyWrapper that checks whether the value of a variable has changed.

Currently I am passing the property name as the "key" parameter. I would like to have this determined in the propertyWrapper, as with ViewModelName via "#function", this is the idea so that the call can look like this:

@TrackChanges var firstName: String

I am currently using

@TrackChanges(key: "firstName") public var firstName: String

which works of course, but I would like the other way much better:

@propertyWrapper
struct TrackChange<Value: Codable> {
private let viewModelName: String
private let propertyName: String
var value: Value? = nil

private let changeTracker = ChangeTracker.shared

public init(viewModelName: String = #function, key: String) {
    self.viewModelName = viewModelName
    self.propertyName = key
    
}

var wrappedValue: Value {
    get {
        value!
    }
    set {
        value = newValue
    }
}

}

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 https://github.com/wickwirew/Runtime or similar library, one can get a property name. In https://github.com/wickwirew/Runtime 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) }
    }
}

Thanks for the support, was very helpful

Terms of Service

Privacy Policy

Cookie Policy