Skip to content

Instantly share code, notes, and snippets.

@sharplet
Last active May 11, 2019 13:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sharplet/4f23ac034d94c0e94b476d8a4ca5565c to your computer and use it in GitHub Desktop.
Save sharplet/4f23ac034d94c0e94b476d8a4ca5565c to your computer and use it in GitHub Desktop.
Mutable property lenses for ReactiveSwift with key paths
struct User {
var name: String
}
let currentUser = MutableProperty(User(name: "Foo"))
currentUser[\.name] <~ nameTextField.reactive.textValues
import ReactiveSwift
import Result
final class MutablePropertyView<Base: ComposableMutablePropertyProtocol, Derived>: ComposableMutablePropertyProtocol {
private let property: Property<Derived>
private let _modify: (_ action: (inout Derived) -> Result<Any, AnyError>) -> Result<Any, AnyError>
let lifetime: Lifetime
let producer: SignalProducer<Derived, NoError>
let signal: Signal<Derived, NoError>
init(base: Base, keyPath: WritableKeyPath<Base.Value, Derived>) {
property = base.map(keyPath)
_modify = { [weak base, property] action in
if let base = base {
return base.modify { newValue in
action(&newValue[keyPath: keyPath])
}
} else {
// The underlying storage disappeared, so use the latest cached value and discard any change.
var lastValue = property.value
return action(&lastValue)
}
}
lifetime = base.lifetime
producer = property.producer
signal = property.signal
// Keep this view alive as long as the base property,
// to allow binding to a view without retaining it:
//
// foo[\.bar] <~ self.bar
base.lifetime.observeEnded { _ = self }
}
// Using the type checker workaround for rethrows described in https://oleb.net/blog/2018/02/performandwait/
func modify<Result>(_ action: (inout Derived) throws -> Result) rethrows -> Result {
return try modifyHelper(execute: action, rescue: { throw $0 })
}
private func modifyHelper<T>(
execute modify: (inout Derived) throws -> T,
rescue: (Error) throws -> T
) rethrows -> T {
let result = _modify { value in
do {
return .success(try modify(&value))
} catch {
return .failure(AnyError(error))
}
}
switch result {
case let .success(value):
return value as! T
case let .failure(error):
return try rescue(error.error)
}
}
}
extension MutablePropertyProtocol where Self: ComposableMutablePropertyProtocol {
var value: Value {
get { return withValue { $0 } }
set { modify { $0 = newValue } }
}
func withValue<Result>(_ action: (Value) throws -> Result) rethrows -> Result {
return try modify { try action($0) }
}
}
extension ComposableMutablePropertyProtocol {
subscript<U>(keyPath: WritableKeyPath<Value, U>) -> MutablePropertyView<Self, U> {
return MutablePropertyView(base: self, keyPath: keyPath)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment