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)}
)
Joe_Groff
(Joe Groff)
2
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.
Joe_Groff
(Joe Groff)
4
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