Fix typos and tweak language in docs
This commit is contained in:
parent
fc519c3bb2
commit
64e40ef604
|
|
@ -3,7 +3,7 @@ API
|
|||
|
||||
## RxSwift supported operators
|
||||
|
||||
In some cases there are multiple aliases for the same operator, because on different platforms / implementations, the same operation is sometimes called differently. Sometimes this is because historical reasons, sometimes because of reserved language keywords.
|
||||
In some cases there are multiple aliases for the same operator, because on different platforms / implementations, the same operation is sometimes named differently. Sometimes this is because of historical reasons, while sometimes because of reserved language keywords.
|
||||
|
||||
When lacking a strong community consensus, RxSwift will usually include multiple aliases.
|
||||
|
||||
|
|
@ -26,6 +26,7 @@ Operators are stateless by default.
|
|||
* [`timer`](http://reactivex.io/documentation/operators/timer.html)
|
||||
|
||||
#### Transforming Observables
|
||||
|
||||
* [`buffer`](http://reactivex.io/documentation/operators/buffer.html)
|
||||
* [`flatMap`](http://reactivex.io/documentation/operators/flatmap.html)
|
||||
* [`flatMapFirst`](http://reactivex.io/documentation/operators/flatmap.html)
|
||||
|
|
@ -35,6 +36,7 @@ Operators are stateless by default.
|
|||
* [`window`](http://reactivex.io/documentation/operators/window.html)
|
||||
|
||||
#### Filtering Observables
|
||||
|
||||
* [`debounce` / `throttle`](http://reactivex.io/documentation/operators/debounce.html)
|
||||
* [`distinctUntilChanged`](http://reactivex.io/documentation/operators/distinct.html)
|
||||
* [`elementAt`](http://reactivex.io/documentation/operators/elementat.html)
|
||||
|
|
@ -71,6 +73,7 @@ Operators are stateless by default.
|
|||
* debug
|
||||
|
||||
#### Conditional and Boolean Operators
|
||||
|
||||
* [`amb`](http://reactivex.io/documentation/operators/amb.html)
|
||||
* [`skipWhile`](http://reactivex.io/documentation/operators/skipwhile.html)
|
||||
* [`skipUntil`](http://reactivex.io/documentation/operators/skipuntil.html)
|
||||
|
|
|
|||
|
|
@ -2,18 +2,18 @@
|
|||
|
||||
RxSwift is somewhat similar to ReactiveCocoa since ReactiveCocoa borrows a large number of concepts from Rx.
|
||||
|
||||
One of the main goals of this project was to create a significantly simpler interface that is more aligned with other Rx implementations, offers richer concurrency model, offers more optimization opportunities and is more aligned with built-in Swift error handling mechanisms.
|
||||
One of the main goals of this project was to create a significantly simpler interface that is more aligned with other Rx implementations, offers a richer concurrency model, offers more optimization opportunities and is more aligned with built-in Swift error handling mechanisms.
|
||||
|
||||
We've also decided to only rely on Swift/llvm compiler and not introduce any external dependencies.
|
||||
We've also decided to only rely on the Swift/llvm compiler and not introduce any external dependencies.
|
||||
|
||||
Probably the main difference between these projects is the difference of approach in building abstractions.
|
||||
Probably the main difference between these projects is in their approach in building abstractions.
|
||||
|
||||
The main goal of RxSwift project is to provide environment agnostic compositional computation glue abstracted in the form of observable sequences.
|
||||
We then aim to improve the experience of using RxSwift on specific platforms. To do this RxCocoa uses those generic computations to build more practical abstractions and wrap Foundation/Cocoa/UKit frameworks. That means that other libraries give context and semantics to the generic computation engine RxSwift provides such as `Driver`, `ControlProperty`, `ControlEvent`s and more.
|
||||
We then aim to improve the experience of using RxSwift on specific platforms. To do this, RxCocoa uses generic computations to build more practical abstractions and wrap Foundation/Cocoa/UKit frameworks. That means that other libraries give context and semantics to the generic computation engine RxSwift provides such as `Driver`, `ControlProperty`, `ControlEvent`s and more.
|
||||
|
||||
One of the benefits to representing all these abstractions as a single concept; _observable sequences_, is that all computation abstractions built on top of them are also composable in the same fundamental way. They all follow the same contract and implement the same interface.
|
||||
One of the benefits to representing all of these abstractions as a single concept - _observable sequences_ - is that all computation abstractions built on top of them are also composable in the same fundamental way. They all follow the same contract and implement the same interface.
|
||||
It is also easy to create flexible subscription (resource) sharing strategies or use one of the built-in ones: `share`, `shareReplay`, `publish`, `multicast`, `shareReplayLatestWhileConnected`...
|
||||
|
||||
This library also offers fine-tunable concurrency model. In case concurrent schedulers are used, observable sequence operators will preserve sequence properties. The same observable sequence operators will also know how to detect and optimally use known serial schedulers. ReactiveCocoa has a more limited concurrency model and only allows serial schedulers.
|
||||
This library also offers a fine-tunable concurrency model. If concurrent schedulers are used, observable sequence operators will preserve sequence properties. The same observable sequence operators will also know how to detect and optimally use known serial schedulers. ReactiveCocoa has a more limited concurrency model and only allows serial schedulers.
|
||||
|
||||
Multithreaded programming is really hard and detecting non trivial loops is even harder. That's why all operators are built in a fault tolerant way. Even in case element generation occurs during element processing (recursion), operators will try to handle that situation and prevent deadlocks. That means that in worst possible case programming error will cause stack overflow, but users won't have to manually kill the app, and you will get crash report in error reporting systems so you can find and fix the problem.
|
||||
Multithreaded programming is really hard and detecting non trivial loops is even harder. That's why all operators are built in a fault tolerant way. Even if element generation occurs during element processing (recursion), operators will try to handle that situation and prevent deadlocks. This means that in the worst possible case programming error will cause stack overflow, but users won't have to manually kill the app, and you will get a crash report in error reporting systems so you can find and fix the problem.
|
||||
|
|
|
|||
|
|
@ -11,29 +11,29 @@ enum Event<Element> {
|
|||
}
|
||||
```
|
||||
|
||||
Let's discuss pros and cons of `ErrorType` being generic.
|
||||
Let's discuss the pros and cons of `ErrorType` being generic.
|
||||
|
||||
If you have generic error type you create additional impedance mismatch between two observables.
|
||||
If you have a generic error type, you create additional impedance mismatch between two observables.
|
||||
|
||||
Let's say you have:
|
||||
|
||||
`Observable<String, E1>` and `Observable<String, E2>`
|
||||
|
||||
There isn't much you can do with them without figuring out what will be the resulting error type.
|
||||
There isn't much you can do with them without figuring out what the resulting error type will be.
|
||||
|
||||
Will it be `E1`, `E2` or some new `E3` maybe? So you need a new set of operators just to solve that impedance mismatch.
|
||||
Will it be `E1`, `E2` or some new `E3` maybe? So, you would need a new set of operators just to solve that impedance mismatch.
|
||||
|
||||
This for sure hurts composition properties, and Rx really doesn't care about why sequence fails, it just usually forwards failure further down the observable chain.
|
||||
This hurts composition properties, and Rx isn't concerned with why a sequence fails, it just usually forwards failures further down the observable chain.
|
||||
|
||||
There is additional problem that maybe in some cases operators will fail for some internal error, and in that case you won't be able to construct resulting error and report failure.
|
||||
There is an additional problem that, in some cases, operators might fail due to some internal error, in which case you wouldn't be able to construct a resulting error and report failure.
|
||||
|
||||
But ok, let's ignore that and assume we can use that to model sequences that don't error out. It looks like it could be useful for that purpose?
|
||||
But OK, let's ignore that and assume we can use that to model sequences that don't error out. Could it be useful for that purpose?
|
||||
|
||||
Well yes, it potentially could be, but lets consider why would you want to use sequences that don't error out.
|
||||
Well yes, it potentially could be, but let's consider why you would want to use sequences that don't error out.
|
||||
|
||||
One obvious application would be for permanent streams in UI layer that drive entire UI. But when you consider that case, it's not really only sufficient to use compiler to prove that sequences don't error out, you also need to prove other properties. Like that elements are observed on `MainScheduler`.
|
||||
One obvious application would be for permanent streams in the UI layer that drive the entire UI. When you consider that case, it's not really sufficient to only use the compiler to prove that sequences don't error out, you also need to prove other properties. For instance, that elements are observed on `MainScheduler`.
|
||||
|
||||
What you really need is a generic way to prove traits for sequences (`Observables`). And you could be interested in a lot of properties. For example:
|
||||
What you really need is a generic way to prove traits for observable sequences. There are a lot of properties you could be interested in. For example:
|
||||
|
||||
* sequence terminates in finite time (server side)
|
||||
* sequence contains only one element (if you are running some computation)
|
||||
|
|
@ -41,19 +41,19 @@ What you really need is a generic way to prove traits for sequences (`Observable
|
|||
* sequence doesn't error out, never terminates and elements are delivered on main scheduler, and has refcounted sharing (UI)
|
||||
* sequence doesn't error out, never terminates and elements are delivered on specific background scheduler (audio engine)
|
||||
|
||||
What you really want is a general compiler enforced system of traits for observable sequences, and a set of invariant operators for those wanted properties.
|
||||
What you really want is a general compiler-enforced system of traits for observable sequences, and a set of invariant operators for those wanted properties.
|
||||
|
||||
A good analogy IMHO would be
|
||||
A good analogy would be:
|
||||
|
||||
```
|
||||
1, 3.14, e, 2.79, 1 + 1i <-> Observable<E>
|
||||
1m/s, 1T, 5kg, 1.3 pounds <-> Errorless observable, UI observable, Finite observable ...
|
||||
```
|
||||
|
||||
There are many ways how to do that in Swift by either using composition or inheritance of observables.
|
||||
There are many ways to do such a thing in Swift by either using composition or inheritance of observables.
|
||||
|
||||
Additional benefit of using unit system is that you can prove that UI code is executing on same scheduler and thus use lockless operators for all transformations.
|
||||
An additional benefit of using a unit system is that you can prove that UI code is executing on the same scheduler and thus use lockless operators for all transformations.
|
||||
|
||||
Since Rx already doesn't have locks for single sequence operations, and all of the remaining locks are in statefull components (aka UI), that would practically remove all of the remaining locks out of Rx code and create compiler enforced lockless Rx code.
|
||||
Since RxSwift already doesn't have locks for single sequence operations, and all of the locks that do exist are in stateful components (e.g. UI), there are practically no locks in RxSwift code, which allows for such details to be compiler enforced.
|
||||
|
||||
So IMHO, there really is no benefit of using typed Errors that couldn't be achieved cleaner in other ways while preserving Rx compositional semantics. And other ways also have huge other benefits.
|
||||
There really is no benefit to using typed Errors that couldn't be achieved in other cleaner ways while preserving Rx compositional semantics.
|
||||
|
|
|
|||
|
|
@ -9,92 +9,82 @@ Examples
|
|||
|
||||
## Calculated variable
|
||||
|
||||
Let's first start with some imperative swift code.Sw
|
||||
The purpose of example is to bind identifier `c` to a value calculated from `a` and `b` if some condition is satisfied.
|
||||
First, let's start with some imperative code.
|
||||
The purpose of this example is to bind the identifier `c` to a value calculated from `a` and `b` if some condition is satisfied.
|
||||
|
||||
Here is the imperative swift code that calculates the value of `c`:
|
||||
Here is the imperative code that calculates the value of `c`:
|
||||
|
||||
```swift
|
||||
// this is usual imperative code
|
||||
// this is standard imperative code
|
||||
var c: String
|
||||
var a = 1 // this will only assign value `1` to `a` once
|
||||
var b = 2 // this will only assign value `2` to `b` once
|
||||
var a = 1 // this will only assign the value `1` to `a` once
|
||||
var b = 2 // this will only assign the value `2` to `b` once
|
||||
|
||||
if a + b >= 0 {
|
||||
c = "\(a + b) is positive" // this will only assign value to `c` once
|
||||
c = "\(a + b) is positive" // this will only assign the value to `c` once
|
||||
}
|
||||
```
|
||||
|
||||
The value of `c` is now `3 is positive`. But if we change the value of `a` to `4`, `c` will still contain the old value.
|
||||
The value of `c` is now `3 is positive`. However, if we change the value of `a` to `4`, `c` will still contain the old value.
|
||||
|
||||
```swift
|
||||
a = 4 // c will still be equal "3 is positive" which is not good
|
||||
// c should be equal to "6 is positive" because 4 + 2 = 6
|
||||
a = 4 // `c` will still be equal to "3 is positive" which is not good
|
||||
// we want `c` to be equal to "6 is positive" since 4 + 2 = 6
|
||||
```
|
||||
|
||||
This is not the wanted behavior.
|
||||
This is not the desired behavior.
|
||||
|
||||
To integrate RxSwift framework into your project just include framework in your project and write `import RxSwift`.
|
||||
|
||||
This is the same logic using RxSwift.
|
||||
This is the improved logic using RxSwift:
|
||||
|
||||
```swift
|
||||
let a /*: Observable<Int>*/ = Variable(1) // a = 1
|
||||
let b /*: Observable<Int>*/ = Variable(2) // b = 2
|
||||
|
||||
// This will "bind" rx variable `c` to definition
|
||||
// if a + b >= 0 {
|
||||
// c = "\(a + b) is positive"
|
||||
// }
|
||||
|
||||
// combines latest values of variables `a` and `b` using `+`
|
||||
let c = Observable.combineLatest(a.asObservable(), b.asObservable()) { $0 + $1 }
|
||||
.filter { $0 >= 0 } // if `a + b >= 0` is true, `a + b` is passed to map operator
|
||||
.filter { $0 >= 0 } // if `a + b >= 0` is true, `a + b` is passed to the map operator
|
||||
.map { "\($0) is positive" } // maps `a + b` to "\(a + b) is positive"
|
||||
|
||||
// Since initial values are a = 1, b = 2
|
||||
// 1 + 2 = 3 which is >= 0, `c` is initially equal to "3 is positive"
|
||||
// Since the initial values are a = 1 and b = 2
|
||||
// 1 + 2 = 3 which is >= 0, so `c` is initially equal to "3 is positive"
|
||||
|
||||
// To pull values out of rx variable `c`, subscribe to values from `c`.
|
||||
// `subscribeNext` means subscribe to next (fresh) values of variable `c`.
|
||||
// To pull values out of the Rx `Observable` `c`, subscribe to values from `c`.
|
||||
// `subscribeNext` means subscribe to the next (fresh) values of `c`.
|
||||
// That also includes the initial value "3 is positive".
|
||||
c.subscribeNext { print($0) } // prints: "3 is positive"
|
||||
|
||||
// Now let's increase the value of `a`
|
||||
// a = 4 is in RxSwift
|
||||
// Now, let's increase the value of `a`
|
||||
a.value = 4 // prints: 6 is positive
|
||||
// Sum of latest values is now `4 + 2`, `6` is >= 0, map operator
|
||||
// produces "6 is positive" and that result is "assigned" to `c`.
|
||||
// The sum of the latest values, `4` and `2`, is now `6`.
|
||||
// Since this is `>= 0`, the `map` operator produces "6 is positive"
|
||||
// and that result is "assigned" to `c`.
|
||||
// Since the value of `c` changed, `{ print($0) }` will get called,
|
||||
// and "6 is positive" is printed.
|
||||
// and "6 is positive" will be printed.
|
||||
|
||||
// Now let's change the value of `b`
|
||||
// b = -8 is in RxSwift
|
||||
// Now, let's change the value of `b`
|
||||
b.value = -8 // doesn't print anything
|
||||
// Sum of latest values is `4 + (-8)`, `-4` is not >= 0, map doesn't
|
||||
// get executed.
|
||||
// That means that `c` still contains "6 is positive" and that's correct.
|
||||
// Since `c` hasn't been updated, that means next value hasn't been produced,
|
||||
// The sum of the latest values, `4 + (-8)`, is `-4`.
|
||||
// Since this is not `>= 0`, `map` doesn't get executed.
|
||||
// This means that `c` still contains "6 is positive"
|
||||
// Since `c` hasn't been updated, a new "next" value hasn't been produced,
|
||||
// and `{ print($0) }` won't be called.
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
## Simple UI bindings
|
||||
|
||||
* instead of binding to variables, let's bind to text field values (rx_text)
|
||||
* next, parse that into an int and calculate if the number is prime using an async API (map)
|
||||
* if text field value is changed before async call completes, new async call will be enqueued (concat)
|
||||
* bind results to label (bindTo(resultLabel.rx_text))
|
||||
* Instead of binding to variables, let's bind to `UITextField` values using the `rx_text` property
|
||||
* Next, `map` the `String` into an `Int` and determine if the number is prime using an async API
|
||||
* If the text is changed before the async call completes, a new async call will replace it via `concat`
|
||||
* Bind the results to a `UILabel`
|
||||
|
||||
```swift
|
||||
let subscription/*: Disposable */ = primeTextField.rx_text // type is Observable<String>
|
||||
.map { WolframAlphaIsPrime(Int($0) ?? 0) } // type is Observable<Observable<Prime>>
|
||||
.map { WolframAlphaIsPrime(Int($0) ?? 0) } // type is Observable<Observable<Prime>>
|
||||
.concat() // type is Observable<Prime>
|
||||
.map { "number \($0.n) is prime? \($0.isPrime)" } // type is Observable<String>
|
||||
.bindTo(resultLabel.rx_text) // return Disposable that can be used to unbind everything
|
||||
|
||||
// This will set resultLabel.text to "number 43 is prime? true" after
|
||||
// This will set `resultLabel.text` to "number 43 is prime? true" after
|
||||
// server call completes.
|
||||
primeTextField.text = "43"
|
||||
|
||||
|
|
@ -104,13 +94,13 @@ primeTextField.text = "43"
|
|||
subscription.dispose()
|
||||
```
|
||||
|
||||
All of the operators used in this example are the same operators used in the first example with variables. Nothing special about it.
|
||||
All of the operators used in this example are the same operators used in the first example with variables. There's nothing special about it.
|
||||
|
||||
## Autocomplete
|
||||
|
||||
If you are new to Rx, next example will probably be a little overwhelming, but it's here to demonstrate how RxSwift code looks like in real world examples.
|
||||
If you are new to Rx, the next example will probably be a little overwhelming at first. However, it's here to demonstrate how RxSwift code looks in the real-world.
|
||||
|
||||
The third example is a real world, complex UI async validation logic, with progress notifications.
|
||||
This example contains complex async UI validation logic with progress notifications.
|
||||
All operations are cancelled the moment `disposeBag` is deallocated.
|
||||
|
||||
Let's give it a shot.
|
||||
|
|
@ -129,20 +119,20 @@ self.usernameOutlet.rx_text
|
|||
return Observable.just((valid: false, message: "Username can't be empty."))
|
||||
}
|
||||
|
||||
...
|
||||
// ...
|
||||
|
||||
// Every user interface probably shows some state while async operation
|
||||
// is executing.
|
||||
// Let's assume that we want to show "Checking availability" while waiting for result.
|
||||
// valid parameter can be
|
||||
// User interfaces should probably show some state while async operations
|
||||
// are executing.
|
||||
// Let's assume that we want to show "Checking availability" while waiting for a result.
|
||||
// Valid parameters can be:
|
||||
// * true - is valid
|
||||
// * false - not valid
|
||||
// * false - is not valid
|
||||
// * nil - validation pending
|
||||
typealias LoadingInfo = (valid : String?, message: String?)
|
||||
typealias LoadingInfo = (valid: String?, message: String?)
|
||||
let loadingValue : LoadingInfo = (valid: nil, message: "Checking availability ...")
|
||||
|
||||
// This will fire a server call to check if the username already exists.
|
||||
// Guess what, its type is `Observable<ValidationResult>`
|
||||
// Its type is `Observable<ValidationResult>`
|
||||
return API.usernameAvailable(username)
|
||||
.map { available in
|
||||
if available {
|
||||
|
|
@ -156,26 +146,25 @@ self.usernameOutlet.rx_text
|
|||
.startWith(loadingValue)
|
||||
}
|
||||
// Since we now have `Observable<Observable<ValidationResult>>`
|
||||
// we somehow need to return to normal `Observable` world.
|
||||
// We could use `concat` operator from second example, but we really
|
||||
// want to cancel pending asynchronous operation if new username is
|
||||
// provided.
|
||||
// That's what `switchLatest` does
|
||||
// we need to somehow return to a simple `Observable<ValidationResult>`.
|
||||
// We could use the `concat` operator from the second example, but we really
|
||||
// want to cancel pending asynchronous operations if a new username is provided.
|
||||
// That's what `switchLatest` does.
|
||||
.switchLatest()
|
||||
// Now we need to bind that to the user interface somehow.
|
||||
// Good old `subscribeNext` can do that
|
||||
// Good old `subscribeNext` can do that.
|
||||
// That's the end of `Observable` chain.
|
||||
// This will produce a `Disposable` object that can unbind everything and cancel
|
||||
// pending async operations.
|
||||
.subscribeNext { valid in
|
||||
errorLabel.textColor = validationColor(valid)
|
||||
errorLabel.text = valid.message
|
||||
}
|
||||
// Why would we do it manually, that's tedious,
|
||||
// let's dispose everything automagically on view controller dealloc.
|
||||
// This will produce a `Disposable` object that can unbind everything and cancel
|
||||
// pending async operations.
|
||||
// Instead of doing it manually, which is tedious,
|
||||
// let's dispose everything automagically upon view controller dealloc.
|
||||
.addDisposableTo(disposeBag)
|
||||
```
|
||||
|
||||
Can't get any simpler than this. There are [more examples](../RxExample) in the repository, so feel free to check them out.
|
||||
It doesn't get any simpler than that. There are [more examples](../RxExample) in the repository, so feel free to check them out.
|
||||
|
||||
They include examples on how to use it in the context of MVVM pattern or without it.
|
||||
They include examples on how to use Rx in the context of MVVM pattern or without it.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
**Please copy the following template [here](https://github.com/ReactiveX/RxSwift/issues/new) and fill in the missing fields so we can help you as soon as possible.**
|
||||
|
||||
**If you don't have something to report in the following format, maybe you don't really have an issue an it's easier and faster to resolve doubts in [slack channel](http://http://slack.rxswift.org/) first.**
|
||||
**If you don't have something to report in the following format, it will probably be easier and faster to ask in the [slack channel](http://http://slack.rxswift.org/) first.**
|
||||
|
||||
```
|
||||
*Short description*:
|
||||
|
|
@ -17,31 +17,29 @@
|
|||
|
||||
*Expected outcome*:
|
||||
|
||||
what do you expect to happen goes here
|
||||
what you expect to happen goes here
|
||||
|
||||
*What actually happens*:
|
||||
|
||||
what actually happens goes here
|
||||
|
||||
// filling additional information below is optional, but resolving your issue could be potentially a lot faster
|
||||
// filling in additional information below is optional, but resolving your issue could potentially be a lot faster
|
||||
|
||||
*Integration method*:
|
||||
(so we don't lose time investigating wrong integration)
|
||||
*Installation method*:
|
||||
(so we don't waste time investigating an incorrect integration)
|
||||
* CocoaPods
|
||||
* Carthage
|
||||
* Git submodules
|
||||
|
||||
*I have multiple versions of Xcode installed*:
|
||||
(so we can understand can this cause your issue)
|
||||
(so we can know if this is a potential cause of your issue)
|
||||
* yes (which ones)
|
||||
* no
|
||||
|
||||
*How long have I been using this project*:
|
||||
*Level of RxSwift knowledge*:
|
||||
(this is so we can understand your level of knowledge
|
||||
and formulate the response in an appropriate manner)
|
||||
* just starting
|
||||
* I have a small code base
|
||||
* I have a significant code base
|
||||
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ let package = Package(
|
|||
)
|
||||
```
|
||||
|
||||
What does work:
|
||||
What works:
|
||||
* Distribution using Swift Package Manager
|
||||
* Single Threaded mode (CurrentThreadScheduler)
|
||||
* Half of unit tests are passing.
|
||||
* Half of the unit tests are passing.
|
||||
* Projects that can be compiled and "used":
|
||||
* RxSwift
|
||||
* RxBlocking
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ Math Behind Rx
|
|||
|
||||
## Duality between Observer and Iterator / Enumerator / Generator / Sequences
|
||||
|
||||
There is a duality between the observer and generator patterns. This is what enables us to transition from the async callback world to synchronous world of sequence transformations.
|
||||
There is a duality between the observer and generator patterns. This is what enables us to transition from the async callback world to the synchronous world of sequence transformations.
|
||||
|
||||
In short, the enumerator and observer patterns both describe sequences. It's fairly obvious why the enumerator defines a sequence, but the observer is slightly more complicated.
|
||||
In short, the enumerator and observer patterns both describe sequences. It's fairly obvious why the enumerator defines a sequence, but the observer is slightly more complicated.
|
||||
|
||||
There is, however, a pretty simple example that doesn't require a lot of mathematical knowledge. Assume that you are observing the position of your mouse cursor on screen at given time periods. Over time, these mouse positions form a sequence. This is, in essence, an observer sequence.
|
||||
There is, however, a pretty simple example that doesn't require a lot of mathematical knowledge. Assume that you are observing the position of your mouse cursor on screen at given times. Over time, these mouse positions form a sequence. This is, in essence, an observable sequence.
|
||||
|
||||
There are two basic ways elements of a sequence can be accessed:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
# Migration from RxSwift 1.9 to RxSwift 2.0 version
|
||||
# Migration from RxSwift 1.9 to RxSwift 2.0
|
||||
|
||||
The migration should be pretty straightforward. The changes are mostly cosmetic, so all features are still there.
|
||||
The migration should be pretty straightforward. Changes are mostly cosmetic, so all features are still there.
|
||||
|
||||
* Find replace all `>- ` to `.`
|
||||
* Find replace all "variable" to "shareReplay(1)"
|
||||
* Find replace all "catch" to "catchErrorJustReturn"
|
||||
* Find replace all "returnElement" to "Observable.just"
|
||||
* Find replace all "failWith" to "Observable.error"
|
||||
* Find replace all "never" to "Observable.never"
|
||||
* Find replace all "empty" to "Observable.empty"
|
||||
* Since we've moved from `>-` to `.`, free functions are now methods, so it's `.switchLatest()`, `.distinctUntilChanged()`, ... instead of `>- switchLatest`, `>- distinctUntilChanged`
|
||||
* we've moved from free functions to extensions so it's now `[a, b, c].concat()`, `.merge()`, ... instead of `concat([a, b, c])`, `merge(sequences)`
|
||||
* Now it's `subscribe { n in ... }.addDisposableTo(disposeBag)` instead of `>- disposeBag.addDisposable`
|
||||
* Method `next` on `Variable` is now `value` setter
|
||||
* If you want to use `tableViews`/`collectionViews`, this is the basic use case now
|
||||
* Find replace all `variable` to `shareReplay(1)`
|
||||
* Find replace all `catch` to `catchErrorJustReturn`
|
||||
* Find replace all `returnElement` to `Observable.just`
|
||||
* Find replace all `failWith` to `Observable.error`
|
||||
* Find replace all `never` to `Observable.never`
|
||||
* Find replace all `empty` to `Observable.empty`
|
||||
* Since we've moved from `>-` to `.`, free functions are now methods, so use `.switchLatest()`, `.distinctUntilChanged()`, ... instead of `>- switchLatest`, `>- distinctUntilChanged`
|
||||
* We've moved from free functions to extensions so it's now `[a, b, c].concat()`, `.merge()`, ... instead of `concat([a, b, c])`, `merge(sequences)`
|
||||
* Similarly, it's now `subscribe { n in ... }.addDisposableTo(disposeBag)` instead of `>- disposeBag.addDisposable`
|
||||
* The method `next` on `Variable` is now `value` setter
|
||||
* If you want to use `UITableView` and/or `UICollectionView`, this is the basic use case now:
|
||||
|
||||
```swift
|
||||
viewModel.rows
|
||||
.bindTo(resultsTableView.rx_itemsWithCellIdentifier("WikipediaSearchCell", cellType: WikipediaSearchCell.self)) { (_, viewModel, cell) in
|
||||
cell.viewModel = viewModel
|
||||
}
|
||||
.addDisposableTo(disposeBag)
|
||||
.bindTo(resultsTableView.rx_itemsWithCellIdentifier("WikipediaSearchCell", cellType: WikipediaSearchCell.self)) { (_, viewModel, cell) in
|
||||
cell.viewModel = viewModel
|
||||
}
|
||||
.addDisposableTo(disposeBag)
|
||||
```
|
||||
|
||||
If you have any more doubts how to write some concept in RxSwift 2.0 version, check out [Example app](../RxExample) or playgrounds.
|
||||
If you have any doubts about how some concept in RxSwift 2.0 works, check out the [Example app](../RxExample) or playgrounds.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@
|
|||
To use playgrounds:
|
||||
|
||||
* Open `Rx.xcworkspace`
|
||||
* Build `RxSwift-OSX` scheme
|
||||
* And then open `Rx` playground in `Rx.xcworkspace` tree view.
|
||||
* Choose `View > Show Debug Area`
|
||||
* Build the `RxSwift-OSX` scheme
|
||||
* Open `Rx` playground in the `Rx.xcworkspace` tree view.
|
||||
* Choose `View > Debug Area > Show Debug Area`
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Unit Tests
|
|||
|
||||
## Testing custom operators
|
||||
|
||||
Library uses `RxTests` for all of RxSwift operator tests so you can take a look at AllTests-* target inside the project `Rx.xcworkspace`.
|
||||
RxSwift uses `RxTests` for all operator tests, located in the AllTests-* target inside the project `Rx.xcworkspace`.
|
||||
|
||||
This is an example of a typical `RxSwift` operator unit test:
|
||||
|
||||
|
|
@ -12,16 +12,16 @@ func testMap_Range() {
|
|||
// Initializes test scheduler.
|
||||
// Test scheduler implements virtual time that is
|
||||
// detached from local machine clock.
|
||||
// That enables running the simulation as fast as possible
|
||||
// This enables running the simulation as fast as possible
|
||||
// and proving that all events have been handled.
|
||||
let scheduler = TestScheduler(initialClock: 0)
|
||||
|
||||
// Creates a mock hot observable sequence.
|
||||
// The sequence will emit events at following
|
||||
// times no matter is there some observer subscribed.
|
||||
// The sequence will emit events at desginated
|
||||
// times, no matter if there are observers subscribed or not.
|
||||
// (that's what hot means).
|
||||
// This observable sequence will also record all subscriptions
|
||||
// made during it's lifetime (`subscriptions` property).
|
||||
// made during its lifetime (`subscriptions` property).
|
||||
let xs = scheduler.createHotObservable([
|
||||
next(150, 1), // first argument is virtual time, second argument is element value
|
||||
next(210, 0),
|
||||
|
|
@ -32,10 +32,10 @@ func testMap_Range() {
|
|||
])
|
||||
|
||||
// `start` method will by default:
|
||||
// * run the simulation and record all events
|
||||
// * Run the simulation and record all events
|
||||
// using observer referenced by `res`.
|
||||
// * subscribe at virtual time 200
|
||||
// * dispose subscription at virtual time 1000
|
||||
// * Subscribe at virtual time 200
|
||||
// * Dispose subscription at virtual time 1000
|
||||
let res = scheduler.start { xs.map { $0 * 2 } }
|
||||
|
||||
let correctMessages = [
|
||||
|
|
@ -57,9 +57,9 @@ func testMap_Range() {
|
|||
|
||||
## Testing operator compositions (view models, components)
|
||||
|
||||
Examples how to test operator compositions are contained inside `Rx.xcworkspace` > `RxExample-iOSTests` target.
|
||||
Examples of how to test operator compositions are contained inside `Rx.xcworkspace` > `RxExample-iOSTests` target.
|
||||
|
||||
It easy to define `RxTests` extensions so you can write your tests in a readable way. Provided examples inside `RxExample-iOSTests` are just a tip how you can write those extensions, but there is a lot of possibilities how to write those tests.
|
||||
It's easy to define `RxTests` extensions so you can write your tests in a readable way. Provided examples inside `RxExample-iOSTests` are just suggestions on how you can write those extensions, but there are a lot of possibilities on how to write those tests.
|
||||
|
||||
```swift
|
||||
// expected events and test data
|
||||
|
|
|
|||
|
|
@ -1,42 +1,42 @@
|
|||
Units
|
||||
=====
|
||||
|
||||
This document will try to describe what units are, why they are a useful concept, how to use and create them.
|
||||
This document will try to describe what units are, why they are a useful concept, and how to use and create them.
|
||||
|
||||
* [Why](#why)
|
||||
* [How do they work](#how-do-they-work)
|
||||
* [Why are they named Units](#why-are-they-named-units)
|
||||
* [How they work](#how-they-work)
|
||||
* [Why they are named Units](#why-they-are-named-units)
|
||||
* [RxCocoa units](#rxcocoa-units)
|
||||
* [Driver unit](#driver-unit)
|
||||
* [Why was it named Driver](#why-was-it-named-driver)
|
||||
* [Why it's named Driver](#why-its-named-driver)
|
||||
* [Practical usage example](#practical-usage-example)
|
||||
|
||||
## Why
|
||||
|
||||
Swift has a powerful type system that can be used to improve correctness and stability of applications and make using Rx a more intuitive and straightforward experience.
|
||||
|
||||
**Units are so far specific only to the [RxCocoa](https://github.com/ReactiveX/RxSwift/tree/master/RxCocoa) project, but the same principles could be implemented easily in other Rx implementations if necessary. There is no private API magic needed.**
|
||||
**Units are specific only to the [RxCocoa](https://github.com/ReactiveX/RxSwift/tree/master/RxCocoa) project. However, the same principles could easily be implemented in other Rx implementations, if necessary. There is no private API magic needed.**
|
||||
|
||||
**Units are totally optional, you can use raw observable sequences everywhere in your program and all RxCocoa APIs work with observable sequences.**
|
||||
**Units are totally optional. You can use raw observable sequences everywhere in your program and all RxCocoa APIs work with observable sequences.**
|
||||
|
||||
Units also help communicate and ensure observable sequence properties across interface boundaries.
|
||||
|
||||
Here are some of the properties that are important when writing Cocoa/UIKit applications.
|
||||
|
||||
* can't error out
|
||||
* observe on main scheduler
|
||||
* subscribe on main scheduler
|
||||
* sharing side effects
|
||||
* Can't error out
|
||||
* Observe on main scheduler
|
||||
* Subscribe on main scheduler
|
||||
* Sharing side effects
|
||||
|
||||
## How do they work
|
||||
## How they work
|
||||
|
||||
In its core it's just a struct with a reference to observable sequence.
|
||||
At its core, it's just a struct with a reference to observable sequence.
|
||||
|
||||
You can think of them as a kind of builder pattern for observable sequences. When sequence is built, calling `.asObservable()` will transform a unit into a vanilla observable sequence.
|
||||
You can think of them as a kind of builder pattern for observable sequences. When a sequence is built, calling `.asObservable()` will transform a unit into a vanilla observable sequence.
|
||||
|
||||
## Why are they named Units
|
||||
## Why they are named Units
|
||||
|
||||
Analogies help reason about unfamiliar concepts, here are some ideas how units in physics and RxCocoa (rx units) are similar.
|
||||
Using a couple analogies will help us reason about unfamiliar concepts. Here are some analogies showing how units in physics and RxCocoa (Rx units) are similar.
|
||||
|
||||
Analogies:
|
||||
|
||||
|
|
@ -45,14 +45,14 @@ Analogies:
|
|||
| number (one value) | observable sequence (sequence of values) |
|
||||
| dimensional unit (m, s, m/s, N ...) | Swift struct (Driver, ControlProperty, ControlEvent, Variable, ...) |
|
||||
|
||||
Physical unit is a pair of a number and a corresponding dimensional unit.<br/>
|
||||
Rx unit is a pair of an observable sequence and a corresponding struct that describes observable sequence properties.
|
||||
A physical unit is a pair of a number and a corresponding dimensional unit.<br/>
|
||||
An Rx unit is a pair of an observable sequence and a corresponding struct that describes observable sequence properties.
|
||||
|
||||
Numbers are the basic composition glue when working with physical units: usually real or complex numbers.<br/>
|
||||
Observable sequences are the basic composition glue when working with rx units.
|
||||
Numbers are the basic compositional glue when working with physical units: usually real or complex numbers.<br/>
|
||||
Observable sequences are the basic compositional glue when working with Rx units.
|
||||
|
||||
Physical units and [dimensional analysis](https://en.wikipedia.org/wiki/Dimensional_analysis#Checking_equations_that_involve_dimensions) can alleviate certain class of errors during complex calculations.<br/>
|
||||
Type checking rx units can alleviate certain class of logic errors when writing reactive programs.
|
||||
Physical units and [dimensional analysis](https://en.wikipedia.org/wiki/Dimensional_analysis#Checking_equations_that_involve_dimensions) can alleviate certain classes of errors during complex calculations.<br/>
|
||||
Type checking Rx units can alleviate certain classes of logic errors when writing reactive programs.
|
||||
|
||||
Numbers have operators: `+`, `-`, `*`, `/`.<br/>
|
||||
Observable sequences also have operators: `map`, `filter`, `flatMap` ...
|
||||
|
|
@ -62,9 +62,9 @@ Physics units define operations by using corresponding number operations. E.g.
|
|||
`/` operation on physical units is defined using `/` operation on numbers.
|
||||
|
||||
11 m / 0.5 s = ...
|
||||
* first convert unit to **numbers** and **apply** `/` **operator** `11 / 0.5 = 22`
|
||||
* then calculate unit (m / s)
|
||||
* combine the result = 22 m / s
|
||||
* First, convert the unit to **numbers** and **apply** `/` **operator** `11 / 0.5 = 22`
|
||||
* Then, calculate the unit (m / s)
|
||||
* Lastly, combine the result = 22 m / s
|
||||
|
||||
Rx units define operations by using corresponding observable sequence operations (this is how operators work internally). E.g.
|
||||
|
||||
|
|
@ -75,12 +75,12 @@ let d: Driver<Int> = Drive.just(11)
|
|||
driver.map { $0 / 0.5 } = ...
|
||||
```
|
||||
|
||||
* first convert driver to **observable sequence** and **apply** `map` **operator**
|
||||
* First, convert `Driver` to **observable sequence** and **apply** `map` **operator**
|
||||
```swift
|
||||
let mapped = driver.asObservable().map { $0 / 0.5 } // this `map` is defined on observable sequence
|
||||
```
|
||||
|
||||
* then combine that to get the unit value
|
||||
* Then, combine that to get the unit value
|
||||
```swift
|
||||
let result = Driver(mapped)
|
||||
```
|
||||
|
|
@ -88,10 +88,10 @@ let result = Driver(mapped)
|
|||
There is a set of basic units in physics [(`m`, `kg`, `s`, `A`, `K`, `cd`, `mol`)](https://en.wikipedia.org/wiki/SI_base_unit) that is orthogonal.<br/>
|
||||
There is a set of basic interesting properties for observable sequences in `RxCocoa` that is orthogonal.
|
||||
|
||||
* can't error out
|
||||
* observe on main scheduler
|
||||
* subscribe on main scheduler
|
||||
* sharing side effects
|
||||
* Can't error out
|
||||
* Observe on main scheduler
|
||||
* Subscribe on main scheduler
|
||||
* Sharing side effects
|
||||
|
||||
Derived units in physics sometimes have special names.<br/>
|
||||
E.g.
|
||||
|
|
@ -109,16 +109,16 @@ ControlProperty = (sharing side effects) * (subscribe on main scheduler)
|
|||
Variable = (can't error out) * (sharing side effects)
|
||||
```
|
||||
|
||||
Conversion between different units in physics is done with a help of operators defined on numbers `*`, `/`.<br/>
|
||||
Conversion between different rx units in done with a help of observable sequence operators.
|
||||
Conversion between different units in physics is done with the help of operators defined on numbers `*`, `/`.<br/>
|
||||
Conversion between different Rx units in done with the help of observable sequence operators.
|
||||
|
||||
E.g.
|
||||
|
||||
```
|
||||
can't error out = catchError
|
||||
observe on main scheduler = observeOn(MainScheduler.instance)
|
||||
subscribe on main scheduler = subscribeOn(MainScheduler.instance)
|
||||
sharing side effects = share* (one of the `share` operators)
|
||||
Can't error out = catchError
|
||||
Observe on main scheduler = observeOn(MainScheduler.instance)
|
||||
Subscribe on main scheduler = subscribeOn(MainScheduler.instance)
|
||||
Sharing side effects = share* (one of the `share` operators)
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -126,29 +126,29 @@ sharing side effects = share* (one of the `share` operators)
|
|||
|
||||
### Driver unit
|
||||
|
||||
* can't error out
|
||||
* observe on main scheduler
|
||||
* sharing side effects (`shareReplayLatestWhileConnected`)
|
||||
* Can't error out
|
||||
* Observe on main scheduler
|
||||
* Sharing side effects (`shareReplayLatestWhileConnected`)
|
||||
|
||||
### ControlProperty / ControlEvent
|
||||
|
||||
* can't error out
|
||||
* subscribe on main scheduler
|
||||
* observe on main scheduler
|
||||
* sharing side effects
|
||||
* Can't error out
|
||||
* Subscribe on main scheduler
|
||||
* Observe on main scheduler
|
||||
* Sharing side effects
|
||||
|
||||
### Variable
|
||||
|
||||
* can't error out
|
||||
* sharing side effects
|
||||
* Can't error out
|
||||
* Sharing side effects
|
||||
|
||||
## Driver
|
||||
|
||||
This is the most elaborate unit. It's intention is to provide an intuitive way to write reactive code in UI layer.
|
||||
This is the most elaborate unit. Its intention is to provide an intuitive way to write reactive code in the UI layer.
|
||||
|
||||
### Why was it named Driver
|
||||
### Why it's named Driver
|
||||
|
||||
It's intended use case was to model sequences that drive your application.
|
||||
Its intended use case was to model sequences that drive your application.
|
||||
|
||||
E.g.
|
||||
* Drive UI from CoreData model
|
||||
|
|
@ -156,9 +156,9 @@ E.g.
|
|||
...
|
||||
|
||||
|
||||
Like normal operating system drivers, in case one of those sequence errors out your application will stop responding to user input.
|
||||
Like normal operating system drivers, in case a sequence errors out, your application will stop responding to user input.
|
||||
|
||||
It is also extremely important that those elements are observed on main thread because UI elements and application logic are usually not thread safe.
|
||||
It is also extremely important that those elements are observed on the main thread because UI elements and application logic are usually not thread safe.
|
||||
|
||||
Also, `Driver` unit builds an observable sequence that shares side effects.
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ E.g.
|
|||
|
||||
### Practical usage example
|
||||
|
||||
This is an typical beginner example.
|
||||
This is a typical beginner example.
|
||||
|
||||
```swift
|
||||
let results = query.rx_text
|
||||
|
|
@ -189,14 +189,14 @@ results
|
|||
```
|
||||
|
||||
The intended behavior of this code was to:
|
||||
* throttle user input
|
||||
* contact server and fetch a list of user results (once per query)
|
||||
* then bind the results to two UI elements, results table view and a label that displays number of results
|
||||
* Throttle user input
|
||||
* Contact server and fetch a list of user results (once per query)
|
||||
* Bind the results to two UI elements: results table view and a label that displays the number of results
|
||||
|
||||
So what are the problems with this code:
|
||||
* in case the `fetchAutoCompleteItems` observable sequence errors out (connection failed, or parsing error), this error would unbind everything and UI wouldn't respond any more to new queries.
|
||||
* in case `fetchAutoCompleteItems` returns results on some background thread, results would be bound to UI elements from a background thread and that could cause non deterministic crashes.
|
||||
* results are bound to two UI elements, which means that for each user query two HTTP requests would be made, one for each UI element, which is not intended behavior.
|
||||
So, what are the problems with this code?:
|
||||
* If the `fetchAutoCompleteItems` observable sequence errors out (connection failed or parsing error), this error would unbind everything and the UI wouldn't respond any more to new queries.
|
||||
* If `fetchAutoCompleteItems` returns results on some background thread, results would be bound to UI elements from a background thread which could cause non-deterministic crashes.
|
||||
* Results are bound to two UI elements, which means that for each user query, two HTTP requests would be made, one for each UI element, which is not the intended behavior.
|
||||
|
||||
A more appropriate version of the code would look like this:
|
||||
|
||||
|
|
@ -205,11 +205,11 @@ let results = query.rx_text
|
|||
.throttle(0.3, scheduler: MainScheduler.instance)
|
||||
.flatMapLatest { query in
|
||||
fetchAutoCompleteItems(query)
|
||||
.observeOn(MainScheduler.instance) // results are returned on MainScheduler
|
||||
.catchErrorJustReturn([]) // in worst case, errors are handled
|
||||
.observeOn(MainScheduler.instance) // results are returned on MainScheduler
|
||||
.catchErrorJustReturn([]) // in the worst case, errors are handled
|
||||
}
|
||||
.shareReplay(1) // HTTP requests are shared and results replayed
|
||||
// to all UI elements
|
||||
.shareReplay(1) // HTTP requests are shared and results replayed
|
||||
// to all UI elements
|
||||
|
||||
results
|
||||
.map { "\($0.count)" }
|
||||
|
|
@ -228,17 +228,17 @@ Making sure all of these requirements are properly handled in large systems can
|
|||
The following code looks almost the same:
|
||||
|
||||
```swift
|
||||
let results = query.rx_text.asDriver() // This converts normal sequence into `Driver` sequence.
|
||||
let results = query.rx_text.asDriver() // This converts a normal sequence into a `Driver` sequence.
|
||||
.throttle(0.3, scheduler: MainScheduler.instance)
|
||||
.flatMapLatest { query in
|
||||
fetchAutoCompleteItems(query)
|
||||
.asDriver(onErrorJustReturn: []) // Builder just needs info what to return in case of error.
|
||||
.asDriver(onErrorJustReturn: []) // Builder just needs info about what to return in case of error.
|
||||
}
|
||||
|
||||
results
|
||||
.map { "\($0.count)" }
|
||||
.drive(resultCount.rx_text) // If there is `drive` method available instead of `bindTo`,
|
||||
.addDisposableTo(disposeBag) // that means that compiler has proved all properties
|
||||
.drive(resultCount.rx_text) // If there is a `drive` method available instead of `bindTo`,
|
||||
.addDisposableTo(disposeBag) // that means that the compiler has proven that all properties
|
||||
// are satisfied.
|
||||
results
|
||||
.drive(resultTableView.rx_itemsWithCellIdentifier("Cell")) { (_, result, cell) in
|
||||
|
|
@ -249,37 +249,37 @@ results
|
|||
|
||||
So what is happening here?
|
||||
|
||||
This first `asDriver` method converts `ControlProperty` unit to `Driver` unit.
|
||||
This first `asDriver` method converts the `ControlProperty` unit to a `Driver` unit.
|
||||
|
||||
```swift
|
||||
query.rx_text.asDriver()
|
||||
```
|
||||
|
||||
Notice that there wasn't anything special that needed to be done. `Driver` has all of the properties of the `ControlProperty` unit plus some more. The underlying observable sequence is just wrapped as `Driver` unit, and that's it.
|
||||
Notice that there wasn't anything special that needed to be done. `Driver` has all of the properties of the `ControlProperty` unit, plus some more. The underlying observable sequence is just wrapped as a `Driver` unit, and that's it.
|
||||
|
||||
The second change is
|
||||
The second change is:
|
||||
|
||||
```swift
|
||||
.asDriver(onErrorJustReturn: [])
|
||||
.asDriver(onErrorJustReturn: [])
|
||||
```
|
||||
|
||||
Any observable sequence can be converted to `Driver` unit, it just needs to satisfy 3 properties:
|
||||
* can't error out
|
||||
* observe on main scheduler
|
||||
* sharing side effects (`shareReplayLatestWhileConnected`)
|
||||
Any observable sequence can be converted to `Driver` unit, as long as it satisfies 3 properties:
|
||||
* Can't error out
|
||||
* Observe on main scheduler
|
||||
* Sharing side effects (`shareReplayLatestWhileConnected`)
|
||||
|
||||
So how to make sure those properties are satisfied? Just use normal Rx operators. `asDriver(onErrorJustReturn: [])` is equivalent to following code.
|
||||
So how do you make sure those properties are satisfied? Just use normal Rx operators. `asDriver(onErrorJustReturn: [])` is equivalent to following code.
|
||||
|
||||
```
|
||||
let safeSequence = xs
|
||||
.observeOn(MainScheduler.instance) // observe events on main scheduler
|
||||
.observeOn(MainScheduler.instance) // observe events on main scheduler
|
||||
.catchErrorJustReturn(onErrorJustReturn) // can't error out
|
||||
.shareReplayLatestWhileConnected // side effects sharing
|
||||
return Driver(raw: safeSequence) // wrap it up
|
||||
```
|
||||
|
||||
The final piece is `drive` instead of using `bindTo`.
|
||||
The final piece is using `drive` instead of using `bindTo`.
|
||||
|
||||
`drive` is defined only on `Driver` unit. It means that if you see `drive` somewhere in code, observable sequence that can never error out and observes elements on main thread is being bound to UI element. Which is exactly what is wanted.
|
||||
`drive` is defined only on the `Driver` unit. This means that if you see `drive` somewhere in code, that observable sequence can never error out and it observes on the main thread, which is safe for binding to a UI element.
|
||||
|
||||
Theoretically, somebody could define `drive` method to work on `ObservableType` or some other interface, so creating a temporary definition with `let results: Driver<[Results]> = ...` before binding to UI elements would be necessary for complete proof, but we'll leave it up for reader to decide whether that is a realistic scenario.
|
||||
Note however that, theoretically, someone could still define a `drive` method to work on `ObservableType` or some other interface, so to be extra safe, creating a temporary definition with `let results: Driver<[Results]> = ...` before binding to UI elements would be necessary for complete proof. However, we'll leave it up to the reader to decide whether this is a realistic scenario or not.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Warnings
|
|||
|
||||
The following is valid for the `subscribe*`, `bind*` and `drive*` family of functions that return `Disposable`.
|
||||
|
||||
Warning is probably presented in a context similar to this one:
|
||||
You will receive a warning for doing something such as this:
|
||||
|
||||
```Swift
|
||||
let xs: Observable<E> ....
|
||||
|
|
@ -18,10 +18,10 @@ xs
|
|||
...
|
||||
}, onError: {
|
||||
...
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
The `subscribe` function returns a subscription `Disposable` that can be used to cancel computation and free resources.
|
||||
The `subscribe` function returns a subscription `Disposable` that can be used to cancel computation and free resources. However, not using it (and thus not disposing it) will result in an error.
|
||||
|
||||
The preferred way of terminating these fluent calls is by using a `DisposeBag`, either through chaining a call to `.addDisposableTo(disposeBag)` or by adding the disposable directly to the bag.
|
||||
|
||||
|
|
@ -41,11 +41,11 @@ xs
|
|||
.addDisposableTo(disposeBag) // <--- note `addDisposableTo`
|
||||
```
|
||||
|
||||
When `disposeBag` gets deallocated, the disposables contained in it will be automatically disposed.
|
||||
When `disposeBag` gets deallocated, the disposables contained within it will be automatically disposed as well.
|
||||
|
||||
In the case where `xs` terminates in a predictable way with either a `Completed` or `Error` message, not handling the subscription `Disposable` won't leak any resources. However, even in this case, using a dispose bag is still the preferred way to handle subscription disposables. It ensures that element computation is always terminated at a predictable moment, and makes your code robust and future proof because resources will be properly disposed even if the implementation of `xs` changes.
|
||||
|
||||
Another way to make sure subscriptions and resources are tied with the lifetime of some object is by using the `takeUntil` operator.
|
||||
Another way to make sure subscriptions and resources are tied to the lifetime of some object is by using the `takeUntil` operator.
|
||||
|
||||
```Swift
|
||||
let xs: Observable<E> ....
|
||||
|
|
@ -63,7 +63,7 @@ _ = xs
|
|||
})
|
||||
```
|
||||
|
||||
If ignoring the subscription `Disposable` is desired behavior, this is how to silence the compiler warning.
|
||||
If ignoring the subscription `Disposable` is the desired behavior, this is how to silence the compiler warning.
|
||||
|
||||
```Swift
|
||||
let xs: Observable<E> ....
|
||||
|
|
@ -81,7 +81,7 @@ _ = xs // <-- note the underscore
|
|||
|
||||
### <a name="unused-observable"></a>Unused observable sequence (unused-observable)
|
||||
|
||||
Warning is probably presented in a context similar to this one:
|
||||
You will receive a warning for doing something such as this:
|
||||
|
||||
```Swift
|
||||
let xs: Observable<E> ....
|
||||
|
|
@ -105,7 +105,7 @@ let ys = xs // <--- names definition as `ys`
|
|||
.map { ... }
|
||||
```
|
||||
|
||||
... or start computation based on that definition
|
||||
... or start computation based on that definition
|
||||
|
||||
```Swift
|
||||
let xs: Observable<E> ....
|
||||
|
|
@ -114,7 +114,7 @@ let disposeBag = DisposeBag()
|
|||
xs
|
||||
.filter { ... }
|
||||
.map { ... }
|
||||
.subscribeNext { nextElement in // <-- note the `subscribe*` method
|
||||
.subscribeNext { nextElement in // <-- note the `subscribe*` method
|
||||
// use the element
|
||||
print(nextElement)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,40 +6,40 @@
|
|||
|
||||
```swift
|
||||
Observable.combineLatest(firstName.rx_text, lastName.rx_text) { $0 + " " + $1 }
|
||||
.map { "Greeting \($0)" }
|
||||
.bindTo(greetingLabel.rx_text)
|
||||
.map { "Greetings, \($0)" }
|
||||
.bindTo(greetingLabel.rx_text)
|
||||
```
|
||||
|
||||
This also works with `UITableView`s and `UICollectionView`s.
|
||||
|
||||
```swift
|
||||
viewModel
|
||||
.rows
|
||||
.bindTo(resultsTableView.rx_itemsWithCellIdentifier("WikipediaSearchCell", cellType: WikipediaSearchCell.self)) { (_, viewModel, cell) in
|
||||
cell.title = viewModel.title
|
||||
cell.url = viewModel.url
|
||||
}
|
||||
.addDisposableTo(disposeBag)
|
||||
.rows
|
||||
.bindTo(resultsTableView.rx_itemsWithCellIdentifier("WikipediaSearchCell", cellType: WikipediaSearchCell.self)) { (_, viewModel, cell) in
|
||||
cell.title = viewModel.title
|
||||
cell.url = viewModel.url
|
||||
}
|
||||
.addDisposableTo(disposeBag)
|
||||
```
|
||||
|
||||
**Official suggestion is to always use `.addDisposableTo(disposeBag)` even though that's not necessary for simple bindings.**
|
||||
|
||||
### Retries
|
||||
|
||||
It would be great if APIs wouldn't fail, but unfortunately they do. Let's say there is an API method
|
||||
It would be great if APIs wouldn't fail, but unfortunately they do. Let's say there is an API method:
|
||||
|
||||
```swift
|
||||
func doSomethingIncredible(forWho: String) throws -> IncredibleThing
|
||||
```
|
||||
|
||||
If you are using this function as it is, it's really hard to do retries in case it fails. Not to mention complexities modeling [exponential backoffs](https://en.wikipedia.org/wiki/Exponential_backoff). Sure it's possible, but code would probably contain a lot of transient states that you really don't care about, and it won't be reusable.
|
||||
If you are using this function as it is, it's really hard to do retries in case it fails. Not to mention complexities modeling [exponential backoffs](https://en.wikipedia.org/wiki/Exponential_backoff). Sure it's possible, but the code would probably contain a lot of transient states that you really don't care about, and it wouldn't be reusable.
|
||||
|
||||
You would ideally want to capture the essence of retrying, and to be able to apply it to any operation.
|
||||
Ideally, you would want to capture the essence of retrying, and to be able to apply it to any operation.
|
||||
|
||||
This is how you can do simple retries with Rx
|
||||
|
||||
```swift
|
||||
doSomethingIncredible("me")
|
||||
doSomethingIncredible("me")
|
||||
.retry(3)
|
||||
```
|
||||
|
||||
|
|
@ -47,11 +47,11 @@ You can also easily create custom retry operators.
|
|||
|
||||
### Delegates
|
||||
|
||||
Instead of doing the tedious and non-expressive
|
||||
Instead of doing the tedious and non-expressive:
|
||||
|
||||
```swift
|
||||
public func scrollViewDidScroll(scrollView: UIScrollView) { // what scroll view is this bound to?
|
||||
self.leftPositionConstraint.constant = scrollView.contentOffset.x
|
||||
public func scrollViewDidScroll(scrollView: UIScrollView) { [weak self] // what scroll view is this bound to?
|
||||
self?.leftPositionConstraint.constant = scrollView.contentOffset.x
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -59,14 +59,14 @@ public func scrollViewDidScroll(scrollView: UIScrollView) { // what scroll view
|
|||
|
||||
```swift
|
||||
self.resultsTableView
|
||||
.rx_contentOffset
|
||||
.map { $0.x }
|
||||
.bindTo(self.leftPositionConstraint.rx_constant)
|
||||
.rx_contentOffset
|
||||
.map { $0.x }
|
||||
.bindTo(self.leftPositionConstraint.rx_constant)
|
||||
```
|
||||
|
||||
### KVO
|
||||
|
||||
Instead of
|
||||
Instead of:
|
||||
|
||||
```
|
||||
`TickTock` was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object.
|
||||
|
|
@ -104,37 +104,38 @@ someSuspiciousViewController
|
|||
|
||||
### Notifications
|
||||
|
||||
Instead of using
|
||||
Instead of using:
|
||||
|
||||
```swift
|
||||
@available(iOS 4.0, *)
|
||||
public func addObserverForName(name: String?, object obj: AnyObject?, queue: NSOperationQueue?, usingBlock block: (NSNotification) -> Void) -> NSObjectProtocol
|
||||
@available(iOS 4.0, *)
|
||||
public func addObserverForName(name: String?, object obj: AnyObject?, queue: NSOperationQueue?, usingBlock block: (NSNotification) -> Void) -> NSObjectProtocol
|
||||
```
|
||||
|
||||
... just write
|
||||
|
||||
```swift
|
||||
NSNotificationCenter.defaultCenter()
|
||||
.rx_notification(UITextViewTextDidBeginEditingNotification, object: myTextView)
|
||||
.map { /*do something with data*/ }
|
||||
....
|
||||
NSNotificationCenter.defaultCenter()
|
||||
.rx_notification(UITextViewTextDidBeginEditingNotification, object: myTextView)
|
||||
.map { /*do something with data*/ }
|
||||
....
|
||||
```
|
||||
|
||||
### Transient state
|
||||
|
||||
There is also a lot of problems with transient state when writing async programs. Typical example is autocomplete search box.
|
||||
There are also a lot of problems with transient state when writing async programs. A typical example is an autocomplete search box.
|
||||
|
||||
If you were to write the autocomplete code without Rx, first problem that probably needs to be solved is when `c` in `abc` is typed, and there is a pending request for `ab`, pending request gets cancelled. Ok, that shouldn't be too hard to solve, you just create additional variable to hold reference to pending request.
|
||||
If you were to write the autocomplete code without Rx, the first problem that probably needs to be solved is when `c` in `abc` is typed, and there is a pending request for `ab`, the pending request gets cancelled. OK, that shouldn't be too hard to solve, you just create an additional variable to hold reference to the pending request.
|
||||
|
||||
The next problem is if the request fails, you need to do that messy retry logic. But ok, a couple of more fields that capture number of retries that need to be cleaned up.
|
||||
The next problem is if the request fails, you need to do that messy retry logic. But OK, a couple more fields that capture the number of retries that need to be cleaned up.
|
||||
|
||||
It would be great if program would wait for some time before firing that request to server, after all, we don't want to spam our servers in case somebody is in the process of fast typing something very long. Additional timer field maybe?
|
||||
It would be great if the program would wait for some time before firing a request to the server. After all, we don't want to spam our servers in case somebody is in the process of typing something very long. An additional timer field maybe?
|
||||
|
||||
There is also a question of what needs to be shown on screen while that search is executing, and also what needs to be shown in case we fail even with all of the retries.
|
||||
|
||||
Writing all of this and properly testing it would be tedious. This is that same logic written with Rx.
|
||||
|
||||
```swift
|
||||
searchTextField.rx_text
|
||||
searchTextField.rx_text
|
||||
.throttle(0.3, scheduler: MainScheduler.instance)
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { query in
|
||||
|
|
@ -148,22 +149,22 @@ Writing all of this and properly testing it would be tedious. This is that same
|
|||
}
|
||||
```
|
||||
|
||||
There is no additional flags or fields required. Rx takes care of all that transient mess.
|
||||
There are no additional flags or fields required. Rx takes care of all that transient mess.
|
||||
|
||||
### Compositional disposal
|
||||
|
||||
Lets assume that there is a scenario where you want to display blurred images in a table view. The images should be first fetched from URL, then decoded and then blurred.
|
||||
Let's assume that there is a scenario where you want to display blurred images in a table view. First, the images should be fetched from a URL, then decoded and then blurred.
|
||||
|
||||
It would also be nice if that entire process could be cancelled if a cell exits the visible table view area because bandwidth and processor time for blurring are expensive.
|
||||
It would also be nice if that entire process could be cancelled if a cell exits the visible table view area since bandwidth and processor time for blurring are expensive.
|
||||
|
||||
It would also be nice if we didn't just immediately start to fetch image once the cell enters visible area because if user swipes really fast there could be a lot of requests fired and cancelled.
|
||||
It would also be nice if we didn't just immediately start to fetch an image once the cell enters the visible area since, if user swipes really fast, there could be a lot of requests fired and cancelled.
|
||||
|
||||
It would be also nice if we could limit the number of concurrent image operations because blurring images is an expensive operation.
|
||||
It would be also nice if we could limit the number of concurrent image operations because, again, blurring images is an expensive operation.
|
||||
|
||||
This is how we can do it using Rx.
|
||||
This is how we can do it using Rx:
|
||||
|
||||
```swift
|
||||
// this is conceptual solution
|
||||
// this is a conceptual solution
|
||||
let imageSubscription = imageURLs
|
||||
.throttle(0.2, scheduler: MainScheduler.instance)
|
||||
.flatMapLatest { imageURL in
|
||||
|
|
@ -180,39 +181,39 @@ let imageSubscription = imageURLs
|
|||
.addDisposableTo(reuseDisposeBag)
|
||||
```
|
||||
|
||||
This code will do all that, and when `imageSubscription` is disposed it will cancel all dependent async operations and make sure no rogue image is bound to UI.
|
||||
This code will do all that and, when `imageSubscription` is disposed, it will cancel all dependent async operations and make sure no rogue image is bound to the UI.
|
||||
|
||||
### Aggregating network requests
|
||||
|
||||
What if you need to fire two requests, and aggregate results when they have both finished?
|
||||
What if you need to fire two requests and aggregate results when they have both finished?
|
||||
|
||||
Well, there is of course `zip` operator
|
||||
Well, there is of course the `zip` operator
|
||||
|
||||
```swift
|
||||
let userRequest: Observable<User> = API.getUser("me")
|
||||
let friendsRequest: Observable<Friends> = API.getFriends("me")
|
||||
let userRequest: Observable<User> = API.getUser("me")
|
||||
let friendsRequest: Observable<Friends> = API.getFriends("me")
|
||||
|
||||
Observable.zip(userRequest, friendsRequest) { user, friends in
|
||||
return (user, friends)
|
||||
}
|
||||
.subscribeNext { user, friends in
|
||||
// bind them to user interface
|
||||
}
|
||||
Observable.zip(userRequest, friendsRequest) { user, friends in
|
||||
return (user, friends)
|
||||
}
|
||||
.subscribeNext { user, friends in
|
||||
// bind them to the user interface
|
||||
}
|
||||
```
|
||||
|
||||
So what if those APIs return results on a background thread, and binding has to happen on main UI thread? There is `observeOn`.
|
||||
So what if those APIs return results on a background thread, and binding has to happen on the main UI thread? There is `observeOn`.
|
||||
|
||||
```swift
|
||||
let userRequest: Observable<User> = API.getUser("me")
|
||||
let friendsRequest: Observable<[Friend]> = API.getFriends("me")
|
||||
let userRequest: Observable<User> = API.getUser("me")
|
||||
let friendsRequest: Observable<[Friend]> = API.getFriends("me")
|
||||
|
||||
Observable.zip(userRequest, friendsRequest) { user, friends in
|
||||
return (user, friends)
|
||||
}
|
||||
.observeOn(MainScheduler.instance)
|
||||
.subscribeNext { user, friends in
|
||||
// bind them to user interface
|
||||
}
|
||||
Observable.zip(userRequest, friendsRequest) { user, friends in
|
||||
return (user, friends)
|
||||
}
|
||||
.observeOn(MainScheduler.instance)
|
||||
.subscribeNext { user, friends in
|
||||
// bind them to the user interface
|
||||
}
|
||||
```
|
||||
|
||||
There are many more practical use cases where Rx really shines.
|
||||
|
|
@ -221,19 +222,19 @@ There are many more practical use cases where Rx really shines.
|
|||
|
||||
Languages that allow mutation make it easy to access global state and mutate it. Uncontrolled mutations of shared global state can easily cause [combinatorial explosion] (https://en.wikipedia.org/wiki/Combinatorial_explosion#Computing).
|
||||
|
||||
But on the other hand, when used in smart way, imperative languages can enable writing more efficient code closer to hardware.
|
||||
But on the other hand, when used in a smart way, imperative languages can enable writing more efficient code closer to hardware.
|
||||
|
||||
The usual way to battle combinatorial explosion is to keep state as simple as possible, and use [unidirectional data flows](https://developer.apple.com/videos/play/wwdc2014-229) to model derived data.
|
||||
|
||||
This is what Rx really shines at.
|
||||
This is where Rx really shines.
|
||||
|
||||
Rx is that sweet spot between functional and imperative world. It enables you to use immutable definitions and pure functions to process snapshots of mutable state in a reliable composable way.
|
||||
Rx is that sweet spot between functional and imperative worlds. It enables you to use immutable definitions and pure functions to process snapshots of mutable state in a reliable composable way.
|
||||
|
||||
So what are some of the practical examples?
|
||||
So what are some practical examples?
|
||||
|
||||
### Easy integration
|
||||
|
||||
And what if you need to create your own observable? It's pretty easy. This code is taken from RxCocoa and that's all you need to wrap HTTP requests with `NSURLSession`
|
||||
What if you need to create your own observable? It's pretty easy. This code is taken from RxCocoa and that's all you need to wrap HTTP requests with `NSURLSession`
|
||||
|
||||
```swift
|
||||
extension NSURLSession {
|
||||
|
|
@ -266,30 +267,30 @@ extension NSURLSession {
|
|||
|
||||
### Benefits
|
||||
|
||||
In short using Rx will make your code:
|
||||
In short, using Rx will make your code:
|
||||
|
||||
* composable <- because Rx is composition's nick name
|
||||
* reusable <- because it's composable
|
||||
* declarative <- because definitions are immutable and only data changes
|
||||
* understandable and concise <- raising level of abstraction and removing transient states
|
||||
* stable <- because Rx code is thoroughly unit tested
|
||||
* less stateful <- because you are modeling application as unidirectional data flows
|
||||
* without leaks <- because resource management is easy
|
||||
* Composable <- Because Rx is composition's nickname
|
||||
* Reusable <- Because it's composable
|
||||
* Declarative <- Because definitions are immutable and only data changes
|
||||
* Understandable and concise <- Raising the level of abstraction and removing transient states
|
||||
* Stable <- Because Rx code is thoroughly unit tested
|
||||
* Less stateful <- Because you are modeling applications as unidirectional data flows
|
||||
* Without leaks <- Because resource management is easy
|
||||
|
||||
### It's not all or nothing
|
||||
|
||||
It is usually a good idea to model as much of your application as possible using Rx.
|
||||
|
||||
But what if you don't know all of the operators and does there even exist some operator that models your particular case?
|
||||
But what if you don't know all of the operators and whether or not there even exists some operator that models your particular case?
|
||||
|
||||
Well, all of the Rx operators are based on math and should be intuitive.
|
||||
|
||||
The good news is that about 10-15 operators cover most typical use cases. And that list already includes some of the familiar ones like `map`, `filter`, `zip`, `observeOn` ...
|
||||
The good news is that about 10-15 operators cover most typical use cases. And that list already includes some of the familiar ones like `map`, `filter`, `zip`, `observeOn`, ...
|
||||
|
||||
There is a huge list of [all Rx operators](http://reactivex.io/documentation/operators.html) and list of all of the [currently supported RxSwift operators](API.md).
|
||||
|
||||
For each operator there is [marble diagram](http://reactivex.io/documentation/operators/retry.html) that helps to explain how does it work.
|
||||
For each operator, there is a [marble diagram](http://reactivex.io/documentation/operators/retry.html) that helps to explain how it works.
|
||||
|
||||
But what if you need some operator that isn't on that list? Well, you can make your own operator.
|
||||
|
||||
What if creating that kind of operator is really hard for some reason, or you have some legacy stateful piece of code that you need to work with? Well, you've got yourself in a mess, but you can [jump out of Rx monad](GettingStarted.md#life-happens) easily, process the data, and return back into it.
|
||||
What if creating that kind of operator is really hard for some reason, or you have some legacy stateful piece of code that you need to work with? Well, you've got yourself in a mess, but you can [jump out of Rx monads](GettingStarted.md#life-happens) easily, process the data, and return back into it.
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -39,7 +39,7 @@ KVO observing, async operations and streams are all unified under [abstraction o
|
|||
|
||||
###### ... hack around
|
||||
|
||||
* with example app. [Running Example App](Documentation/ExampleApp.md)
|
||||
* with the example app. [Running Example App](Documentation/ExampleApp.md)
|
||||
* with operators in playgrounds. [Playgrounds](Documentation/Playgrounds.md)
|
||||
|
||||
###### ... interact
|
||||
|
|
@ -110,15 +110,15 @@ searchResults
|
|||
|
||||
Rx doesn't contain any external dependencies.
|
||||
|
||||
These are currently supported options:
|
||||
These are currently the supported options:
|
||||
|
||||
### Manual
|
||||
|
||||
Open Rx.xcworkspace, choose `RxExample` and hit run. This method will build everything and run sample app
|
||||
Open Rx.xcworkspace, choose `RxExample` and hit run. This method will build everything and run the sample app
|
||||
|
||||
### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html)
|
||||
|
||||
**:warning: IMPORTANT! For tvOS support CocoaPods `0.39` is required. :warning:**
|
||||
**:warning: IMPORTANT! For tvOS support, CocoaPods `0.39` is required. :warning:**
|
||||
|
||||
```
|
||||
# Podfile
|
||||
|
|
@ -129,14 +129,14 @@ target 'YOUR_TARGET_NAME' do
|
|||
pod 'RxCocoa', '~> 2.0'
|
||||
end
|
||||
|
||||
# RxTests and RxBlocking have most sense in the context of unit/integration tests
|
||||
# RxTests and RxBlocking make the most sense in the context of unit/integration tests
|
||||
target 'YOUR_TESTING_TARGET' do
|
||||
pod 'RxBlocking', '~> 2.0'
|
||||
pod 'RxTests', '~> 2.0'
|
||||
end
|
||||
```
|
||||
|
||||
replace `YOUR_TARGET_NAME`, then type in the `Podfile` directory:
|
||||
Replace `YOUR_TARGET_NAME` and then, in the `Podfile` directory, type:
|
||||
|
||||
```
|
||||
$ pod install
|
||||
|
|
|
|||
Loading…
Reference in New Issue