Updates documentation.
This commit is contained in:
parent
d537473e49
commit
dc9a0f5505
|
|
@ -0,0 +1,19 @@
|
|||
## Comparison with ReactiveCocoa
|
||||
|
||||
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.
|
||||
|
||||
We've also decided to only rely on Swift/llvm compiler and not introduce any external dependencies.
|
||||
|
||||
Probably the main difference between these projects is the difference of 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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
## RxExamples
|
||||
|
||||
To run the example app:
|
||||
|
||||
* Open `Rx.xcworkspace`
|
||||
* Choose one of example schemes (RxExample-iOS, RxExample-OSX) and hit `Run`.
|
||||
|
||||
You can also run the example app using CocoaPods.
|
||||
|
||||
```
|
||||
pod try RxSwift
|
||||
```
|
||||
|
|
@ -5,7 +5,7 @@ Examples
|
|||
1. [Simple UI bindings](#simple-ui-bindings)
|
||||
1. [Autocomplete](#autocomplete)
|
||||
1. [more examples](../RxExample)
|
||||
1. [Playgrounds](../README.md#playgrounds)
|
||||
1. [Playgrounds](Playgrounds.md)
|
||||
|
||||
## Calculated variable
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
## Build / Install / Run
|
||||
|
||||
Rx doesn't contain any external dependencies.
|
||||
|
||||
These are currently supported options:
|
||||
|
||||
### Manual
|
||||
|
||||
Open Rx.xcworkspace, choose `RxExample` and hit run. This method will build everything and run sample app
|
||||
|
||||
### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html)
|
||||
|
||||
**:warning: IMPORTANT! For tvOS support CocoaPods `0.39` is required. :warning:**
|
||||
|
||||
```
|
||||
# Podfile
|
||||
use_frameworks!
|
||||
|
||||
target 'YOUR_TARGET_NAME' do
|
||||
pod 'RxSwift', '~> 2.0'
|
||||
pod 'RxCocoa', '~> 2.0'
|
||||
pod 'RxBlocking', '~> 2.0'
|
||||
pod 'RxTests', '~> 2.0'
|
||||
end
|
||||
```
|
||||
|
||||
replace `YOUR_TARGET_NAME`, then type in the `Podfile` directory:
|
||||
|
||||
```
|
||||
$ pod install
|
||||
```
|
||||
|
||||
### [Carthage](https://github.com/Carthage/Carthage)
|
||||
|
||||
**Xcode 7.1 required**
|
||||
|
||||
Add this to `Cartfile`
|
||||
|
||||
```
|
||||
github "ReactiveX/RxSwift" ~> 2.0
|
||||
```
|
||||
|
||||
```
|
||||
$ carthage update
|
||||
```
|
||||
|
||||
### Manually using git submodules
|
||||
|
||||
* Add RxSwift as a submodule
|
||||
|
||||
```
|
||||
$ git submodule add git@github.com:ReactiveX/RxSwift.git
|
||||
```
|
||||
|
||||
* Drag `Rx.xcodeproj` into Project Navigator
|
||||
* Go to `Project > Targets > Build Phases > Link Binary With Libraries`, click `+` and select `RxSwift-[Platform]` and `RxCocoa-[Platform]` targets
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
**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.**
|
||||
|
||||
```
|
||||
*Short description*:
|
||||
|
||||
description here
|
||||
|
||||
*Code that reproduces the issue*:
|
||||
|
||||
code goes here
|
||||
|
||||
*Xcode version*:
|
||||
|
||||
Xcode version goes here
|
||||
|
||||
*Expected outcome*:
|
||||
|
||||
what do 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
|
||||
|
||||
*Integration method*:
|
||||
(so we don't lose time investigating wrong integration)
|
||||
* CocoaPods
|
||||
* Carthage
|
||||
* Git submodules
|
||||
|
||||
*I have multiple versions of Xcode installed*:
|
||||
(so we can understand can this cause your issue)
|
||||
* yes (which ones)
|
||||
* no
|
||||
|
||||
*How long have I been using this project*:
|
||||
(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
|
||||
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
**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.**
|
||||
|
||||
```
|
||||
*Short description of missing functionality*:
|
||||
|
||||
E.g. I want this library to implement xxx operator.
|
||||
|
||||
*Short code example of how you would like to use the API*:
|
||||
|
||||
code goes here
|
||||
|
||||
*The reason why I need this functionality*:
|
||||
|
||||
E.g. I was trying to ....
|
||||
|
||||
*Code I have right now*:
|
||||
|
||||
code snippet that demonstrates how you've attempted to solve the problem
|
||||
|
||||
```
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
## Playgrounds
|
||||
|
||||
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`
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
## Why
|
||||
|
||||
**Rx enables building apps in a declarative way.**
|
||||
|
||||
### Bindings
|
||||
|
||||
```swift
|
||||
Observable.combineLatest(firstName.rx_text, lastName.rx_text) { $0 + " " + $1 }
|
||||
.map { "Greeting \($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)
|
||||
```
|
||||
|
||||
** 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
|
||||
|
||||
```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.
|
||||
|
||||
You would ideally 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")
|
||||
.retry(3)
|
||||
```
|
||||
|
||||
You can also easily create custom retry operators.
|
||||
|
||||
### Delegates
|
||||
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
... write
|
||||
|
||||
```swift
|
||||
self.resultsTableView
|
||||
.rx_contentOffset
|
||||
.map { $0.x }
|
||||
.bindTo(self.leftPositionConstraint.rx_constant)
|
||||
```
|
||||
|
||||
### KVO
|
||||
|
||||
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.
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```objc
|
||||
-(void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context
|
||||
```
|
||||
|
||||
Use [`rx_observe` and `rx_observeWeakly`](GettingStarted.md#kvo)
|
||||
|
||||
This is how they can be used:
|
||||
|
||||
```swift
|
||||
view.rx_observe(CGRect.self, "frame")
|
||||
.subscribeNext { frame in
|
||||
print("Got new frame \(frame)")
|
||||
}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```swift
|
||||
someSuspiciousViewController
|
||||
.rx_observeWeakly(Bool.self, "behavingOk")
|
||||
.subscribeNext { behavingOk in
|
||||
print("Cats can purr? \(behavingOk)")
|
||||
}
|
||||
```
|
||||
|
||||
### Notifications
|
||||
|
||||
Instead of using
|
||||
```swift
|
||||
@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*/ }
|
||||
....
|
||||
```
|
||||
|
||||
### Transient state
|
||||
|
||||
There is also a lot of problems with transient state when writing async programs. Typical example is 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.
|
||||
|
||||
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.
|
||||
|
||||
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?
|
||||
|
||||
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
|
||||
.throttle(0.3, scheduler: MainScheduler.instance)
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { query in
|
||||
API.getSearchResults(query)
|
||||
.retry(3)
|
||||
.startWith([]) // clears results on new search term
|
||||
.catchErrorJustReturn([])
|
||||
}
|
||||
.subscribeNext { results in
|
||||
// bind to ui
|
||||
}
|
||||
```
|
||||
|
||||
There is 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.
|
||||
|
||||
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 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 be also nice if we could limit the number of concurrent image operations because blurring images is an expensive operation.
|
||||
|
||||
This is how we can do it using Rx.
|
||||
|
||||
```swift
|
||||
// this is conceptual solution
|
||||
let imageSubscription = imageURLs
|
||||
.throttle(0.2, scheduler: MainScheduler.instance)
|
||||
.flatMapLatest { imageURL in
|
||||
API.fetchImage(imageURL)
|
||||
}
|
||||
.observeOn(operationScheduler)
|
||||
.map { imageData in
|
||||
return decodeAndBlurImage(imageData)
|
||||
}
|
||||
.observeOn(MainScheduler.instance)
|
||||
.subscribeNext { blurredImage in
|
||||
imageView.image = blurredImage
|
||||
}
|
||||
.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.
|
||||
|
||||
### Aggregating network requests
|
||||
|
||||
What if you need to fire two requests, and aggregate results when they have both finished?
|
||||
|
||||
Well, there is of course `zip` operator
|
||||
|
||||
```swift
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
So what if those APIs return results on a background thread, and binding has to happen on main UI thread? There is `observeOn`.
|
||||
|
||||
```swift
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
There are many more practical use cases where Rx really shines.
|
||||
|
||||
### State
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
So what are some of the 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`
|
||||
|
||||
```swift
|
||||
extension NSURLSession {
|
||||
public func rx_response(request: NSURLRequest) -> Observable<(NSData, NSURLResponse)> {
|
||||
return Observable.create { observer in
|
||||
let task = self.dataTaskWithRequest(request) { (data, response, error) in
|
||||
guard let response = response, data = data else {
|
||||
observer.on(.Error(error ?? RxCocoaURLError.Unknown))
|
||||
return
|
||||
}
|
||||
|
||||
guard let httpResponse = response as? NSHTTPURLResponse else {
|
||||
observer.on(.Error(RxCocoaURLError.NonHTTPResponse(response: response)))
|
||||
return
|
||||
}
|
||||
|
||||
observer.on(.Next(data, httpResponse))
|
||||
observer.on(.Completed)
|
||||
}
|
||||
|
||||
task.resume()
|
||||
|
||||
return AnonymousDisposable {
|
||||
task.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Benefits
|
||||
|
||||
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
|
||||
|
||||
### 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?
|
||||
|
||||
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` ...
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
519
README.md
519
README.md
|
|
@ -2,58 +2,10 @@
|
|||
======================================
|
||||
|
||||
[](https://travis-ci.org/ReactiveX/RxSwift)  
|
||||
[](http://slack.rxswift.org)
|
||||
[](http://slack.rxswift.org) [slack.rxswift.org](http://slack.rxswift.org)
|
||||
|
||||
Xcode 7 Swift 2.1 required
|
||||
|
||||
### Change Log (from 1.9 version)
|
||||
|
||||
* Removes deprecated APIs
|
||||
* Adds `ObservableType`
|
||||
* Moved from using `>-` operator to protocol extensions `.`
|
||||
* Adds support for Swift 2.0 error handling `try`/`do`/`catch`
|
||||
|
||||
You can now just write
|
||||
|
||||
```swift
|
||||
API.fetchData(URL)
|
||||
.map { rawData in
|
||||
if invalidData(rawData) {
|
||||
throw myParsingError
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
return parsedData
|
||||
}
|
||||
```
|
||||
|
||||
* RxCocoa introduces `bindTo` extensions
|
||||
|
||||
```swift
|
||||
Observable.combineLatest(firstName.rx_text, lastName.rx_text) { $0 + " " + $1 }
|
||||
.map { "Greeting \($0)" }
|
||||
.bindTo(greetingLabel.rx_text)
|
||||
```
|
||||
|
||||
... works for `UITableView`/`UICollectionView` too
|
||||
|
||||
```swift
|
||||
viewModel.rows
|
||||
.bindTo(resultsTableView.rx_itemsWithCellIdentifier("WikipediaSearchCell", cellType: WikipediaSearchCell.self)) { (_, viewModel, cell) in
|
||||
cell.viewModel = viewModel
|
||||
}
|
||||
.addDisposableTo(disposeBag)
|
||||
```
|
||||
|
||||
* Adds new operators (array version of `zip`, array version of `combineLatest`, ...)
|
||||
* Renames `catch` to `catchError`
|
||||
* Change from `disposeBag.addDisposable` to `disposable.addDisposableTo`
|
||||
* Deprecates `aggregate` in favor of `reduce`
|
||||
* Deprecates `variable` in favor of `shareReplay(1)` (to be consistent with RxJS version)
|
||||
|
||||
Check out [Migration guide to RxSwift 2.0](Documentation/Migration.md)
|
||||
|
||||
## About Rx
|
||||
|
||||
Rx is a [generic abstraction of computation](https://youtu.be/looJcaeboBY) expressed through `Observable<Element>` interface.
|
||||
|
|
@ -68,472 +20,39 @@ Like the original Rx, its intention is to enable easy composition of asynchronou
|
|||
|
||||
KVO observing, async operations and streams are all unified under [abstraction of sequence](Documentation/GettingStarted.md#observables-aka-sequences). This is the reason why Rx is so simple, elegant and powerful.
|
||||
|
||||
## I came here because I want to ...
|
||||
|
||||
```
|
||||
RxSwift
|
||||
|
|
||||
├-LICENSE.md
|
||||
├-README.md
|
||||
├-RxSwift - platform agnostic core
|
||||
├-RxCocoa - extensions for UI, NSURLSession, KVO ...
|
||||
├-RxBlocking - set of blocking operators for unit testing
|
||||
├-RxExample - example apps: taste of Rx
|
||||
└-Rx.xcworkspace - workspace that contains all of the projects hooked up
|
||||
```
|
||||
* Understand why people are using Rx. [Why to Use Rx](Documentation/Why.md)
|
||||
* Understand how RxSwift works. [Getting Started](Documentation/GettingStarted.md)
|
||||
* Understand what is that `Driver`, `ControlProperty`, `Variable` ... and why do they exist. [Units](Documentation/Units.md)
|
||||
* Understand the math behind Rx. [Math Behind Rx](Documentation/MathBehindRx.md)
|
||||
* Understand what are those hot/cold observable sequences. [Hot vs Cold Observable Sequences](Documentation/HotAndColdObservables.md)
|
||||
|
||||
## Docs
|
||||
* Integrate RxSwift/RxCocoa with my app. [Installation Guide](Documentation/Installation.md)
|
||||
|
||||
1. [Getting started](Documentation/GettingStarted.md)
|
||||
1. [Math behind](Documentation/MathBehindRx.md)
|
||||
1. [Units](Documentation/Units.md)
|
||||
1. [Simple Examples](Documentation/Examples.md)
|
||||
1. [API - RxSwift operators / RxCocoa extensions](Documentation/API.md)
|
||||
1. [Hot and cold observables](Documentation/HotAndColdObservables.md)
|
||||
|
||||
## Content
|
||||
* Hack with example app. [Running Example App](Documentation/ExampleApp.md)
|
||||
* Hack with operators in playgrounds. [Playgrounds](Documentation/Playgrounds.md)
|
||||
|
||||
1. [Why](#why)
|
||||
1. [State](#state)
|
||||
1. [Bindings](#bindings)
|
||||
1. [Retries](#retries)
|
||||
1. [Transient state](#transient-state)
|
||||
1. [Aggregating network requests](#aggregating-network-requests)
|
||||
1. [Easy integration](#easy-integration)
|
||||
1. [Compositional disposal](#compositional-disposal)
|
||||
1. [Delegates](#delegates)
|
||||
1. [Notifications](#notifications)
|
||||
1. [KVO](#kvo)
|
||||
1. [Benefits](#benefits)
|
||||
1. [It's not all or nothing](#its-not-all-or-nothing)
|
||||
1. [Build / Install / Run](#build--install--run)
|
||||
1. [Comparison with ReactiveCocoa](#comparison-with-reactivecocoa)
|
||||
1. [Playgrounds](#playgrounds)
|
||||
1. [RxExamples](#rxexamples)
|
||||
1. [References](#references)
|
||||
|
||||
## Why
|
||||
* All of this is great, but I want to meet other people using this library and exchange experience, brainstorm, ask about real world problems and solutions. [Join Slack Channel](http://slack.rxswift.org/) [](http://slack.rxswift.org)
|
||||
* Report a problem using the library. [Open an Issue With Bug Template](Documentation/IssueTemplate.md)
|
||||
* Request a new feature. [Open an Issue With Feature Request Template](Documentation/NewFeatureRequestTemplate.md)
|
||||
|
||||
Producing stable code fast is usually unexpectedly hard using just your vanilla language of choice.
|
||||
|
||||
There are many unexpected pitfalls that can ruin all of your hard work and halt development of new features.
|
||||
|
||||
### State
|
||||
* Compare with other libraries. [Comparison](Documentation/ComparisonWithOtherLibraries.md)
|
||||
|
||||
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.
|
||||
* Glance at the public API. [API Summary](Documentation/API.md)
|
||||
|
||||
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.
|
||||
* Find RxSwift compatible libraries from [RxSwiftCommunity](https://github.com/RxSwiftCommunity)
|
||||
* [Pods using RxSwift](https://cocoapods.org/?q=uses%3Arxswift)
|
||||
|
||||
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.
|
||||
|
||||
So what are some of the practical examples?
|
||||
|
||||
### Bindings
|
||||
|
||||
When writing embedded UI applications you would ideally want your program interface to be just a [pure function](https://en.wikipedia.org/wiki/Pure_function) of the [truth of the system](https://developer.apple.com/videos/play/wwdc2014-229). In that way user interface could be optimally redrawn only when truth changes, and there wouldn't be any inconsistencies.
|
||||
|
||||
These are so called bindings and Rx can help you model your system that way.
|
||||
|
||||
```swift
|
||||
Observable.combineLatest(firstName.rx_text, lastName.rx_text) { $0 + " " + $1 }
|
||||
.map { "Greeting \($0)" }
|
||||
.bindTo(greetingLabel.rx_text)
|
||||
```
|
||||
|
||||
** 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
|
||||
|
||||
```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 modelling [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.
|
||||
|
||||
You would ideally 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")
|
||||
.retry(3)
|
||||
```
|
||||
|
||||
You can also easily create custom retry operators.
|
||||
|
||||
### Transient state
|
||||
|
||||
There is also a lot of problems with transient state when writing async programs. Typical example is 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.
|
||||
|
||||
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.
|
||||
|
||||
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?
|
||||
|
||||
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
|
||||
.throttle(0.3, scheduler: MainScheduler.instance)
|
||||
.distinctUntilChanged()
|
||||
.flatMapLatest { query in
|
||||
API.getSearchResults(query)
|
||||
.retry(3)
|
||||
.startWith([]) // clears results on new search term
|
||||
.catchErrorJustReturn([])
|
||||
}
|
||||
.subscribeNext { results in
|
||||
// bind to ui
|
||||
}
|
||||
```
|
||||
|
||||
There is no additional flags or fields required. Rx takes care of all that transient mess.
|
||||
|
||||
### Aggregating network requests
|
||||
|
||||
What if you need to fire two requests, and aggregate results when they have both finished?
|
||||
|
||||
Well, there is of course `zip` operator
|
||||
|
||||
```swift
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
So what if those APIs return results on a background thread, and binding has to happen on main UI thread? There is `observeOn`.
|
||||
|
||||
```swift
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
There are many more practical use cases where Rx really shines.
|
||||
|
||||
### 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`
|
||||
|
||||
```swift
|
||||
extension NSURLSession {
|
||||
public func rx_response(request: NSURLRequest) -> Observable<(NSData, NSURLResponse)> {
|
||||
return Observable.create { observer in
|
||||
let task = self.dataTaskWithRequest(request) { (data, response, error) in
|
||||
guard let response = response, data = data else {
|
||||
observer.on(.Error(error ?? RxCocoaURLError.Unknown))
|
||||
return
|
||||
}
|
||||
|
||||
guard let httpResponse = response as? NSHTTPURLResponse else {
|
||||
observer.on(.Error(RxCocoaURLError.NonHTTPResponse(response: response)))
|
||||
return
|
||||
}
|
||||
|
||||
observer.on(.Next(data, httpResponse))
|
||||
observer.on(.Completed)
|
||||
}
|
||||
|
||||
task.resume()
|
||||
|
||||
return AnonymousDisposable {
|
||||
task.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
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 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 be also nice if we could limit the number of concurrent image operations because blurring images is an expensive operation.
|
||||
|
||||
This is how we can do it using Rx.
|
||||
|
||||
```swift
|
||||
// this is conceptual solution
|
||||
let imageSubscription = imageURLs
|
||||
.throttle(0.2, scheduler: MainScheduler.instance)
|
||||
.flatMapLatest { imageURL in
|
||||
API.fetchImage(imageURL)
|
||||
}
|
||||
.observeOn(operationScheduler)
|
||||
.map { imageData in
|
||||
return decodeAndBlurImage(imageData)
|
||||
}
|
||||
.observeOn(MainScheduler.instance)
|
||||
.subscribeNext { blurredImage in
|
||||
imageView.image = blurredImage
|
||||
}
|
||||
.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.
|
||||
|
||||
|
||||
### Delegates
|
||||
|
||||
Delegates can be used both as a hook for customizing behavior and as an observing mechanism.
|
||||
|
||||
Each usage has it's drawbacks, but Rx can help remedy some of the problem with using delegates as an observing mechanism.
|
||||
|
||||
Using delegates and optional methods to report changes can be problematic because there can be usually only one delegate registered, so there is no way to register multiple observers.
|
||||
|
||||
Also, delegates usually don't fire initial value upon invoking delegate setter, so you'll also need to read that initial value in some other way. That is kind of tedious.
|
||||
|
||||
RxCocoa not only provides wrappers for popular UIKit/Cocoa classes, but it also provides a generic mechanism called `DelegateProxy` that enables wrapping your own delegates and exposing them as observable sequences.
|
||||
|
||||
This is real code taken from `UISearchBar` integration.
|
||||
|
||||
It uses delegate as a notification mechanism to create an `Observable<String>` that immediately returns current search text upon subscription, and then emits changed search values.
|
||||
|
||||
```swift
|
||||
extension UISearchBar {
|
||||
|
||||
public var rx_delegate: DelegateProxy {
|
||||
return proxyForObject(RxSearchBarDelegateProxy.self, self)
|
||||
}
|
||||
|
||||
public var rx_text: Observable<String> {
|
||||
return Observable.deferred { [weak self] in
|
||||
let text = self?.text ?? ""
|
||||
|
||||
return self?.rx_delegate.observe("searchBar:textDidChange:") ?? empty()
|
||||
.map { a in
|
||||
return a[1] as? String ?? ""
|
||||
}
|
||||
.startWith(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Definition of `RxSearchBarDelegateProxy` can be found [here](RxCocoa/iOS/Proxies/RxSearchBarDelegateProxy.swift)
|
||||
|
||||
This is how that API can be now used
|
||||
|
||||
```swift
|
||||
|
||||
searchBar.rx_text
|
||||
.subscribeNext { searchText in
|
||||
print("Current search text '\(searchText)'")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Notifications
|
||||
|
||||
Notifications enable registering multiple observers easily, but they are also untyped. Values need to be extracted from either `userInfo` or original target once they fire.
|
||||
|
||||
They are just a notification mechanism, and initial value usually has to be acquired in some other way.
|
||||
|
||||
That leads to this tedious pattern:
|
||||
|
||||
```swift
|
||||
let initialText = object.text
|
||||
|
||||
doSomething(initialText)
|
||||
|
||||
// ....
|
||||
|
||||
func controlTextDidChange(notification: NSNotification) {
|
||||
doSomething(object.text)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
You can use `rx_notification` to create an observable sequence with wanted properties in a similar fashion like `searchText` was constructed in delegate example, and thus reduce scattering of logic and duplication of code.
|
||||
|
||||
### KVO
|
||||
|
||||
KVO is a handy observing mechanism, but not without flaws. It's biggest flaw is confusing memory management.
|
||||
|
||||
In case of observing a property on some object, the object has to outlive the KVO observer registration otherwise your system will crash with an exception.
|
||||
|
||||
```
|
||||
`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.
|
||||
```
|
||||
|
||||
There are some rules that you can follow when observing some object that is a direct descendant or ancestor in ownership chain, but if that relation is unknown, then it becomes tricky.
|
||||
|
||||
It also has a really awkward callback method that needs to be implemented
|
||||
|
||||
```objc
|
||||
-(void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary *)change
|
||||
context:(void *)context
|
||||
```
|
||||
|
||||
RxCocoa provides a really convenient observable sequence that solves those issues called [`rx_observe` and `rx_observeWeakly`](Documentation/GettingStarted.md#kvo)
|
||||
|
||||
This is how they can be used:
|
||||
|
||||
```swift
|
||||
view.rx_observe(CGRect.self, "frame")
|
||||
.subscribeNext { (frame: CGRect?) in
|
||||
print("Got new frame \(frame)")
|
||||
}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```swift
|
||||
someSuspiciousViewController.rx_observeWeakly(Bool.self, "behavingOk")
|
||||
.subscribeNext { (behavingOk: Bool?) in
|
||||
print("Cats can purr? \(behavingOk)")
|
||||
}
|
||||
```
|
||||
|
||||
### Benefits
|
||||
|
||||
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
|
||||
|
||||
### 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?
|
||||
|
||||
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` ...
|
||||
|
||||
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](Documentation/API.md).
|
||||
|
||||
For each operator there is [marble diagram](http://reactivex.io/documentation/operators/retry.html) that helps to explain how does it work.
|
||||
|
||||
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](Documentation/GettingStarted.md#life-happens) easily, process the data, and return back into it.
|
||||
|
||||
## Build / Install / Run
|
||||
|
||||
Rx doesn't contain any external dependencies.
|
||||
|
||||
These are currently supported options:
|
||||
|
||||
### Manual
|
||||
|
||||
Open Rx.xcworkspace, choose `RxExample` and hit run. This method will build everything and run sample app
|
||||
|
||||
### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html)
|
||||
|
||||
**:warning: IMPORTANT! For tvOS support CocoaPods `0.39` is required. :warning:**
|
||||
|
||||
```
|
||||
# Podfile
|
||||
use_frameworks!
|
||||
|
||||
target 'YOUR_TARGET_NAME' do
|
||||
pod 'RxSwift', '~> 2.0'
|
||||
pod 'RxCocoa', '~> 2.0'
|
||||
pod 'RxBlocking', '~> 2.0'
|
||||
pod 'RxTests', '~> 2.0'
|
||||
end
|
||||
```
|
||||
|
||||
replace `YOUR_TARGET_NAME`, then type in the `Podfile` directory:
|
||||
|
||||
```
|
||||
$ pod install
|
||||
```
|
||||
|
||||
### [Carthage](https://github.com/Carthage/Carthage)
|
||||
|
||||
**Xcode 7.1 required**
|
||||
|
||||
Add this to `Cartfile`
|
||||
|
||||
```
|
||||
github "ReactiveX/RxSwift" ~> 2.0
|
||||
```
|
||||
|
||||
```
|
||||
$ carthage update
|
||||
```
|
||||
|
||||
### Manually using git submodules
|
||||
|
||||
* Add RxSwift as a submodule
|
||||
|
||||
```
|
||||
$ git submodule add git@github.com:ReactiveX/RxSwift.git
|
||||
```
|
||||
|
||||
* Drag `Rx.xcodeproj` into Project Navigator
|
||||
* Go to `Project > Targets > Build Phases > Link Binary With Libraries`, click `+` and select `RxSwift-[Platform]` and `RxCocoa-[Platform]` targets
|
||||
|
||||
## Comparison with ReactiveCocoa
|
||||
|
||||
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.
|
||||
|
||||
We've also decided to only rely on Swift/llvm compiler and not introduce any external dependencies.
|
||||
|
||||
Probably the main difference between these projects is the difference of 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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Playgrounds
|
||||
|
||||
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`
|
||||
|
||||
## RxExamples
|
||||
|
||||
To use playgrounds:
|
||||
|
||||
* Open `Rx.xcworkspace`
|
||||
* Choose one of example schemes and hit `Run`.
|
||||
* Does this exist for Android also? [RxJava](https://github.com/ReactiveX/RxJava)
|
||||
* Where is all of this going, what is the future, what about reactive architectures, how do you design entire apps this way? [Cycle.js](https://github.com/cyclejs/cycle-core) - this is javascript, but [RxJS](https://github.com/Reactive-Extensions/RxJS) is javascript version of Rx.
|
||||
|
||||
## References
|
||||
|
||||
|
|
|
|||
|
|
@ -103,6 +103,12 @@
|
|||
<FileRef
|
||||
location = "group:HotAndColdObservables.md">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:IssueTemplate.md">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Linux.md">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:MathBehindRx.md">
|
||||
</FileRef>
|
||||
|
|
@ -121,9 +127,6 @@
|
|||
<FileRef
|
||||
location = "group:Warnings.md">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Linux.md">
|
||||
</FileRef>
|
||||
</Group>
|
||||
<FileRef
|
||||
location = "group:Rx.playground">
|
||||
|
|
|
|||
Loading…
Reference in New Issue