Skip to content

Commit 6288aef

Browse files
committed
Changes to the DynamicProperty semantics.
1 parent 0859e13 commit 6288aef

File tree

2 files changed

+191
-70
lines changed

2 files changed

+191
-70
lines changed

ReactiveCocoa/Swift/DynamicProperty.swift

Lines changed: 104 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,71 +5,70 @@ import enum Result.NoError
55
/// types, including generic types when boxed via `AnyObject`).
66
private protocol ObjectiveCRepresentable {
77
associatedtype Value
8-
static func extract(from representation: Any) -> Value
9-
static func represent(_ value: Value) -> Any
8+
static func extract(from representation: Any?) -> Value
9+
static func represent(_ value: Value) -> Any?
1010
}
1111

12-
/// Wraps a `dynamic` property, or one defined in Objective-C, using Key-Value
12+
/// A lens to a `dynamic` property, or one defined in Objective-C, using Key-Value
1313
/// Coding and Key-Value Observing.
1414
///
15-
/// Use this class only as a last resort! `MutableProperty` is generally better
16-
/// unless KVC/KVO is required by the API you're using (for example,
17-
/// `NSOperation`).
15+
/// - important: `DynamicProperty` retains its lensing object. Moreover,
16+
/// `DynamicProperty` merely passes through the lifetime of its
17+
/// lensing object. Therefore, all bindings targeting a
18+
/// `DynamicProperty` would not be solely teared down by its
19+
/// deinitialization.
20+
///
21+
/// - warning: Use this class only as a last resort. For just observations to
22+
/// KVO-compliant key paths, use `NSObject.values(forKeyPath:)`.
23+
/// `MutableProperty` is generally better unless KVC/KVO is
24+
/// required by the API you're using (for example, `NSOperation`).
1825
public final class DynamicProperty<Value>: MutablePropertyProtocol {
19-
private weak var object: NSObject?
26+
private let object: NSObject
2027
private let keyPath: String
2128

22-
private let extractValue: (_ from: Any) -> Value
23-
private let represent: (Value) -> Any
24-
25-
private var property: MutableProperty<Value?>?
29+
private let extractValue: (_ from: Any?) -> Value
30+
private let represent: (Value) -> Any?
2631

27-
/// The current value of the property, as read and written using Key-Value
28-
/// Coding.
29-
public var value: Value? {
32+
/// The current value of the property.
33+
public var value: Value {
3034
get {
31-
return object?.value(forKeyPath: keyPath).map(extractValue)
35+
return extractValue(object.value(forKeyPath: keyPath))
3236
}
3337

3438
set(newValue) {
35-
object?.setValue(newValue.map(represent), forKeyPath: keyPath)
39+
object.setValue(represent(newValue), forKeyPath: keyPath)
3640
}
3741
}
3842

3943
/// The lifetime of the property.
4044
public var lifetime: Lifetime {
41-
return object?.rac_lifetime ?? .empty
45+
return object.rac_lifetime
4246
}
4347

44-
/// A producer that will create a Key-Value Observer for the given object,
45-
/// send its initial value then all changes over time, and then complete
46-
/// when the observed object has deallocated.
48+
/// A producer that send the initial value then all changes over time of the
49+
/// property, and then complete when its lensing object deinitializes.
4750
///
48-
/// - important: This only works if the object given to init() is KVO-compliant.
49-
/// Most UI controls are not!
50-
public var producer: SignalProducer<Value?, NoError> {
51-
return (object.map { $0.values(forKeyPath: keyPath) } ?? .empty)
52-
.map { [extractValue = self.extractValue] in $0.map(extractValue) }
51+
/// - important: The lensing key path must be KVO compliant.
52+
public var producer: SignalProducer<Value, NoError> {
53+
return object.values(forKeyPath: keyPath).map(extractValue)
5354
}
5455

55-
public lazy var signal: Signal<Value?, NoError> = { [unowned self] in
56+
public lazy var signal: Signal<Value, NoError> = { [unowned self] in
5657
var signal: Signal<DynamicProperty.Value, NoError>!
5758
self.producer.startWithSignal { innerSignal, _ in signal = innerSignal }
5859
return signal
5960
}()
6061

61-
/// Initializes a property that will observe and set the given key path of
62+
/// Initializes a property that acts as a lens to the given key path of
6263
/// the given object, using the supplied representation.
6364
///
64-
/// - important: `object` must support weak references!
65-
///
6665
/// - parameters:
67-
/// - object: An object to be observed.
68-
/// - keyPath: Key path to observe on the object.
66+
/// - object: An object to be lensed.
67+
/// - keyPath: Key path to lense on the object.
6968
/// - representable: A representation that bridges the values across the
7069
/// language boundary.
7170
fileprivate init<Representatable: ObjectiveCRepresentable>(
72-
object: NSObject?,
71+
object: NSObject,
7372
keyPath: String,
7473
representable: Representatable.Type
7574
)
@@ -80,64 +79,118 @@ public final class DynamicProperty<Value>: MutablePropertyProtocol {
8079

8180
self.extractValue = Representatable.extract(from:)
8281
self.represent = Representatable.represent
82+
}
8383

84-
/// A DynamicProperty will stay alive as long as its object is alive.
85-
/// This is made possible by strong reference cycles.
86-
_ = object?.rac_lifetime.ended.observeCompleted { _ = self }
84+
@discardableResult
85+
public static func <~ <Source: SignalProtocol>(target: DynamicProperty, signal: Source) -> Disposable? where Source.Value == Value, Source.Error == NoError {
86+
return signal
87+
.take(during: target.object.rac_lifetime)
88+
.observeNext { [weak object = target.object, represent = target.represent, keyPath = target.keyPath] value in
89+
object?.setValue(represent(value), forKeyPath: keyPath)
90+
}
8791
}
8892
}
8993

9094
extension DynamicProperty where Value: _ObjectiveCBridgeable {
91-
/// Initializes a property that will observe and set the given key path of
95+
/// Initializes a property that acts as a lens to the given key path of
9296
/// the given object, where `Value` is a value type that is bridgeable
9397
/// to Objective-C.
9498
///
95-
/// - important: `object` must support weak references!
96-
///
9799
/// - parameters:
98-
/// - object: An object to be observed.
99-
/// - keyPath: Key path to observe on the object.
100-
public convenience init(object: NSObject?, keyPath: String) {
100+
/// - object: An object to be lensed.
101+
/// - keyPath: Key path to lense on the object.
102+
public convenience init(object: NSObject, keyPath: String) {
101103
self.init(object: object, keyPath: keyPath, representable: BridgeableRepresentation.self)
102104
}
103105
}
104106

105107
extension DynamicProperty where Value: AnyObject {
106-
/// Initializes a property that will observe and set the given key path of
108+
/// Initializes a property that acts as a lens to the given key path of
107109
/// the given object, where `Value` is a reference type that can be
108110
/// represented directly in Objective-C via `AnyObject`.
109111
///
110-
/// - important: `object` must support weak references!
111-
///
112112
/// - parameters:
113-
/// - object: An object to be observed.
114-
/// - keyPath: Key path to observe on the object.
115-
public convenience init(object: NSObject?, keyPath: String) {
113+
/// - object: An object to be lensed.
114+
/// - keyPath: Key path to lense on the object.
115+
public convenience init(object: NSObject, keyPath: String) {
116116
self.init(object: object, keyPath: keyPath, representable: DirectRepresentation.self)
117117
}
118118
}
119119

120+
extension DynamicProperty where Value: OptionalProtocol, Value.Wrapped: _ObjectiveCBridgeable {
121+
/// Initializes a property that acts as a lens to the given key path of
122+
/// the given object, where `Value` is a value type that is bridgeable
123+
/// to Objective-C.
124+
///
125+
/// - parameters:
126+
/// - object: An object to be lensed.
127+
/// - keyPath: Key path to lense on the object.
128+
public convenience init(object: NSObject, keyPath: String) {
129+
self.init(object: object, keyPath: keyPath, representable: NullableBridgeableRepresentation.self)
130+
}
131+
}
132+
133+
extension DynamicProperty where Value: OptionalProtocol, Value.Wrapped: AnyObject {
134+
/// Initializes a property that acts as a lens to the given key path of
135+
/// the given object, where `Value` is a reference type that can be
136+
/// represented directly in Objective-C via `AnyObject`.
137+
///
138+
/// - parameters:
139+
/// - object: An object to be lensed.
140+
/// - keyPath: Key path to lense on the object.
141+
public convenience init(object: NSObject, keyPath: String) {
142+
self.init(object: object, keyPath: keyPath, representable: NullableDirectRepresentation.self)
143+
}
144+
}
145+
120146
/// Represents values in Objective-C directly, via `AnyObject`.
121147
private struct DirectRepresentation<Value: AnyObject>: ObjectiveCRepresentable {
122-
static func extract(from representation: Any) -> Value {
148+
static func extract(from representation: Any?) -> Value {
123149
return representation as! Value
124150
}
125151

126-
static func represent(_ value: Value) -> Any {
152+
static func represent(_ value: Value) -> Any? {
127153
return value
128154
}
129155
}
130156

131157
/// Represents values in Objective-C indirectly, via bridging.
132158
private struct BridgeableRepresentation<Value: _ObjectiveCBridgeable>: ObjectiveCRepresentable {
133-
static func extract(from representation: Any) -> Value {
159+
static func extract(from representation: Any?) -> Value {
134160
let object = representation as! Value._ObjectiveCType
135161
var result: Value?
136162
Value._forceBridgeFromObjectiveC(object, result: &result)
137163
return result!
138164
}
139165

140-
static func represent(_ value: Value) -> Any {
166+
static func represent(_ value: Value) -> Any? {
141167
return value._bridgeToObjectiveC()
142168
}
143169
}
170+
171+
/// Represents nullable values in Objective-C directly, via `AnyObject`.
172+
private struct NullableDirectRepresentation<Value: OptionalProtocol>: ObjectiveCRepresentable where Value.Wrapped: AnyObject {
173+
static func extract(from representation: Any?) -> Value {
174+
return representation as! Value
175+
}
176+
177+
static func represent(_ value: Value) -> Any? {
178+
return value.optional
179+
}
180+
}
181+
182+
/// Represents nullable values in Objective-C indirectly, via bridging.
183+
private struct NullableBridgeableRepresentation<Value: OptionalProtocol>: ObjectiveCRepresentable where Value.Wrapped: _ObjectiveCBridgeable {
184+
static func extract(from representation: Any?) -> Value {
185+
let object = representation as? Value.Wrapped._ObjectiveCType
186+
return Value(reconstructing: object.map { value in
187+
var result: Value.Wrapped?
188+
Value.Wrapped._forceBridgeFromObjectiveC(value, result: &result)
189+
return result!
190+
})
191+
}
192+
193+
static func represent(_ value: Value) -> Any? {
194+
return value.optional.map { $0._bridgeToObjectiveC() }
195+
}
196+
}

0 commit comments

Comments
 (0)