From 2da50ec40e860bd810175bfa8a2a487e5572a870 Mon Sep 17 00:00:00 2001 From: Markus Chmelar Date: Sun, 20 Nov 2016 12:35:55 +0100 Subject: [PATCH 1/5] Update documentation on Property --- Documentation/FrameworkOverview.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Documentation/FrameworkOverview.md b/Documentation/FrameworkOverview.md index 298122b3a..47e4305ec 100644 --- a/Documentation/FrameworkOverview.md +++ b/Documentation/FrameworkOverview.md @@ -136,15 +136,15 @@ For interaction with `NSControl` or `UIControl`, RAC provides the ## Properties -A **property**, represented by the [`PropertyType`][Property] protocol, +A **property**, represented by the [`PropertyProtocol`][Property], stores a value and notifies observers about future changes to that value. The current value of a property can be obtained from the `value` getter. The `producer` getter returns a [signal producer](#signal-producers) that will send -the property’s current value, followed by all changes over time. +the property’s current value, followed by all changes over time. The `signal` getter returns a [signal](#signals) that will send all changes over time, but not the initial value. The `<~` operator can be used to bind properties in different ways. Note that in -all cases, the target has to be a [`MutablePropertyType`][Property]. +all cases, the target has to be a [`BindingTarget`][BindingTarget], [`MutableProperty`][MutableProperty] is the only property that implements this. * `property <~ signal` binds a [signal](#signals) to the property, updating the property’s value to the latest value sent by the signal. @@ -157,9 +157,14 @@ The [`DynamicProperty`][Property] type can be used to bridge to Objective-C APIs that require Key-Value Coding (KVC) or Key-Value Observing (KVO), like `NSOperation`. Note that most AppKit and UIKit properties do _not_ support KVO, so their changes should be observed through other mechanisms. + +[ReactiveCocoa][ReactiveCocoa] implements a number of extensions on AppKit and UIKit to allow observation of and binding to properties via the `.reactive` structure. + [`MutableProperty`][Property] should be preferred over dynamic properties whenever possible! +Properties provide a number of transformations like `map`, `combineLatest` or `zip` for manipulation similar to [signal](#signals) and [signal producer](#signal-producers) + ## Disposables A **disposable**, represented by the [`Disposable`][Disposable] protocol, is a mechanism @@ -199,6 +204,7 @@ do not allow tasks to be reordered or depend on one another. [Design Guidelines]: DesignGuidelines.md [BasicOperators]: BasicOperators.md [README]: ../README.md +[ReactiveCocoa]: https://github.com/ReactiveCocoa/ [Signal]: ../Sources/Signal.swift [SignalProducer]: ../Sources/SignalProducer.swift [Action]: ../Sources/Action.swift @@ -208,3 +214,4 @@ do not allow tasks to be reordered or depend on one another. [Property]: ../Sources/Property.swift [Event]: ../Sources/Event.swift [Observer]: ../Sources/Observer.swift +[BindingTarget]: ../Sources/UnidirectionalBinding.swift \ No newline at end of file From b47748d862440311036bcb92967a6a2e6864c021 Mon Sep 17 00:00:00 2001 From: Markus Chmelar Date: Sun, 20 Nov 2016 13:54:21 +0100 Subject: [PATCH 2/5] Add playground page for `Property` --- .../Property.xcplaygroundpage/Contents.swift | 290 ++++++++++++++++++ .../contents.xcplayground | 3 +- 2 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 ReactiveSwift.playground/Pages/Property.xcplaygroundpage/Contents.swift diff --git a/ReactiveSwift.playground/Pages/Property.xcplaygroundpage/Contents.swift b/ReactiveSwift.playground/Pages/Property.xcplaygroundpage/Contents.swift new file mode 100644 index 000000000..279075e2f --- /dev/null +++ b/ReactiveSwift.playground/Pages/Property.xcplaygroundpage/Contents.swift @@ -0,0 +1,290 @@ +/*: + > # IMPORTANT: To use `ReactiveSwift.playground`, please: + + 1. Retrieve the project dependencies using one of the following terminal commands from the ReactiveSwift project root directory: + - `script/bootstrap` + **OR**, if you have [Carthage](https://github.com/Carthage/Carthage) installed + - `carthage checkout` + 1. Open `ReactiveSwift.xcworkspace` + 1. Build `Result-Mac` scheme + 1. Build `ReactiveSwift-macOS` scheme + 1. Finally open the `ReactiveSwift.playground` + 1. Choose `View > Show Debug Area` + */ +import Result +import ReactiveSwift +import Foundation +/*: + ## Property + + A **property**, represented by the [`PropertyProtocol`](https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Sources/Property.swift) , + stores a value and notifies observers about future changes to that value. + + - The current value of a property can be obtained from the `value` getter. + - The `producer` getter returns a [signal producer](SignalProductr) that will send the property’s current value, followed by all changes over time. + - The `signal` getter returns a [signal](Signal) that will send all changes over time, but not the initial value. + + */ +scopedExample("Creation") { + let mutableProperty = MutableProperty(1) + + // The value of the property can be accessed via its `value` attribute + print("Property has initial value \(mutableProperty.value)") + // The properties value can be observed via its `producer` or `signal attribute` + // Note, how the `producer` immediately sends the initial value, but the `signal` only sends new values + mutableProperty.producer.startWithValues { + print("mutableProperty.producer receied \($0)") + } + mutableProperty.signal.observeValues { + print("mutableProperty.signal received \($0)") + } + + print("---") + print("Setting new value for mutableProperty: 2") + mutableProperty.value = 2 + + print("---") + // If a property should be exposed for readonly access, it can be wrapped in a Property + let property = Property(mutableProperty) + + print("Reading value of readonly property: \(property.value)") + property.signal.observeValues { + print("property.signal received \($0)") + } + + // Its not possible to set the value of a Property +// readonlyProperty.value = 3 + // But you can still change the value of the mutableProperty and observe its change on the property + print("---") + print("Setting new value for mutableProperty: 3") + mutableProperty.value = 3 + + // Constant properties can be created by using the `Property(value:)` initializer + let constant = Property(value: 1) +// constant.value = 2 // The value of a constant property can not be changed +} +/*: + ### Binding + + The `<~` operator can be used to bind properties in different ways. Note that in + all cases, the target has to be a [`BindingTarget`](https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Sources/UnidirectionalBinding.swift) , [`MutableProperty`](https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Sources/Property.swift#L28) is the only property that implements this. + + * `property <~ signal` binds a [signal](#signals) to the property, updating the + property’s value to the latest value sent by the signal. + * `property <~ producer` starts the given [signal producer](#signal-producers), + and binds the property’s value to the latest value sent on the started signal. + * `property <~ otherProperty` binds one property to another, so that the destination + property’s value is updated whenever the source property is updated. + */ +scopedExample("Binding from SignalProducer") { + let producer = SignalProducer { observer, _ in + print("New subscription, starting operation") + observer.send(value: 1) + observer.send(value: 2) + } + let property = MutableProperty(0) + property.producer.startWithValues { + print("Property received \($0)") + } + + // Notice how the producer will start the work as soon it is bound to the property + property <~ producer +} + +scopedExample("Binding from Signal") { + let (signal, observer) = Signal.pipe() + let property = MutableProperty(0) + property.producer.startWithValues { + print("Property received \($0)") + } + + property <~ signal + + print("Sending new value on signal: 1") + observer.send(value: 1) + + print("Sending new value on signal: 2") + observer.send(value: 2) +} + +scopedExample("Binding from other Property") { + let property = MutableProperty(0) + property.producer.startWithValues { + print("Property received \($0)") + } + + let otherProperty = MutableProperty(0) + + // Notice how property receives another value of 0 as soon as the binding is established + property <~ otherProperty + + print("Setting new value for otherProperty: 1") + otherProperty.value = 1 + + print("Setting new value for otherProperty: 2") + otherProperty.value = 2 +} +/*: + ### Transformations + + Properties provide a number of transformations like `map`, `combineLatest` or `zip` for manipulation similar to [signal](Signal) and [signal producer](SignalProducer) + */ +scopedExample("`map`") { + let property = MutableProperty(0) + let mapped = property.map { $0 * 2 } + mapped.producer.startWithValues { + print("Mapped property received \($0)") + } + + print("Setting new value for property: 1") + property.value = 1 + + print("Setting new value for property: 2") + property.value = 2 +} + +scopedExample("`skipRepeats`") { + let property = MutableProperty(0) + let skipRepeatsProperty = property.skipRepeats() + + property.producer.startWithValues { + print("Property received \($0)") + } + skipRepeatsProperty.producer.startWithValues { + print("Skip-Repeats property received \($0)") + } + + property.value = 0 + property.value = 1 + property.value = 1 + property.value = 0 +} + +scopedExample("`skipRepeats`") { + let property = MutableProperty(0) + let skipRepeatsProperty = property.skipRepeats() + + property.producer.startWithValues { + print("Property received \($0)") + } + skipRepeatsProperty.producer.startWithValues { + print("Skip-Repeats property received \($0)") + } + + print("Setting new value for property: 0") + property.value = 0 + print("Setting new value for property: 1") + property.value = 1 + print("Setting new value for property: 1") + property.value = 1 + print("Setting new value for property: 0") + property.value = 0 +} + +scopedExample("`uniqueValues`") { + let property = MutableProperty(0) + let unique = property.uniqueValues() + property.producer.startWithValues { + print("Property received \($0)") + } + unique.producer.startWithValues { + print("Unique values property received \($0)") + } + + print("Setting new value for property: 0") + property.value = 0 + print("Setting new value for property: 1") + property.value = 1 + print("Setting new value for property: 1") + property.value = 1 + print("Setting new value for property: 0") + property.value = 0 + +} + +scopedExample("`combineLatest`") { + let propertyA = MutableProperty(0) + let propertyB = MutableProperty("A") + let combined = propertyA.combineLatest(with: propertyB) + combined.producer.startWithValues { + print("Combined property received \($0)") + } + + print("Setting new value for propertyA: 1") + propertyA.value = 1 + + print("Setting new value for propertyB: 'B'") + propertyB.value = "B" + + print("Setting new value for propertyB: 'C'") + propertyB.value = "C" + + print("Setting new value for propertyB: 'D'") + propertyB.value = "D" + + print("Setting new value for propertyA: 2") + propertyA.value = 2 +} + +scopedExample("`zip`") { + let propertyA = MutableProperty(0) + let propertyB = MutableProperty("A") + let zipped = propertyA.zip(with: propertyB) + zipped.producer.startWithValues { + print("Zipped property received \($0)") + } + + print("Setting new value for propertyA: 1") + propertyA.value = 1 + + print("Setting new value for propertyB: 'B'") + propertyB.value = "B" + + // Observe that, in contrast to `combineLatest`, setting a new value for propertyB does not cause a new value for the zipped property until propertyA has a new value as well + print("Setting new value for propertyB: 'C'") + propertyB.value = "C" + + print("Setting new value for propertyB: 'D'") + propertyB.value = "D" + + print("Setting new value for propertyA: 2") + propertyA.value = 2 +} + +scopedExample("`flatten`") { + let property1 = MutableProperty("0") + let property2 = MutableProperty("A") + let property3 = MutableProperty("!") + let property = MutableProperty(property1) + // Try different merge strategies and see how the results change + property.flatten(.latest).producer.startWithValues { + print("Flattened property receive \($0)") + } + + print("Sending new value on property1: 1") + property1.value = "1" + + print("Sending new value on property: property2") + property.value = property2 + + print("Sending new value on property1: 2") + property1.value = "2" + + print("Sending new value on property2: B") + property2.value = "B" + + print("Sending new value on property1: 3") + property1.value = "3" + + print("Sending new value on property: property3") + property.value = property3 + + print("Sending new value on property3: ?") + property3.value = "?" + + print("Sending new value on property2: C") + property2.value = "C" + + print("Sending new value on property1: 4") + property1.value = "4" +} diff --git a/ReactiveSwift.playground/contents.xcplayground b/ReactiveSwift.playground/contents.xcplayground index 43399d296..f368adc7d 100644 --- a/ReactiveSwift.playground/contents.xcplayground +++ b/ReactiveSwift.playground/contents.xcplayground @@ -1,8 +1,9 @@ - + + \ No newline at end of file From 30aaa930cf9abf53566fbe003bb134224145937e Mon Sep 17 00:00:00 2001 From: Markus Chmelar Date: Mon, 21 Nov 2016 09:35:30 +0100 Subject: [PATCH 3/5] Documentation fixes according to review --- Documentation/FrameworkOverview.md | 2 +- .../Pages/Property.xcplaygroundpage/Contents.swift | 2 +- ReactiveSwift.playground/contents.xcplayground | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/FrameworkOverview.md b/Documentation/FrameworkOverview.md index 47e4305ec..9d23b3b06 100644 --- a/Documentation/FrameworkOverview.md +++ b/Documentation/FrameworkOverview.md @@ -144,7 +144,7 @@ The current value of a property can be obtained from the `value` getter. The the property’s current value, followed by all changes over time. The `signal` getter returns a [signal](#signals) that will send all changes over time, but not the initial value. The `<~` operator can be used to bind properties in different ways. Note that in -all cases, the target has to be a [`BindingTarget`][BindingTarget], [`MutableProperty`][MutableProperty] is the only property that implements this. +all cases, the target has to be a binding target, represented by the [`BindingTargetProtocol`][BindingTarget]. All mutable property types, represented by the [`MutablePropertyProtocol`][MutableProperty], are inherently binding targets. * `property <~ signal` binds a [signal](#signals) to the property, updating the property’s value to the latest value sent by the signal. diff --git a/ReactiveSwift.playground/Pages/Property.xcplaygroundpage/Contents.swift b/ReactiveSwift.playground/Pages/Property.xcplaygroundpage/Contents.swift index 279075e2f..55a0c2684 100644 --- a/ReactiveSwift.playground/Pages/Property.xcplaygroundpage/Contents.swift +++ b/ReactiveSwift.playground/Pages/Property.xcplaygroundpage/Contents.swift @@ -67,7 +67,7 @@ scopedExample("Creation") { ### Binding The `<~` operator can be used to bind properties in different ways. Note that in - all cases, the target has to be a [`BindingTarget`](https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Sources/UnidirectionalBinding.swift) , [`MutableProperty`](https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Sources/Property.swift#L28) is the only property that implements this. + all cases, the target has to be a binding target, represented by the [`BindingTargetProtocol`](https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Sources/UnidirectionalBinding.swift). All mutable property types, represented by the [`MutablePropertyProtocol`](https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Sources/Property.swift#L28), are inherently binding targets. * `property <~ signal` binds a [signal](#signals) to the property, updating the property’s value to the latest value sent by the signal. diff --git a/ReactiveSwift.playground/contents.xcplayground b/ReactiveSwift.playground/contents.xcplayground index f368adc7d..8200c9f12 100644 --- a/ReactiveSwift.playground/contents.xcplayground +++ b/ReactiveSwift.playground/contents.xcplayground @@ -1,5 +1,5 @@ - + From 74e9c4b0f30c872d8a043823e41db5ab4cdf3e7b Mon Sep 17 00:00:00 2001 From: Markus Chmelar Date: Mon, 21 Nov 2016 09:45:44 +0100 Subject: [PATCH 4/5] Remove `MutableProperty` from Documentation since thats implemented in ReactiveCocoa, not in ReactiveSwift --- Documentation/FrameworkOverview.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Documentation/FrameworkOverview.md b/Documentation/FrameworkOverview.md index 9d23b3b06..0acbe9bd1 100644 --- a/Documentation/FrameworkOverview.md +++ b/Documentation/FrameworkOverview.md @@ -153,16 +153,8 @@ all cases, the target has to be a binding target, represented by the [`BindingTa * `property <~ otherProperty` binds one property to another, so that the destination property’s value is updated whenever the source property is updated. -The [`DynamicProperty`][Property] type can be used to bridge to Objective-C APIs -that require Key-Value Coding (KVC) or Key-Value Observing (KVO), like -`NSOperation`. Note that most AppKit and UIKit properties do _not_ support KVO, -so their changes should be observed through other mechanisms. - [ReactiveCocoa][ReactiveCocoa] implements a number of extensions on AppKit and UIKit to allow observation of and binding to properties via the `.reactive` structure. -[`MutableProperty`][Property] should be preferred over dynamic properties -whenever possible! - Properties provide a number of transformations like `map`, `combineLatest` or `zip` for manipulation similar to [signal](#signals) and [signal producer](#signal-producers) ## Disposables From 146aaa9292ee1461c6e35bf001e9415e9754e148 Mon Sep 17 00:00:00 2001 From: Markus Chmelar Date: Mon, 21 Nov 2016 22:05:45 +0100 Subject: [PATCH 5/5] Review changes --- Documentation/FrameworkOverview.md | 2 -- .../Property.xcplaygroundpage/Contents.swift | 17 ----------------- 2 files changed, 19 deletions(-) diff --git a/Documentation/FrameworkOverview.md b/Documentation/FrameworkOverview.md index 0acbe9bd1..eacfd320b 100644 --- a/Documentation/FrameworkOverview.md +++ b/Documentation/FrameworkOverview.md @@ -153,8 +153,6 @@ all cases, the target has to be a binding target, represented by the [`BindingTa * `property <~ otherProperty` binds one property to another, so that the destination property’s value is updated whenever the source property is updated. -[ReactiveCocoa][ReactiveCocoa] implements a number of extensions on AppKit and UIKit to allow observation of and binding to properties via the `.reactive` structure. - Properties provide a number of transformations like `map`, `combineLatest` or `zip` for manipulation similar to [signal](#signals) and [signal producer](#signal-producers) ## Disposables diff --git a/ReactiveSwift.playground/Pages/Property.xcplaygroundpage/Contents.swift b/ReactiveSwift.playground/Pages/Property.xcplaygroundpage/Contents.swift index 55a0c2684..d8e16abf1 100644 --- a/ReactiveSwift.playground/Pages/Property.xcplaygroundpage/Contents.swift +++ b/ReactiveSwift.playground/Pages/Property.xcplaygroundpage/Contents.swift @@ -143,23 +143,6 @@ scopedExample("`map`") { property.value = 2 } -scopedExample("`skipRepeats`") { - let property = MutableProperty(0) - let skipRepeatsProperty = property.skipRepeats() - - property.producer.startWithValues { - print("Property received \($0)") - } - skipRepeatsProperty.producer.startWithValues { - print("Skip-Repeats property received \($0)") - } - - property.value = 0 - property.value = 1 - property.value = 1 - property.value = 0 -} - scopedExample("`skipRepeats`") { let property = MutableProperty(0) let skipRepeatsProperty = property.skipRepeats()