|
9 | 9 | ⚠️ [Still using Swift 2.x?][] |
10 | 10 |
|
11 | 11 | ## What is ReactiveSwift? |
12 | | -__ReactiveSwift__ offers composable, declarative and flexible primitives that are built around the grand concept of ___streams of values over time___. These primitives can be used to uniformly represent common Cocoa and generic programming patterns that are fundamentally an act of observation, e.g.: |
| 12 | +__ReactiveSwift__ offers composable, declarative and flexible primitives that are built around the grand concept of ___streams of values over time___. |
13 | 13 |
|
14 | | - * Delegate methods |
15 | | - * Callback blocks |
16 | | - * Notifications |
17 | | - * Control actions and responder chain events |
18 | | - * [Futures and promises](https://en.wikipedia.org/wiki/Futures_and_promises) |
19 | | - * [Key-value observing](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html) (KVO) |
| 14 | +These primitives can be used to uniformly represent common Cocoa and generic programming patterns that are fundamentally an act of observation, e.g. delegate pattern, callback closures, notifications, control actions, responder chain events, [futures/promises](https://en.wikipedia.org/wiki/Futures_and_promises) and [key-value observing](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html) (KVO). |
20 | 15 |
|
21 | 16 | Because all of these different mechanisms can be represented in the _same_ way, |
22 | | -it’s easy to declaratively chain and combine them together, with less spaghetti |
| 17 | +it’s easy to declaratively compose them together, with less spaghetti |
23 | 18 | code and state to bridge the gap. |
24 | 19 |
|
25 | | -For more information about the concepts in ReactiveSwift, see the [Framework |
26 | | -Overview][]. |
| 20 | +### Core Reactive Primitives |
| 21 | +#### `Signal`: a unidirectional stream of events. |
| 22 | +The owner of a `Signal` has unilateral control of the event stream. Observers may register their interests in the future events at any time, but the observation would have no side effect on the stream or its owner. |
| 23 | + |
| 24 | +It is like a live TV feed — you can observe and react to the content, but you cannot have a side effect on the live feed or the TV station. |
| 25 | + |
| 26 | +```swift |
| 27 | +let channel: Signal<Program, NoError> = tvStation.channelOne |
| 28 | +channel.observeValues { program in ... } |
| 29 | +``` |
| 30 | + |
| 31 | +#### `Event`: the basic transfer unit of an event stream. |
| 32 | +A `Signal` may have any arbitrary number of events carrying a value, following by an eventual terminal event of a specific reason. |
| 33 | + |
| 34 | +It is like a frame in a one-time live feed — seas of data frames carry the visual and audio data, but the feed would eventually be terminated with a special frame to indicate "end of stream". |
| 35 | + |
| 36 | +#### `SignalProducer`: deferred work that creates a stream of values. |
| 37 | +`SignalProducer` defers work — of which the output is represented as a stream of values — until it is started. For every invocation to start the `SignalProducer`, a new `Signal` is created and the deferred work is subsequently invoked. |
| 38 | + |
| 39 | +It is like a on-demand streaming service — even though the episode is streamed like a live TV feed, you can choose what you watch, when to start watching and when to interrupt it. |
| 40 | + |
| 41 | + |
| 42 | +```swift |
| 43 | +let frames: SignalProducer<VideoFrame, ConnectionError> = vidStreamer.streamAsset(id: tvShowId) |
| 44 | +let interrupter = frames.start { frame in ... } |
| 45 | +interrupter.dispose() |
| 46 | +``` |
| 47 | + |
| 48 | +#### `Property`: an observable box that always holds a value. |
| 49 | +`Property` is a variable that can be observed for its changes. In other words, it is a stream of values with a stronger guarantee than `Signal` — the latest value is always available, and the stream would never fail. |
| 50 | + |
| 51 | +It is like the continuously updated current time offset of a video playback — the playback is always at a certain time offset at any time, and it would be updated by the playback logic as the playback continues. |
| 52 | + |
| 53 | +```swift |
| 54 | +let currentTime: Property<TimeInterval> = video.currentTime |
| 55 | +print("Current time offset: \(currentTime.value)") |
| 56 | +currentTime.observeValues { timeBar.timeLabel.text = "\($0)" } |
| 57 | +``` |
| 58 | + |
| 59 | +#### `Action`: a serialized worker with a preset action. |
| 60 | +When being invoked with an input, `Action` apply the input and the latest state to the preset action, and pushes the output to any interested parties. |
| 61 | + |
| 62 | +It is like an automatic vending machine — after choosing an option with coins inserted, the machine would process the order and eventually output your wanted snacks. Notice that the entire process is mutually exclusive — you cannot have the machine to serve two customers concurrently. |
| 63 | + |
| 64 | +```swift |
| 65 | +// Purchase from the vending machine with a specific option. |
| 66 | +vendingMachine.purchase |
| 67 | + .apply(snackId) |
| 68 | + .startWithResults { result |
| 69 | + switch results { |
| 70 | + case let .success(snacks): |
| 71 | + print("Snack: \(snacks)") |
| 72 | + |
| 73 | + case let .failure(error): |
| 74 | + // Out of stock? Insufficient fund? |
| 75 | + print("Transaction aborted: \(error)") |
| 76 | + } |
| 77 | + } |
| 78 | + |
| 79 | +// The vending machine. |
| 80 | +class VendingMachine { |
| 81 | + let purchase: Action<(), [Snack], VendingMachineError> |
| 82 | + let coins: MutableProperty<Int> |
| 83 | + |
| 84 | + // The vending machine is connected with a sales recorder. |
| 85 | + init(_ salesRecorder: SalesRecorder) { |
| 86 | + coins = MutableProperty(0) |
| 87 | + purchase = Action(state: coins, enabledIf: { $0 > 0 }) { coins, snackId in |
| 88 | + return SignalProducer { observer, _ in |
| 89 | + // The sales magic happens here. |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + // The sales recorders are notified for any successful sales. |
| 94 | + purchase.values.observeValues(salesRecorder.record) |
| 95 | + } |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +#### References |
| 100 | + |
| 101 | +For more details about the concepts and primitives in ReactiveSwift, check these documentations out: |
| 102 | + |
| 103 | +1. **[Framework Overview][]** |
| 104 | + |
| 105 | + An overview of the behaviors and the suggested use cases of the ReactiveSwift primitives and utilities. |
| 106 | + |
| 107 | +1. **[Basic Operators][]** |
| 108 | + |
| 109 | + An overview of the operators provided to compose and transform these primitives. |
| 110 | + |
| 111 | +1. **[Design Guidelines][]** |
| 112 | + |
| 113 | + Contracts of the ReactiveSwift primitives, Best Practices with ReactiveSwift, and Guidelines on implementing custom operators. |
27 | 114 |
|
28 | 115 | ## Example: online search |
29 | 116 |
|
@@ -335,6 +422,7 @@ If you need any help, please visit our [GitHub issues][] or [Stack Overflow][]. |
335 | 422 | [ReactiveCocoa]: https://github.com/ReactiveCocoa/ReactiveCocoa/#readme |
336 | 423 | [Actions]: Documentation/FrameworkOverview.md#actions |
337 | 424 | [Basic Operators]: Documentation/BasicOperators.md |
| 425 | +[Design Guidelines]: Documentation/DesignGuidelines.md |
338 | 426 | [Carthage]: https://github.com/Carthage/Carthage/#readme |
339 | 427 | [CocoaPods]: https://cocoapods.org/ |
340 | 428 | [CHANGELOG]: CHANGELOG.md |
|
0 commit comments