Last active
May 11, 2019 13:00
-
-
Save sharplet/4f23ac034d94c0e94b476d8a4ca5565c to your computer and use it in GitHub Desktop.
Mutable property lenses for ReactiveSwift with key paths
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct User { | |
var name: String | |
} | |
let currentUser = MutableProperty(User(name: "Foo")) | |
currentUser[\.name] <~ nameTextField.reactive.textValues |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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