Is there a way to access the getter and setters of a computed variable?

Is there a way to access the getter and setters of the computed variable x in the following example (without using reflections):

extension Client {

    var x: String? {
        get { userDefaults().string(forKey: xKey) }
        set { userDefaults().setValue(newValue, forKey: xKey) }
    }

    static func live(
        x: @escaping () -> String? = { nil },
        saveX: @escaping (String) -> Void = { _ in }
    ) { [...] }

    let client = Client.live( 
       x: { x }
       saveX: { x = $0 } 
    )

Is there an elegant way to pass getters and setters to a function?

Is it okay to use Binding outside of a view and just pass bindings around?

var xKey = Binding<String?>(
    get: { UserDefaults.standard.string(forKey: xKey) },
    set: { UserDefaults.standard.setValue($0, forKey: xKey)}
)

You can have live take a WritableKeyPath to the property, along with an object to apply the key path to, instead of a pair of closures

1 Like

Could you please give me a simple example? I am a bit confused.

extension Client {

    var x: String? {
        get { userDefaults().string(forKey: xKey) }
        set { userDefaults().setValue(newValue, forKey: xKey) }
    }

    static func live(instance: Client, property: WritableKeyPath<Client, String?) {
      // value = instance[keyPath: property]
      // instance[keyPath: property] = newValue
    }

    let client = Client.live(self, \.x)
}
3 Likes

Oh I see now, thanks a lot for you help!

I find it a lot more ergonomic though to just pass a Binding<String?>

public extension Client {
  static func live(
    userDefaultsKey: String,
    userDefaults: @escaping() -> UserDefaults
  ) -> Self {
    live(
      sessionKeychainKey: Binding<String?>(
        get: { userDefaults().string(forKey: userDefaultsKey) },
        set: { userDefaults().set($0, forKey: userDefaultsKey) }
      )
    )
  }
  
  static func live(
    sessionKeychainKey keychainKey: Binding<String?> = .constant(nil)
  ) -> Self {
    live(

I can declare my variables inside unamed closures, etc... and create those binding on the fly:

let client = IGClient.live(
  session: Binding<IGClientLiveSession?>(
    get: { session },
    set: { session = $0 }
  )
)

It doesn't seem to be possible to pass a writable key path for a variable that was declared inside an unamed closure or inside a playground. Or am I missing something

Binding is wonderful, but it's overkill and doesn't solve your problem directly. We never got a simpler, more usable variant in the standard library, that isn't SwiftUI-related. Here's mine:

/// A workaround for limitations of Swift's computed properties.
/// 
/// Limitations of Swift's computed property accessors:
/// 1. They are not mutable.
/// 2. They cannot be referenced as closures.
@propertyWrapper public struct Computed<Value> {
  public typealias Get = () -> Value
  public typealias Set = (Value) -> Void

  public init(
    get: @escaping Get,
    set: @escaping Set
  ) {
    self.get = get
    self.set = set
  }

  public var get: Get
  public var set: Set

  public var wrappedValue: Value {
    get { get() }
    set { set(newValue) }
  }

  public var projectedValue: Self {
    get { self }
    set { self = newValue }
  }
}

//MARK:- public
public extension Computed {
  init(
    wrappedValue: Value,
    get: @escaping Get = {
      fatalError("`get` must be assigned before accessing `wrappedValue`.")
    },
    set: @escaping Set
  ) {
    self.init(get: get, set: set)
    self.wrappedValue = wrappedValue
  }
}
struct Structure {
  private(set) static var value: Int = 0

  @Computed( set: { value = $0 } ) var property = 1
}

var value: Int = .random
var instance = Structure()

XCTAssertEqual(Structure.value, 1)
instance.$property = .init
  { value }
  set: { value = $0 }

instance.property = 2
XCTAssertEqual(value, 2)

instance.$property.get = { 3 }
XCTAssertEqual(instance.property, 3)

instance.$property.set = { _ in }
instance.property = 4
XCTAssertNotEqual(4, value)

Similar, for observers:

/// A workaround for limitations of Swift's observed properties.
///
/// Limitations of Swift's property observers:
/// 1. They are not mutable.
/// 2. They cannot be referenced as closures.
@propertyWrapper public struct Observed<Value> {
  public typealias WillSet = (_ newValue: Value) -> Void
  public typealias DidSet = (
    _ oldValue: Value,
    _ value: inout Value
  ) -> Void

  public var willSet: WillSet
  public var didSet: DidSet

  public var wrappedValue: Value {
    willSet { willSet(newValue) }
    didSet { didSet(oldValue, &wrappedValue) }
  }

  public var projectedValue: Self {
    get { self }
    set { self = newValue }
  }
}

//MARK:- public
public extension Observed {
  init(
    wrappedValue: Value,
    willSet: @escaping WillSet = { _ in },
    didSet: @escaping DidSet = { _, _ in }
  ) {
    self.wrappedValue = wrappedValue
    self.willSet = willSet
    self.didSet = didSet
  }
}
1 Like