New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Include KeyPath subscripts on Property types #613
Comments
Interesting: on the left hand side of |
If the intention is just to support bindings, It can be a retaining view with a custom binding target implementation. There was a very similar proposal before for |
How's this for a start? This is specifically not retaining the base property, so that it has the same lifetime. final class MutablePropertyView<Base, Derived>: MutablePropertyProtocol {
typealias Value = Derived
let lifetime: Lifetime
let producer: SignalProducer<Derived, NoError>
let signal: Signal<Derived, NoError>
private weak var base: MutableProperty<Base>?
private let cache: Property<Derived>
private let keyPath: WritableKeyPath<Base, Derived>
init(base: MutableProperty<Base>, keyPath: WritableKeyPath<Base, Derived>) {
self.base = base
self.keyPath = keyPath
cache = base.map(keyPath)
lifetime = base.lifetime
producer = cache.producer
signal = cache.signal
base.lifetime.observeEnded { _ = self }
}
var value: Derived {
get { return cache.value }
set { base?.modify { $0[keyPath: keyPath] = newValue } }
}
}
extension MutableProperty {
subscript<U>(keyPath: WritableKeyPath<Value, U>) -> MutablePropertyView<Value, U> {
return MutablePropertyView(base: self, keyPath: keyPath)
}
} I'm not sure I have the bandwidth to drive the implementation this, but I had need for something like this in my current project so thought I'd share in case someone wants to work on it. |
I don't see much of a concern for a view to strongly retain its backing property. |
So I tried a version where the view retains the base property, but that causes problems with this kind of thing: property[\.name] <~ someBindingSource In this expression, unless something retains the view, the whole binding will immediately complete. So in my example above, the base property retains the view until the end if its lifetime. If you then make the view strongly reference the base property, you have a leak. I haven't figured out a way to break this cycle yet. So, it's possible to conform |
Ok, I figured out how to get it to conform to |
I should probably just open a PR with what I have at this point, so we can more easily discuss the finer points of the implementation. |
The view can supply a manual binding target — it has the access to the backing property to derive a binding target from it, instead of relying on the default implementation. |
Ok, so I think I understand what you mean now: the view retains the base property, but the binding target does not, so when the view immediately is deallocated, the binding target continues with the same lifetime as the base property. However, I quickly discovered in my use case that I wanted the ability to keep these views around, and created a type-erased wrapper: let username: AnyMutableProperty<String> = AnyMutableProperty(user[\.name) This is really useful, but definitely complicates the object graph. Given the precedent of |
If it is meant to be an existential wrapper, it should be strong after all as to align with the semantics of generalised existential when it lands in Swift eventually. i.e.
|
I'd like to add that having views to retain its base object is not quite a rare pattern. The Standard Library slices use the same semantic. TBH I would be in favour of ditching Alternatively, we could redesign our property types as a class cluster (e.g. the |
Have a related issue going on in #704 where trying to find the best way to consolidate related property bindings, signals and producers. The current mutable property model doesn't look very appealing and I'm trying to understand a couple of things. As a concept properties sound great. However, in practice ReactiveCocoa doesn't use them at all where they would fit naturally and reverts to binding targets, signals and producers instead, which cause naming inconsistencies (discussed here and here). I find it rather strange to have a specific tool in the framework, but instead use three other tools to do the job it's designed to do… Do you think having lighter properties which don't require being retained would help breaking this "tradition"? My second questions is related. Why must the ReactiveSwift/Sources/Property.swift Lines 14 to 16 in 2a4ffea
My guess is that this was designed upon retaining the value and not considering that this can be a wrapper or proxy. E.g., it can be composited of an |
It's this bit:
For something to be observable, it must have reference semantics. (Since values can't change in an observable way.) You can create a thin wrapper as a |
Doesn't it depend on how you look at the observable though? It doesn't need to be when it's acting as a "reactive" proxy for struct PropertyProxy<Base: AnyObject & ReactiveExtensionsProvider, Value>: BindingSource, BindingTargetProvider
{
init(_ base: Base, _ keyPath: ReferenceWritableKeyPath<Base, Value>, _ signal: Signal<Value, NoError>) {
self.base = base
self.keyPath = keyPath
self.signal = signal
}
let base: Base
let keyPath: ReferenceWritableKeyPath<Base, Value>
let signal: Signal<Value, NoError>
var lifetime: Lifetime { return Lifetime.of(self.base) }
var producer: SignalProducer<Value, NoError> { return self.signal.producer.prefix(value: self.value) }
var bindingTarget: BindingTarget<Value> { return self.base.reactive[self.keyPath] }
var value: Value {
get { return self.base[keyPath: self.keyPath] }
nonmutating set { self.base[keyPath: self.keyPath] = newValue }
}
} Again, this is not to say that this is how things must be, but feels like a great approach to Simply dropping |
Why not make |
Okay. My life just divided in before and after I checked this in playground… The struct initialization is outperformed by the same class. Sure this might differ with optimizations, etc. I was basing my belief that structs will outperform classes. Since the proxying property is temporary disposable type it felt like a better idea sticking with structs. If there's no dramatic difference then there's no point. And it also won't be invoked in 10k-iteration loops either, so… No more questions! ✌️ |
Hello. 👋 Thanks for opening this issue. Due to inactivity, we will soft close the issue. If you feel that it should remain open, please let us know. 😄 |
I think it'd make sense to include a way to create a new
Property
orBindingTarget
from aProperty
with aKeyPath
.This is awkward to express otherwise. You can't really use
<~
and are forced to observe the values manually.I'd suggest making the subscript return a property, except that (1) this might make the ownership difficult to work out and (2) I'm not sure if we can express the
Property
/MutableProperty
relationship in Swift's type system. But if it is possible, then it'd be nice.The text was updated successfully, but these errors were encountered: