Skip to content

Commit 1294573

Browse files
authored
Merge branch 'master' into producer-lift
2 parents d2cc8fc + b26b6c3 commit 1294573

17 files changed

+444
-60
lines changed

Documentation/DesignGuidelines.md

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ resource for getting up to speed on the main types and concepts provided by Reac
1515
1. [`completion` indicates success](#completion-indicates-success)
1616
1. [`interruption`s cancel outstanding work and usually propagate immediately](#interruptions-cancel-outstanding-work-and-usually-propagate-immediately)
1717
1. [Events are serial](#events-are-serial)
18-
1. [Events cannot be sent recursively](#events-cannot-be-sent-recursively)
18+
1. [Events are never delivered recursively, and values cannot be sent recursively](#events-are-never-delivered-recursively-and-values-cannot-be-sent-recursively)
1919
1. [Events are sent synchronously by default](#events-are-sent-synchronously-by-default)
2020

2121
**[The `Signal` contract](#the-signal-contract)**
@@ -33,6 +33,14 @@ resource for getting up to speed on the main types and concepts provided by Reac
3333
1. [Signal operators can be lifted to apply to signal producers](#signal-operators-can-be-lifted-to-apply-to-signal-producers)
3434
1. [Disposing of a produced signal will interrupt it](#disposing-of-a-produced-signal-will-interrupt-it)
3535

36+
37+
**[The Property contract](#the-property-contract)**
38+
39+
1. [A property must have its latest value sent synchronously accessible](#a-property-must-have-its-latest-value-sent-synchronously-accessible)
40+
1. [Events must be synchronously emitted after the mutation is visible](#events-must-be-synchronously-emitted-after-the-mutation-is-visible)
41+
1. [Reentrancy must be supported for reads](#reentrancy-must-be-supported-for-reads)
42+
1. [A composed property does not have a side effect on its sources, and does not own its lifetime](#a-composed-property-does-not-have-a-side-effect-on-its-sources-and-does-not-own-its-lifetime)
43+
3644
**[Best practices](#best-practices)**
3745

3846
1. [Process only as many values as needed](#process-only-as-many-values-as-needed)
@@ -143,18 +151,20 @@ simultaneously.
143151

144152
This simplifies [operator][Operators] implementations and [observers][].
145153

146-
#### Events cannot be sent recursively
154+
#### Events are never delivered recursively, and values cannot be sent recursively.
147155

148-
Just like ReactiveSwift guarantees that [events will not be received
149-
concurrently](#events-are-serial), it also guarantees that they won’t be
150-
received recursively. As a consequence, [operators][] and [observers][] _do not_ need to
156+
Just like [the guarantee of events not being delivered
157+
concurrently](#events-are-serial), it is also guaranteed that events would not be
158+
delivered recursively. As a consequence, [operators][] and [observers][] _do not_ need to
151159
be reentrant.
152160

153-
If an event is sent upon a signal from a thread that is _already processing_
154-
a previous event from that signal, deadlock will result. This is because
161+
If a `value` event is sent upon a signal from a thread that is _already processing_
162+
a previous event from that signal, it would result in a deadlock. This is because
155163
recursive signals are usually programmer error, and the determinacy of
156164
a deadlock is preferable to nondeterministic race conditions.
157165

166+
Note that a terminal event is permitted to be sent recursively.
167+
158168
When a recursive signal is explicitly desired, the recursive event should be
159169
time-shifted, with an operator like [`delay`][delay], to ensure that it isn’t sent from
160170
an already-running event handler.
@@ -304,6 +314,36 @@ everything added to the [`CompositeDisposable`][CompositeDisposable] in
304314
Note that disposing of one produced `Signal` will not affect other signals created
305315
by the same `SignalProducer`.
306316

317+
## The Property contract.
318+
319+
A property is essentially a `Signal` which guarantees it has an initial value, and its latest value is always available for being read out.
320+
321+
All read-only property types should conform to `PropertyProtocol`, while the mutable counterparts should conform to `MutablePropertyProtocol`. ReactiveSwift includes two primitives that implement the contract: `Property` and `MutableProperty`.
322+
323+
#### A property must have its latest value sent synchronously accessible.
324+
325+
A property must have its latest value cached or stored at any point of time. It must be synchronously accessible through `PropertyProtocol.value`.
326+
327+
The `SignalProducer` of a property must replay the latest value before forwarding subsequent changes, and it may ensure that no race condition exists between the replaying and the setup of the forwarding.
328+
329+
#### Events must be synchronously emitted after the mutation is visible.
330+
331+
A mutable property must emit its values and the `completed` event synchronously.
332+
333+
The observers of a property should always observe the same value from the signal and the producer as `PropertyProtocol.value`. This implies that all observations are a `didSet` observer.
334+
335+
#### Reentrancy must be supported for reads.
336+
337+
All properties must guarantee that observers reading `PropertyProtocol.value` would not deadlock.
338+
339+
In other words, if a mutable property type implements its own, or inherits a synchronization mechanism from its container, the synchronization generally should be reentrant due to the requirements of synchrony.
340+
341+
#### A composed property does not have a side effect on its sources, and does not own its lifetime.
342+
343+
A composed property presents a transformed view of its sources. It should not have a side effect on them, as [observing a signal does not have side effects](#observing-a-signal-does-not-have-side-effects) either. This implies a composed property should never retain its sources, or otherwise the `completed` event emitted upon deinitialization would be influenced.
344+
345+
Moreover, it does not own its lifetime, and its deinitialization should not affect its signal and its producer. The signal and the producer should respect the lifetime of the ultimate sources in a property composition graph.
346+
307347
## Best practices
308348

309349
The following recommendations are intended to help keep ReactiveSwift-based code

README.md

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,17 @@ currentTime.observeValues { timeBar.timeLabel.text = "\($0)" }
6363
#### `Action`: a serialized worker with a preset action.
6464
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.
6565

66-
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.
66+
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 snack. Notice that the entire process is mutually exclusive — you cannot have the machine to serve two customers concurrently.
6767

6868
```swift
6969
// Purchase from the vending machine with a specific option.
7070
vendingMachine.purchase
7171
.apply(snackId)
7272
.startWithResult { result
7373
switch result {
74-
case let .success(snacks):
75-
print("Snack: \(snacks)")
76-
74+
case let .success(snack):
75+
print("Snack: \(snack)")
76+
7777
case let .failure(error):
7878
// Out of stock? Insufficient fund?
7979
print("Transaction aborted: \(error)")
@@ -82,18 +82,19 @@ vendingMachine.purchase
8282

8383
// The vending machine.
8484
class VendingMachine {
85-
let purchase: Action<(), [Snack], VendingMachineError>
85+
let purchase: Action<Int, Snack, VendingMachineError>
8686
let coins: MutableProperty<Int>
87-
87+
8888
// The vending machine is connected with a sales recorder.
8989
init(_ salesRecorder: SalesRecorder) {
9090
coins = MutableProperty(0)
91-
purchase = Action(state: coins, enabledIf: { $0 > 0 }) { coins, snackId in
91+
purchase = Action(state: coins, enabledIf: { $0 > 0 }) { coins, snackId in
9292
return SignalProducer { observer, _ in
9393
// The sales magic happens here.
94+
// Fetch a snack based on its id
9495
}
9596
}
96-
97+
9798
// The sales recorders are notified for any successful sales.
9899
purchase.values.observeValues(salesRecorder.record)
99100
}
@@ -163,7 +164,7 @@ one. Just think of how much code that would take to do by hand!
163164

164165
#### Receiving the results
165166

166-
Since the source of search strings is a `Signal` which has a hot signal semantic,
167+
Since the source of search strings is a `Signal` which has a hot signal semantic,
167168
the transformations we applied are automatically evaluated whenever new values are
168169
emitted from `searchStrings`.
169170

@@ -174,10 +175,10 @@ searchResults.observe { event in
174175
switch event {
175176
case let .value(results):
176177
print("Search results: \(results)")
177-
178+
178179
case let .failed(error):
179180
print("Search error: \(error)")
180-
181+
181182
case .completed, .interrupted:
182183
break
183184
}
@@ -279,13 +280,13 @@ let searchString = textField.reactive.continuousTextValues
279280

280281
For more information and advance usage, check the [Debugging Techniques](Documentation/DebuggingTechniques.md) document.
281282

282-
## How does ReactiveSwift relate to Rx?
283-
284-
While ReactiveCocoa was inspired and heavily influenced by [ReactiveX][] (Rx), ReactiveSwift is
285-
an opinionated implementation of [functional reactive programming][], and _intentionally_ not a
283+
## How does ReactiveSwift relate to RxSwift?
284+
RxSwift is a Swift implementation of the [ReactiveX][] (Rx) APIs. While ReactiveCocoa
285+
was inspired and heavily influenced by Rx, ReactiveSwift is an opinionated
286+
implementation of [functional reactive programming][], and _intentionally_ not a
286287
direct port like [RxSwift][].
287288

288-
ReactiveSwift differs from ReactiveX in places that:
289+
ReactiveSwift differs from RxSwift/ReactiveX where doing so:
289290

290291
* Results in a simpler API
291292
* Addresses common sources of confusion
@@ -369,7 +370,7 @@ If you use [Carthage][] to manage your dependencies, simply add
369370
ReactiveSwift to your `Cartfile`:
370371

371372
```
372-
github "ReactiveCocoa/ReactiveSwift" "1.0.0-rc.3"
373+
github "ReactiveCocoa/ReactiveSwift" ~> 1.0
373374
```
374375

375376
If you use Carthage to build your dependencies, make sure you have added `ReactiveSwift.framework`, and `Result.framework` to the "_Linked Frameworks and Libraries_" section of your target, and have included them in your Carthage framework copying build phase.
@@ -380,7 +381,7 @@ If you use [CocoaPods][] to manage your dependencies, simply add
380381
ReactiveSwift to your `Podfile`:
381382

382383
```
383-
pod 'ReactiveSwift', '1.0.0-rc.3'
384+
pod 'ReactiveSwift', '~> 1.0.0'
384385
```
385386

386387
#### Swift Package Manager
@@ -389,7 +390,7 @@ If you use Swift Package Manager, simply add ReactiveSwift as a dependency
389390
of your package in `Package.swift`:
390391

391392
```
392-
.Package(url: "https://github.com/ReactiveCocoa/ReactiveSwift.git", "1.0.0-rc.3")
393+
.Package(url: "https://github.com/ReactiveCocoa/ReactiveSwift.git", majorVersion: 1)
393394
```
394395

395396
#### Git submodule
@@ -419,20 +420,13 @@ We also provide a great Playground, so you can get used to ReactiveCocoa's opera
419420
1. Build `ReactiveSwift-macOS` scheme
420421
1. Finally open the `ReactiveSwift.playground`
421422
1. Choose `View > Show Debug Area`
422-
423+
423424
## Have a question?
424425
If you need any help, please visit our [GitHub issues][] or [Stack Overflow][]. Feel free to file an issue if you do not manage to find any solution from the archives.
425426

426427
## Release Roadmap
427428
**Current Stable Release:**<br />[![GitHub release](https://img.shields.io/github/release/ReactiveCocoa/ReactiveSwift.svg)](https://github.com/ReactiveCocoa/ReactiveSwift/releases)
428429

429-
### Code Complete: ReactiveSwift 1.0
430-
It targets Swift 3.0.x. The tentative schedule of a Gold Master release is January 2017.
431-
432-
[Release Candidiate 3](https://github.com/ReactiveCocoa/ReactiveSwift/releases/tag/1.0.0-rc.3) has been released.
433-
434-
A point release is expected with performance optimizations.
435-
436430
### Plan of Record
437431
#### ReactiveSwift 2.0
438432
It targets Swift 3.1.x. The estimated schedule is Spring 2017.

ReactiveSwift.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
Pod::Spec.new do |s|
22
s.name = "ReactiveSwift"
33
# Version goes here and will be used to access the git tag later on, once we have a first release.
4-
s.version = "1.0.0-rc.3"
4+
s.version = "1.0.0"
55
s.summary = "Streams of values over time"
66
s.description = <<-DESC
77
ReactiveSwift is a Swift framework inspired by Functional Reactive Programming. It provides APIs for composing and transforming streams of values over time.
88
DESC
99
s.homepage = "https://github.com/ReactiveCocoa/ReactiveSwift"
1010
s.license = { :type => "MIT", :file => "LICENSE.md" }
1111
s.author = "ReactiveCocoa"
12-
12+
1313
s.ios.deployment_target = "8.0"
1414
s.osx.deployment_target = "10.9"
1515
s.watchos.deployment_target = "2.0"

Sources/Atomic.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ import MachO
1616
internal protocol AtomicStateProtocol {
1717
associatedtype State: RawRepresentable
1818

19-
/// Try to transit from the expected current state to the specified next
19+
/// Try to transition from the expected current state to the specified next
2020
/// state.
2121
///
2222
/// - parameters:
2323
/// - expected: The expected state.
2424
///
2525
/// - returns:
2626
/// `true` if the transition succeeds. `false` otherwise.
27-
func tryTransiting(from expected: State, to next: State) -> Bool
27+
func tryTransition(from expected: State, to next: State) -> Bool
2828
}
2929

3030
/// A simple, generic lock-free finite state machine.
@@ -65,7 +65,7 @@ internal struct UnsafeAtomicState<State: RawRepresentable>: AtomicStateProtocol
6565
value)
6666
}
6767

68-
/// Try to transit from the expected current state to the specified next
68+
/// Try to transition from the expected current state to the specified next
6969
/// state.
7070
///
7171
/// - parameters:
@@ -74,7 +74,7 @@ internal struct UnsafeAtomicState<State: RawRepresentable>: AtomicStateProtocol
7474
/// - returns:
7575
/// `true` if the transition succeeds. `false` otherwise.
7676
@inline(__always)
77-
internal func tryTransiting(from expected: State, to next: State) -> Bool {
77+
internal func tryTransition(from expected: State, to next: State) -> Bool {
7878
return OSAtomicCompareAndSwap32Barrier(expected.rawValue,
7979
next.rawValue,
8080
value)
@@ -105,15 +105,15 @@ internal struct UnsafeAtomicState<State: RawRepresentable>: AtomicStateProtocol
105105
return value.modify { $0 == expected.rawValue }
106106
}
107107

108-
/// Try to transit from the expected current state to the specified next
108+
/// Try to transition from the expected current state to the specified next
109109
/// state.
110110
///
111111
/// - parameters:
112112
/// - expected: The expected state.
113113
///
114114
/// - returns:
115115
/// `true` if the transition succeeds. `false` otherwise.
116-
internal func tryTransiting(from expected: State, to next: State) -> Bool {
116+
internal func tryTransition(from expected: State, to next: State) -> Bool {
117117
return value.modify { value in
118118
if value == expected.rawValue {
119119
value = next.rawValue

Sources/Deprecations+Removals.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,13 @@ extension SignalProtocol where Error == NoError {
232232
public func observeNext(_ next: (Value) -> Void) -> Disposable? { fatalError() }
233233
}
234234

235+
extension SignalProtocol where Value: Sequence {
236+
@available(*, deprecated, message: "Use flatten() instead")
237+
public func flatten(_ strategy: FlattenStrategy) -> Signal<Value.Iterator.Element, Error> {
238+
return self.flatMap(strategy, transform: SignalProducer.init)
239+
}
240+
}
241+
235242
extension SignalProducerProtocol {
236243
@available(*, unavailable, renamed:"take(first:)")
237244
public func take(_ count: Int) -> SignalProducer<Value, Error> { fatalError() }
@@ -307,6 +314,13 @@ extension SignalProducerProtocol where Error == NoError {
307314
public func startWithNext(_ value: @escaping (Value) -> Void) -> Disposable { fatalError() }
308315
}
309316

317+
extension SignalProducerProtocol where Value: Sequence {
318+
@available(*, deprecated, message: "Use flatten() instead")
319+
public func flatten(_ strategy: FlattenStrategy) -> SignalProducer<Value.Iterator.Element, Error> {
320+
return self.flatMap(strategy, transform: SignalProducer.init)
321+
}
322+
}
323+
310324
extension SignalProducer {
311325
@available(*, unavailable, message:"Use properties instead. `buffer(_:)` is removed in RAC 5.0.")
312326
public static func buffer(_ capacity: Int) -> (SignalProducer, Signal<Value, Error>.Observer) { fatalError() }

Sources/Disposable.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ private enum DisposableState: Int32 {
2929
}
3030

3131
extension AtomicStateProtocol where State == DisposableState {
32-
/// Try to transit from `active` to `disposed`.
32+
/// Try to transition from `active` to `disposed`.
3333
///
3434
/// - returns:
3535
/// `true` if the transition succeeds. `false` otherwise.
3636
@inline(__always)
3737
fileprivate func tryDispose() -> Bool {
38-
return tryTransiting(from: .active, to: .disposed)
38+
return tryTransition(from: .active, to: .disposed)
3939
}
4040
}
4141

Sources/EventLogger.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ private func defaultEventLog(identifier: String, event: String, fileName: String
3232
}
3333

3434
/// A type that represents an event logging function.
35+
/// Signature is:
36+
/// - identifier
37+
/// - event
38+
/// - fileName
39+
/// - functionName
40+
/// - lineNumber
3541
public typealias EventLogger = (
3642
_ identifier: String,
3743
_ event: String,

Sources/Flatten.swift

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -245,11 +245,10 @@ extension SignalProtocol where Value: SignalProtocol, Value.Error == NoError {
245245
}
246246
}
247247

248-
extension SignalProtocol where Value: Sequence, Error == NoError {
249-
/// Flattens the `sequence` value sent by `signal` according to
250-
/// the semantics of the given strategy.
251-
public func flatten(_ strategy: FlattenStrategy) -> Signal<Value.Iterator.Element, Error> {
252-
return self.flatMap(strategy) { .init($0) }
248+
extension SignalProtocol where Value: Sequence {
249+
/// Flattens the `sequence` value sent by `signal`.
250+
public func flatten() -> Signal<Value.Iterator.Element, Error> {
251+
return self.flatMap(.merge, transform: SignalProducer.init)
253252
}
254253
}
255254

@@ -312,11 +311,10 @@ extension SignalProducerProtocol where Value: SignalProtocol, Value.Error == NoE
312311
}
313312
}
314313

315-
extension SignalProducerProtocol where Value: Sequence, Error == NoError {
316-
/// Flattens the `sequence` value sent by `producer` according to
317-
/// the semantics of the given strategy.
318-
public func flatten(_ strategy: FlattenStrategy) -> SignalProducer<Value.Iterator.Element, Error> {
319-
return self.flatMap(strategy) { .init($0) }
314+
extension SignalProducerProtocol where Value: Sequence {
315+
/// Flattens the `sequence` value sent by `signal`.
316+
public func flatten() -> SignalProducer<Value.Iterator.Element, Error> {
317+
return self.flatMap(.merge, transform: SignalProducer.init)
320318
}
321319
}
322320

Sources/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<key>CFBundlePackageType</key>
1616
<string>FMWK</string>
1717
<key>CFBundleShortVersionString</key>
18-
<string>1.0</string>
18+
<string>1.0.0</string>
1919
<key>CFBundleSignature</key>
2020
<string>????</string>
2121
<key>CFBundleVersion</key>

0 commit comments

Comments
 (0)