Merge branch 'develop' of github.com:ReactiveX/RxSwift into feature/documentation

This commit is contained in:
Junior B 2015-11-18 14:32:25 +01:00
commit cb69030ec0
281 changed files with 19479 additions and 5800 deletions

5
.gitignore vendored
View File

@ -34,3 +34,8 @@ timeline.xctimeline
# Carthage/Checkouts
Carthage/Build
# Various
.DS_Store

View File

@ -1,8 +1,104 @@
# Change Log
All notable changes to this project will be documented in this file.
* Adds `ignoreElements` operator.
* Adds `rx_attributedText` to `UILabel`.
---
## [2.0.0-beta.3](https://github.com/ReactiveX/RxSwift/releases/tag/2.0.0-beta.3)
### Updated
* Improves KVO mechanism.
* Type of observed object is now first argument `view.rx_observe(CGRect.self, "frame")`
* Support for observing ObjC bridged enums and `RawRepresentable` protocol
* Support for easier extending of KVO using `KVORepresentable` protocol
* Deprecates KVO extensions that don't accept type as first argument in favor of ones that do.
* Adds `flatMapLatest` (also added to `Driver` unit).
* Adds `flatMapFirst` operator (also added to `Driver` unit).
* Adds `retryWhen` operator.
* Adds `window` operator.
* Adds `single` operator.
* Adds `single` (blocking version) operator.
* Adds `rx_primaryAction` on `UIButton` for `tvOS`.
* Transforms error types in `RxSwift`/`RxCocoa` projects from `NSError`s to Swift enum types.
* `RxError`
* `RxCocoaError`
* `RxCocoaURLError`
* ...
* `NSURLSession` extensions now return `Observable<(NSData!, NSHTTPURLResponse)>` instead of `Observable<(NSData!, NSURLResponse!)>`.
* Optimizes consecutive map operators. For example `map(validate1).map(validate2).map(parse)` is now internally optimized to one `map` operator.
* Adds overloads for `just`, `sequenceOf`, `toObservable` that accept scheduler.
* Deprecates `asObservable` extension of `SequenceType` in favor of `toObservable`.
* Adds `toObservable` extension to `Array`.
* Improves table view animated data source example.
* Polishing of `RxDataSourceStarterKit`
* `differentiateForSectionedView` operator
* `rx_itemsAnimatedWithDataSource` extension
* Makes blocking operators run current thread's runloop while blocking and thus disabling deadlocks.
### Fixed
* Fixes example with `Variable` in playgrounds so it less confusing regarding memory management.
* Fixes `UIImageView` extensions to use `UIImage?` instead of `UIImage!`.
* Fixes improper usage of `CustomStringConvertible` and replaces it with `CustomDebugStringConvertible`.
## [2.0.0-beta.2](https://github.com/ReactiveX/RxSwift/releases/tag/2.0.0-beta.2)
#### Updated
* Optimizations. System now performs significantly fewer allocations and is several times faster then 2.0.0-beta.1
* Makes `AnonymousObservable` private in favor of `create` method.
* Adds `toArray` operator (non blocking version).
* Adds `withLatestFrom` operator, and also extends `Driver` with that operation.
* Adds `elementAt` operator (non blocking version).
* Adds `takeLast` operator.
* Improves `RxExample` app. Adds retries example when network becomes available again.
* Adds composite extensions to `Bag` (`on`, `disposeAllIn`).
* Renames mistyped extension on `ObserverType` from `onComplete` to `onCompleted`.
#### Fixed
* Fixes minimal platform version in OSX version of library to 10.9
## [2.0.0-beta.1](https://github.com/ReactiveX/RxSwift/releases/tag/2.0.0-beta.1)
#### Updated
* Adds `Driver` unit. This unit uses Swift compiler to prove certain properties about observable sequences. Specifically
* that fallback error handling is put in place
* results are observed on main thread
* work is performed only when there is a need (at least one subscriber)
* computation results are shared between different observers (replay latest element)
* Renames `ObserverOf` to `AnyObserver`.
* Adds new interface `ObservableConvertibleType`.
* Adds `BlockingObservable` to `RxBlocking` and makes it more consistent with `RxJava`.
* Renames `func subscribe(next:error:completed:disposed:)` to `func subscribe(onNext:onError:onCompleted:onDisposed:)`
* Adds concat convenience method `public func concat<O : ObservableConvertibleType where O.E == E>(second: O) -> RxSwift.Observable<Self.E>`
* Adds `skipUntil` operator.
* Adds `takeWhile` operator.
* Renames `takeWhile` indexed version to `takeWhileWithIndex`
* Adds `skipWhile` operator.
* Adds `skipWhileWithIndex` operator.
* Adds `using` operator.
* Renames `func doOn(next:error:completed:)` to `func doOn(onNext:onError:onCompleted:)`.
* Makes `RecursiveImmediateSchedulerOf` private.
* Makes `RecursiveSchedulerOf` private.
* Adds `ConcurrentMainScheduler`.
* Adds overflow error so now in case of overflow, operators will return `RxErrorCode.Overflow`.
* Adds `rx_modelAtIndexPath` to `UITableView` and `UICollectionView`.
* Adds `var rx_didUpdateFocusInContextWithAnimationCoordinator: ControlEvent<(context:animationCoordinator:)>` to `UITableView` and `UICollectionView`
* Makes `resultSelector` argument in `combineLatest` explicit `func combineLatest<O1, O2, R>(source1: O1, _ source2: O2, resultSelector: (O1.E, O2.E) throws -> R) -> RxSwift.Observable<R>`.
* Makes `resultSelector` argument in `zip` explicit `func combineLatest<O1, O2, R>(source1: O1, _ source2: O2, resultSelector: (O1.E, O2.E) throws -> R) -> RxSwift.Observable<R>`.
* Adds activity indicator example in `RxExample` app.
* Adds two way binding example in `RxExample` app.
* many other small features
#### Fixed
* Problem with xcodebuild 7.0.1 treating tvOS shared schemes as osx schemes.
## [2.0.0-alpha.4](https://github.com/ReactiveX/RxSwift/releases/tag/2.0.0-alpha.4)
#### Updated

View File

@ -2,7 +2,7 @@
There are multiple ways you can contribute to this project.
The easiest way to contribute is to report possible bugs, request features, [discuss ideas](mailto:krunoslav.zaher@gmail.com?subject=[RxSwift] I have an idea) and share excitement about this project.
The easiest way to contribute is to report possible bugs, request features, [discuss ideas](https://github.com/ReactiveX/RxSwift/issues) and share excitement about this project.
You can also make pull requests.
@ -10,10 +10,8 @@ There are some best practices that will be followed during the development of th
So what does this mean in practice:
* If you notice a bug in **documentation** only, that could be considered non risky hotfix, so please make a PR to **master** branch
* If you notice a bug in **source code** please make a PR to **develop** branch because otherwise it could get a lot worse :) If needed, hotfix will be created from commit with the fix and applied to master branch after the PR has been merged into develop and tested.
* If you want to make a small contribution (dozen lines of code) that is not a bug fix please make a pull request to **develop** branch.
* If you want to make a big contribution to the project, please [discuss it](mailto:krunoslav.zaher@gmail.com?subject=[RxSwift] I have an idea) first with me so we can make sure project is going in the right direction. All pull requests with **source code** contributions should be targeted to **develop** branch.
* Please target your PR to **develop** branch
* If you want to make a bigger contribution to the project, please [open an issue first](https://github.com/ReactiveX/RxSwift/issues/new) so we can plan that work, discuss the architecture and brainstorm around that idea first.
## Developer's Certificate of Origin 1.1

View File

@ -21,21 +21,29 @@ Operators are stateless by default.
* [`never`](http://reactivex.io/documentation/operators/empty-never-throw.html)
* [`returnElement` / `just`](http://reactivex.io/documentation/operators/just.html)
* [`returnElements`](http://reactivex.io/documentation/operators/from.html)
* [`range`](http://reactivex.io/documentation/operators/range.html)
* [`repeatElement`](http://reactivex.io/documentation/operators/repeat.html)
* [`timer`](http://reactivex.io/documentation/operators/timer.html)
#### Transforming Observables
* [`flatMap`](http://reactivex.io/documentation/operators/flatmap.html)
* [`map` / `select`](http://reactivex.io/documentation/operators/map.html)
* [`scan`](http://reactivex.io/documentation/operators/scan.html)
* [`buffer`](http://reactivex.io/documentation/operators/buffer.html)
* [`flatMap`](http://reactivex.io/documentation/operators/flatmap.html)
* [`flatMapFirst`](http://reactivex.io/documentation/operators/flatmap.html)
* [`flatMapLatest`](http://reactivex.io/documentation/operators/flatmap.html)
* [`map`](http://reactivex.io/documentation/operators/map.html)
* [`scan`](http://reactivex.io/documentation/operators/scan.html)
* [`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)
* [`filter` / `where`](http://reactivex.io/documentation/operators/filter.html)
* [`elementAt`](http://reactivex.io/documentation/operators/elementat.html)
* [`filter`](http://reactivex.io/documentation/operators/filter.html)
* [`sample`](http://reactivex.io/documentation/operators/sample.html)
* [`skip`](http://reactivex.io/documentation/operators/skip.html)
* [`take`](http://reactivex.io/documentation/operators/take.html)
* [`takeLast`](http://reactivex.io/documentation/operators/takelast.html)
* [`single`](http://reactivex.io/documentation/operators/first.html)
#### Combining Observables
@ -49,6 +57,7 @@ Operators are stateless by default.
* [`catch`](http://reactivex.io/documentation/operators/catch.html)
* [`retry`](http://reactivex.io/documentation/operators/retry.html)
* [`retryWhen`](http://reactivex.io/documentation/operators/retry.html)
#### Observable Utility Operators
@ -57,10 +66,13 @@ Operators are stateless by default.
* [`observeOn` / `observeSingleOn`](http://reactivex.io/documentation/operators/observeon.html)
* [`subscribe`](http://reactivex.io/documentation/operators/subscribe.html)
* [`subscribeOn`](http://reactivex.io/documentation/operators/subscribeon.html)
* [`using`](http://reactivex.io/documentation/operators/using.html)
* 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)
* [`takeUntil`](http://reactivex.io/documentation/operators/takeuntil.html)
* [`takeWhile`](http://reactivex.io/documentation/operators/takewhile.html)
@ -68,6 +80,7 @@ Operators are stateless by default.
* [`concat`](http://reactivex.io/documentation/operators/concat.html)
* [`reduce` / `aggregate`](http://reactivex.io/documentation/operators/reduce.html)
* [`toArray`](http://reactivex.io/documentation/operators/to.html)
#### Connectable Observable Operators
@ -433,6 +446,6 @@ extension NSTextField {
public var rx_delegate: DelegateProxy {}
public var rx_text: ControlProperty<String> {}
}
```

View File

@ -56,14 +56,14 @@ let c = combineLatest(a, b) { $0 + $1 } // combines latest values of variabl
// To pull values out of rx variable `c`, subscribe to values from `c`.
// `subscribeNext` means subscribe to next (fresh) values of variable `c`.
// That also includes the inital value "3 is positive".
c.subscribeNext { println($0) } // prints: "3 is positive"
c.subscribeNext { print($0) } // prints: "3 is positive"
// Now let's increase the value of `a`
// a = 4 is in RxSwift
a.next(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`.
// Since the value of `c` changed, `{ println($0) }` will get called,
// Since the value of `c` changed, `{ print($0) }` will get called,
// and "6 is positive" is printed.
// Now let's change the value of `b`
@ -73,7 +73,7 @@ b.next(-8) // doesn't print anything
// 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,
// and `{ println($0) }` won't be called.
// and `{ print($0) }` won't be called.
// ...
```

View File

@ -6,7 +6,7 @@ This project tries to be consistent with [ReactiveX.io](http://reactivex.io/). T
1. [Observables aka Sequences](#observables-aka-sequences)
1. [Disposing](#disposing)
1. [Implicit `Observable` guarantees](#implicit-observable-guarantees)
1. [Creating your first `Observable` (aka sequence producers)](#creating-your-own-observable-aka-sequence-producers)
1. [Creating your first `Observable` (aka observable sequence)](#creating-your-own-observable-aka-observable-sequence)
1. [Creating an `Observable` that performs work](#creating-an-observable-that-performs-work)
1. [Sharing subscription and `shareReplay` operator](#sharing-subscription-and-sharereplay-operator)
1. [Operators](#operators)
@ -92,7 +92,7 @@ protocol ObserverType {
**When sequence sends `Complete` or `Error` event all internal resources that compute sequence elements will be freed.**
**To cancel production of sequence elements and free resources immediatelly, call `dispose` on returned subscription.**
**To cancel production of sequence elements and free resources immediately, call `dispose` on returned subscription.**
If a sequence terminates in finite time, not calling `dispose` or not using `addDisposableTo(disposeBag)` won't cause any permanent resource leaks, but those resources will be used until sequence completes in some way (finishes producing elements or error happens).
@ -265,7 +265,7 @@ Event processing ended
Event processing ended
```
## Creating your own `Observable` (aka sequence producers)
## Creating your own `Observable` (aka observable sequence)
There is one crucial thing to understand about observables.
@ -304,7 +304,7 @@ Let's create a function which creates a sequence that returns one element upon s
func myJust<E>(element: E) -> Observable<E> {
return create { observer in
observer.on(.Next(element))
obsever.on(.Completed)
observer.on(.Completed)
return NopDisposable.instance
}
}
@ -745,7 +745,7 @@ If you are unsure how exactly some of the operators work, [playgrounds](../Rx.pl
The are two error mechanisms.
### Anynchronous error handling mechanism in observables
### Asynchronous error handling mechanism in observables
Error handling is pretty straightforward. If one sequence terminates with error, then all of the dependent sequences will terminate with error. It's usual short circuit logic.
@ -753,68 +753,6 @@ You can recover from failure of observable by using `catch` operator. There are
There is also `retry` operator that enables retries in case of errored sequence.
### Synchronous error handling
Unfortunately Swift doesn't have a concept of exceptions or some kind of built in error monad so this project introduces `RxResult` enum.
It is Swift port of Scala [`Try`](http://www.scala-lang.org/api/2.10.2/index.html#scala.util.Try) type. It is also similar to Haskell [`Either`](https://hackage.haskell.org/package/category-extras-0.52.0/docs/Control-Monad-Either.html) monad.
**This will be replaced in Swift 2.0 with try/throws**
```swift
public enum RxResult<ResultType> {
case Success(ResultType)
case Error(ErrorType)
}
```
To enable writing more readable code, a few `Result` operators are introduced
```swift
result1.flatMap { okValue in // success handling block
// executed on success
return ?
}.recoverWith { error in // error handling block
// executed on error
return ?
}
```
### Error handling and function names
For every group of transforming functions there are versions with and without "OrDie" suffix.
**This will change in 2.0 version and map will have two overloads, with and without `throws`.**
e.g.
```swift
public func mapOrDie<E, R>
(selector: E -> RxResult<R>)
-> (Observable<E> -> Observable<R>) {
return { source in
return selectOrDie(selector)(source)
}
}
public func map<E, R>
(selector: E -> R)
-> (Observable<E> -> Observable<R>) {
return { source in
return select(selector)(source)
}
}
```
Returning an error from a selector will cause entire graph of dependent sequence transformers to "die" and fail with error. Dying implies that it will release all of its resources and never produce another sequence value. This is usually not an obvious effect.
If there is some `UITextField` bound to a observable sequence that fails with error or completes, screen won't be updated ever again.
To make those situations more obvious, RxCocoa debug build will throw an exception in case some sequence that is bound to UI control terminates with an error.
Using functions without "OrDie" suffix is usually a more safe option.
There is also the `catch` operator for easier error handling.
## Debugging Compile Errors
When writing elegant RxSwift/RxCocoa code, you are probably relying heavily on compiler to deduce types of `Observable`s. This is one of the reasons why Swift is awesome, but it can also be frustrating sometimes.
@ -1008,32 +946,37 @@ There are two built in ways this library supports KVO.
```swift
// KVO
extension NSObject {
public func rx_observe<Element>(keyPath: String, retainSelf: Bool = true) -> Observable<Element?> {}
public func rx_observe<E>(type: E.Type, _ keyPath: String, options: NSKeyValueObservingOptions, retainSelf: Bool = true) -> Observable<E?> {}
}
#if !DISABLE_SWIZZLING
// KVO
extension NSObject {
public func rx_observeWeakly<Element>(keyPath: String) -> Observable<Element?> {}
public func rx_observeWeakly<E>(type: E.Type, _ keyPath: String, options: NSKeyValueObservingOptions) -> Observable<E?> {}
}
#endif
```
**If Swift compiler doesn't have a way to deduce observed type (return Observable type), it will report error about function not existing.**
Example how to observe frame of `UIView`.
Here are some ways you can give him hints about observed type:
**WARNING: UIKit isn't KVO compliant, but this will work.**
```swift
view.rx_observe("frame") as Observable<CGRect?>
view
.rx_observe(CGRect.self, "frame")
.subscribeNext { frame in
...
}
```
or
```swift
view.rx_observe("frame")
.map { (rect: CGRect?) in
//
}
view
.rx_observeWeakly(CGRect.self, "frame")
.subscribeNext { frame in
...
}
```
### `rx_observe`
@ -1047,12 +990,12 @@ view.rx_observe("frame")
E.g.
```swift
self.rx_observe("view.frame", retainSelf = false) as Observable<CGRect?>
self.rx_observe(CGRect.self, "view.frame", retainSelf: false)
```
### `rx_observeWeakly`
`rx_observeWeakly` has somewhat worse performance because it has to handle object deallocation in case of weak references.
`rx_observeWeakly` has somewhat slower then `rx_observe` because it has to handle object deallocation in case of weak references.
It can be used in all cases where `rx_observe` can be used and additionally
@ -1062,16 +1005,18 @@ It can be used in all cases where `rx_observe` can be used and additionally
E.g.
```swift
someSuspiciousViewController.rx_observeWeakly("behavingOk") as Observable<Bool?>
someSuspiciousViewController.rx_observeWeakly(Bool.self, "behavingOk")
```
### Observing structs
KVO is an Objective-C mechanism so it relies heavily on `NSValue`. RxCocoa has additional specializations for `CGRect`, `CGSize` and `CGPoint` that make it convenient to observe those types.
KVO is an Objective-C mechanism so it relies heavily on `NSValue`.
When observing some other structures it is necessary to extract those structures from `NSValue` manually, or creating your own observable sequence specializations.
**RxCocoa has built in support for KVO observing of `CGRect`, `CGSize` and `CGPoint` structs.**
[Here](../RxCocoa/Common/Observables/NSObject+Rx+CoreGraphics.swift) are examples how to correctly extract structures from `NSValue`.
When observing some other structures it is necessary to extract those structures from `NSValue` manually.
[Here](../RxCocoa/Common/KVORepresentable+CoreGraphics.swift) are examples how to extend KVO observing mechanism and `rx_observe*` methods for other structs by implementing `KVORepresentable` protocol.
## UI layer tips
@ -1105,13 +1050,12 @@ Let's say you have something like this:
let searchResults = searchText
.throttle(0.3, $.mainScheduler)
.distinctUntilChanged
.map { query in
.flatMapLatest { query in
API.getSearchResults(query)
.retry(3)
.startWith([]) // clears results on new search term
.catchErrorJustReturn([])
}
.switchLatest()
.shareReplay(1) // <- notice the `shareReplay` operator
```
@ -1119,6 +1063,8 @@ What you usually want is to share search results once calculated. That is what `
**It is usually a good rule of thumb in the UI layer to add `shareReplay` at the end of transformation chain because you really want to share calculated results. You don't want to fire separate HTTP connections when binding `searchResults` to multiple UI elements.**
**Also take a look at `Driver` unit. It is designed to transparently wrap those `shareReply` calls, make sure elements are observed on main UI thread and that no error can be bound to UI.**
## Making HTTP requests
Making http requests is one of the first things people try.

View File

@ -94,6 +94,17 @@ Rx can use all types of schedulers, but it can also perform some additional opti
These are currently supported schedulers
## CurrentThreadScheduler (Serial scheduler)
Schedules units of work on the current thread.
This is the default scheduler for operators that generate elements.
This scheduler is also sometimes called `trampoline scheduler`.
If `CurrentThreadScheduler.instance.schedule(state) { }` is called for first time on some thread, scheduled action will be executed immediately and hidden queue will be created where all recursively scheduled actions will be temporarily enqueued.
If some parent frame on call stack is already running `CurrentThreadScheduler.instance.schedule(state) { }`, scheduled action will be enqueued and executed when currently running action and all previously enqueued actions have finished executing.
## MainScheduler (Serial scheduler)
Abstracts work that needs to be performed on `MainThread`. In case `schedule` methods are called from main thread, it will perform action immediately without scheduling.
@ -110,12 +121,12 @@ Main scheduler is an instance of `SerialDispatchQueueScheduler`.
## ConcurrentDispatchQueueScheduler (Concurrent scheduler)
Abstracts the work that needs to be peformed on a specific `dispatch_queue_t`. You can also pass a serial dispatch queue, it shouldn't cause any problems.
Abstracts the work that needs to be performed on a specific `dispatch_queue_t`. You can also pass a serial dispatch queue, it shouldn't cause any problems.
This scheduler is suitable when some work needs to be performed in background.
## OperationQueueScheduler (Concurrent scheduler)
Abstracts the work that needs to be peformed on a specific `NSOperationQueue`.
Abstracts the work that needs to be performed on a specific `NSOperationQueue`.
This scheduler is suitable for cases when there is some bigger chunk of work that needs to be performed in background and you want to fine tune concurrent processing using `maxConcurrentOperationCount`.

24
Documentation/Units.md Normal file
View File

@ -0,0 +1,24 @@
Units
=====
This document will try to describe what are units, why are they a useful concept, how to use them and how to create them.
* [Why](#why)
* [Design Rationale](#design-rationale)
* ...
# Why
The purpose of units is to use the Swift compiler static type checking to prove your code is behaving like designed.
RxCocoa project already contains several units, but the most elaborate one is called `Driver`, so this unit will be used to explain the idea behind units.
`Driver` was named that way because it describes sequences that drive certain parts of the app. Those sequences will usually drive UI bindings, UI event pumps that keep your application responsive, but also drive application services, etc.
The purpose of `Driver` unit is to ensure the underlying observable sequence has the following properties.
* can't fail, all failures are being handled properly
* elements are delivered on main thread
* sequence computation resources are shared
TBD...

124
Documentation/Warnings.md Normal file
View File

@ -0,0 +1,124 @@
Warnings
========
### <a name="unused-disposable"></a>Unused disposable (unused-disposable)
The same is valid for `subscribe*`, `bind*` and `drive*` family of functions that return `Disposable`.
Warning is probably presented in a context similar to this one:
```Swift
let xs: Observable<E> ....
xs
.filter { ... }
.map { ... }
.switchLatest()
.subscribe(onNext: {
...
}, onError: {
...
})
```
`subscribe` function returns a subscription `Disposable` that can be used to cancel computation and free resources.
Preferred way of terminating these fluent calls is by using `.addDisposableTo(disposeBag)` or in some equivalent way.
```Swift
let xs: Observable<E> ....
let disposeBag = DisposeBag()
xs
.filter { ... }
.map { ... }
.switchLatest()
.subscribe(onNext: {
...
}, onError: {
...
})
.addDisposableTo(disposeBag) // <--- note `addDisposableTo`
```
When `disposeBag` gets deallocated, subscription will be automatically disposed.
In case `xs` terminates in a predictable way with `Completed` or `Error` message, not handling subscription `Disposable` won't leak any resources, but it's still preferred way because in that way element computation is terminated at predictable moment.
That will also make your code robust and future proof because resources will be properly disposed even if `xs` implementation changes.
Another way to make sure subscriptions and resources are tied with the lifetime of some object is by using `takeUntil` operator.
```Swift
let xs: Observable<E> ....
let someObject: NSObject ...
_ = xs
.filter { ... }
.map { ... }
.switchLatest()
.takeUntil(someObject.rx_dellocated) // <-- note the `takeUntil` operator
.subscribe(onNext: {
...
}, onError: {
...
})
```
If ignoring the subscription `Disposable` is desired behavior, this is how to silence the compiler warning.
```Swift
let xs: Observable<E> ....
let disposeBag = DisposeBag()
_ = xs // <-- note the underscore
.filter { ... }
.map { ... }
.switchLatest()
.subscribe(onNext: {
...
}, onError: {
...
})
```
### <a name="unused-observable"></a>Unused observable sequence (unused-observable)
Warning is probably presented in a context similar to this one:
```Swift
let xs: Observable<E> ....
xs
.filter { ... }
.map { ... }
```
This code defines observable sequence that is filtered and mapped `xs` sequence but then ignores the result.
Since this code just defines an observable sequence and then ignores it, it doesn't actually do nothing and it's pretty much useless.
Your intention was probably to either store the observable sequence definition and use it later ...
```Swift
let xs: Observable<E> ....
let ys = xs // <--- names definition as `ys`
.filter { ... }
.map { ... }
```
... or start computation based on that definition
```Swift
let xs: Observable<E> ....
let disposeBag = DisposeBag()
xs
.filter { ... }
.map { ... }
.subscribeNext { nextElement in // <-- note the `subscribe*` method
... probably print or something
}
.addDisposableTo(disposeBag)
```

View File

@ -45,7 +45,7 @@ example("combineLatest 1") {
let intOb1 = PublishSubject<String>()
let intOb2 = PublishSubject<Int>()
combineLatest(intOb1, intOb2) {
_ = combineLatest(intOb1, intOb2) {
"\($0) \($1)"
}
.subscribe {
@ -68,7 +68,7 @@ example("combineLatest 2") {
let intOb1 = just(2)
let intOb2 = sequenceOf(0, 1, 2, 3, 4)
combineLatest(intOb1, intOb2) {
_ = combineLatest(intOb1, intOb2) {
$0 * $1
}
.subscribe {
@ -85,8 +85,8 @@ example("combineLatest 3") {
let intOb2 = sequenceOf(0, 1, 2, 3)
let intOb3 = sequenceOf(0, 1, 2, 3, 4)
combineLatest(intOb1, intOb2, intOb3) {
($0 + $1) * $2
_ = combineLatest(intOb1, intOb2, intOb3) {
($0 + $1) * $2
}
.subscribe {
print($0)
@ -95,6 +95,39 @@ example("combineLatest 3") {
//: Combinelatest version that allows combining sequences with different types.
example("combineLatest 4") {
let intOb = just(2)
let stringOb = just("a")
_ = combineLatest(intOb, stringOb) {
"\($0) " + $1
}
.subscribe {
print($0)
}
}
//: `combineLatest` extension method for Array of `ObservableType` conformable types
//: The array must be formed by `Observables` of the same type.
example("combineLatest 5") {
let intOb1 = just(2)
let intOb2 = sequenceOf(0, 1, 2, 3)
let intOb3 = sequenceOf(0, 1, 2, 3, 4)
_ = [intOb1, intOb2, intOb3].combineLatest { intArray -> Int in
Int((intArray[0] + intArray[1]) * intArray[2])
}
.subscribe { (event: Event<Int>) -> Void in
print(event)
}
}
/*:
### `zip`
@ -108,7 +141,7 @@ example("zip 1") {
let intOb1 = PublishSubject<String>()
let intOb2 = PublishSubject<Int>()
zip(intOb1, intOb2) {
_ = zip(intOb1, intOb2) {
"\($0) \($1)"
}
.subscribe {
@ -132,7 +165,7 @@ example("zip 2") {
let intOb2 = sequenceOf(0, 1, 2, 3, 4)
zip(intOb1, intOb2) {
_ = zip(intOb1, intOb2) {
$0 * $1
}
.subscribe {
@ -146,7 +179,7 @@ example("zip 3") {
let intOb2 = sequenceOf(0, 1, 2, 3)
let intOb3 = sequenceOf(0, 1, 2, 3, 4)
zip(intOb1, intOb2, intOb3) {
_ = zip(intOb1, intOb2, intOb3) {
($0 + $1) * $2
}
.subscribe {
@ -170,7 +203,7 @@ example("merge 1") {
let subject1 = PublishSubject<Int>()
let subject2 = PublishSubject<Int>()
sequenceOf(subject1, subject2)
_ = sequenceOf(subject1, subject2)
.merge()
.subscribeNext { int in
print(int)
@ -190,7 +223,7 @@ example("merge 2") {
let subject1 = PublishSubject<Int>()
let subject2 = PublishSubject<Int>()
sequenceOf(subject1, subject2)
_ = sequenceOf(subject1, subject2)
.merge(maxConcurrent: 2)
.subscribe {
print($0)

View File

@ -23,7 +23,7 @@ example("takeUntil") {
let originalSequence = PublishSubject<Int>()
let whenThisSendsNextWorldStops = PublishSubject<Int>()
originalSequence
_ = originalSequence
.takeUntil(whenThisSendsNextWorldStops)
.subscribe {
print($0)
@ -53,7 +53,7 @@ example("takeWhile") {
let sequence = PublishSubject<Int>()
sequence
_ = sequence
.takeWhile { int in
int < 4
}

View File

@ -1,7 +1,6 @@
//: [<< Previous](@previous) - [Index](Index)
import RxSwift
import XCPlayground
/*:
## Connectable Observable Operators
@ -15,13 +14,13 @@ func sampleWithoutConnectableOperators() {
let int1 = interval(1, MainScheduler.sharedInstance)
int1
_ = int1
.subscribe {
print("first subscription \($0)")
}
delay(5) {
int1
_ = int1
.subscribe {
print("second subscription \($0)")
}
@ -44,7 +43,7 @@ func sampleWithMulticast() {
let subject1 = PublishSubject<Int64>()
subject1
_ = subject1
.subscribe {
print("Subject \($0)")
}
@ -52,7 +51,7 @@ func sampleWithMulticast() {
let int1 = interval(1, MainScheduler.sharedInstance)
.multicast(subject1)
int1
_ = int1
.subscribe {
print("first subscription \($0)")
}
@ -62,14 +61,14 @@ func sampleWithMulticast() {
}
delay(4) {
int1
_ = int1
.subscribe {
print("second subscription \($0)")
}
}
delay(6) {
int1
_ = int1
.subscribe {
print("third subscription \($0)")
}
@ -95,7 +94,7 @@ func sampleWithReplayBuffer0() {
let int1 = interval(1, MainScheduler.sharedInstance)
.replay(0)
int1
_ = int1
.subscribe {
print("first subscription \($0)")
}
@ -105,14 +104,14 @@ func sampleWithReplayBuffer0() {
}
delay(4) {
int1
_ = int1
.subscribe {
print("second subscription \($0)")
}
}
delay(6) {
int1
_ = int1
.subscribe {
print("third subscription \($0)")
}
@ -130,7 +129,7 @@ func sampleWithReplayBuffer2() {
let int1 = interval(1, MainScheduler.sharedInstance)
.replay(2)
int1
_ = int1
.subscribe {
print("first subscription \($0)")
}
@ -140,14 +139,14 @@ func sampleWithReplayBuffer2() {
}
delay(4) {
int1
_ = int1
.subscribe {
print("second subscription \($0)")
}
}
delay(6) {
int1
_ = int1
.subscribe {
print("third subscription \($0)")
}
@ -173,7 +172,7 @@ func sampleWithPublish() {
let int1 = interval(1, MainScheduler.sharedInstance)
.publish()
int1
_ = int1
.subscribe {
print("first subscription \($0)")
}
@ -183,14 +182,14 @@ func sampleWithPublish() {
}
delay(4) {
int1
_ = int1
.subscribe {
print("second subscription \($0)")
}
}
delay(6) {
int1
_ = int1
.subscribe {
print("third subscription \($0)")
}
@ -200,6 +199,6 @@ func sampleWithPublish() {
// sampleWithPublish()
XCPSetExecutionShouldContinueIndefinitely(true)
playgroundShouldContinueIndefinitely()
//: [Index](Index)

View File

@ -21,7 +21,7 @@ example("catchError 1") {
let sequenceThatFails = PublishSubject<Int>()
let recoverySequence = sequenceOf(100, 200, 300, 400)
sequenceThatFails
_ = sequenceThatFails
.catchError { error in
return recoverySequence
}
@ -40,7 +40,7 @@ example("catchError 1") {
example("catchError 2") {
let sequenceThatFails = PublishSubject<Int>()
sequenceThatFails
_ = sequenceThatFails
.catchErrorJustReturn(100)
.subscribe {
print($0)
@ -83,7 +83,7 @@ example("retry") {
return NopDisposable.instance
}
funnyLookingSequence
_ = funnyLookingSequence
.retry()
.subscribe {
print($0)

View File

@ -81,8 +81,8 @@ example("sequenceOf") {
`from` creates a sequence from `SequenceType`
*/
example("from") {
let sequenceFromArray = [1, 2, 3, 4, 5].asObservable()
example("toObservable") {
let sequenceFromArray = [1, 2, 3, 4, 5].toObservable()
let subscription = sequenceFromArray
.subscribe { event in
@ -149,12 +149,12 @@ example("deferred") {
}
}
deferredSequence
_ = deferredSequence
.subscribe { event in
print(event)
}
deferredSequence
_ = deferredSequence
.subscribe { event in
print(event)
}

View File

@ -65,7 +65,7 @@ This function will perform a function on each element in the sequence until it i
*/
example("reduce") {
sequenceOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
_ = sequenceOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
.reduce(0, +)
.subscribe {
print($0)

View File

@ -18,7 +18,7 @@ A toolbox of useful Operators for working with Observables.
example("subscribe") {
let sequenceOfInts = PublishSubject<Int>()
sequenceOfInts
_ = sequenceOfInts
.subscribe {
print($0)
}
@ -40,7 +40,7 @@ There are several variants of the `subscribe` operator.
example("subscribeNext") {
let sequenceOfInts = PublishSubject<Int>()
sequenceOfInts
_ = sequenceOfInts
.subscribeNext {
print($0)
}
@ -58,7 +58,7 @@ example("subscribeNext") {
example("subscribeCompleted") {
let sequenceOfInts = PublishSubject<Int>()
sequenceOfInts
_ = sequenceOfInts
.subscribeCompleted {
print("It's completed")
}
@ -76,7 +76,7 @@ example("subscribeCompleted") {
example("subscribeError") {
let sequenceOfInts = PublishSubject<Int>()
sequenceOfInts
_ = sequenceOfInts
.subscribeError { error in
print(error)
}
@ -98,7 +98,7 @@ register an action to take upon a variety of Observable lifecycle events
example("doOn") {
let sequenceOfInts = PublishSubject<Int>()
sequenceOfInts
_ = sequenceOfInts
.doOn {
print("Intercepted event \($0)")
}

View File

@ -7,8 +7,8 @@ import RxSwift
A Subject is a sort of bridge or proxy that is available in some implementations of ReactiveX that acts both as an observer and as an Observable. Because it is an observer, it can subscribe to one or more Observables, and because it is an Observable, it can pass through the items it observes by reemitting them, and it can also emit new items.
*/
func writeSequenceToConsole<O: ObservableType>(name: String, sequence: O) {
sequence
func writeSequenceToConsole<O: ObservableType>(name: String, sequence: O) -> Disposable {
return sequence
.subscribe { e in
print("Subscription: \(name), event: \(e)")
}
@ -27,11 +27,13 @@ func writeSequenceToConsole<O: ObservableType>(name: String, sequence: O) {
*/
example("PublishSubject") {
let disposeBag = DisposeBag()
let subject = PublishSubject<String>()
writeSequenceToConsole("1", sequence: subject)
writeSequenceToConsole("1", sequence: subject).addDisposableTo(disposeBag)
subject.on(.Next("a"))
subject.on(.Next("b"))
writeSequenceToConsole("2", sequence: subject)
writeSequenceToConsole("2", sequence: subject).addDisposableTo(disposeBag)
subject.on(.Next("c"))
subject.on(.Next("d"))
}
@ -46,12 +48,13 @@ example("PublishSubject") {
![](https://raw.githubusercontent.com/kzaher/rxswiftcontent/master/MarbleDiagrams/png/replaysubject.png)
*/
example("ReplaySubject") {
let disposeBag = DisposeBag()
let subject = ReplaySubject<String>.create(bufferSize: 1)
writeSequenceToConsole("1", sequence: subject)
writeSequenceToConsole("1", sequence: subject).addDisposableTo(disposeBag)
subject.on(.Next("a"))
subject.on(.Next("b"))
writeSequenceToConsole("2", sequence: subject)
writeSequenceToConsole("2", sequence: subject).addDisposableTo(disposeBag)
subject.on(.Next("c"))
subject.on(.Next("d"))
}
@ -68,11 +71,13 @@ When an observer subscribes to a `BehaviorSubject`, it begins by emitting the it
![](https://raw.githubusercontent.com/kzaher/rxswiftcontent/master/MarbleDiagrams/png/behaviorsubject_error.png)
*/
example("BehaviorSubject") {
let disposeBag = DisposeBag()
let subject = BehaviorSubject(value: "z")
writeSequenceToConsole("1", sequence: subject)
writeSequenceToConsole("1", sequence: subject).addDisposableTo(disposeBag)
subject.on(.Next("a"))
subject.on(.Next("b"))
writeSequenceToConsole("2", sequence: subject)
writeSequenceToConsole("2", sequence: subject).addDisposableTo(disposeBag)
subject.on(.Next("c"))
subject.on(.Next("d"))
subject.on(.Completed)
@ -86,11 +91,12 @@ example("BehaviorSubject") {
*/
example("Variable") {
let disposeBag = DisposeBag()
let variable = Variable("z")
writeSequenceToConsole("1", sequence: variable)
writeSequenceToConsole("1", sequence: variable).addDisposableTo(disposeBag)
variable.value = "a"
variable.value = "b"
writeSequenceToConsole("2", sequence: variable)
writeSequenceToConsole("2", sequence: variable).addDisposableTo(disposeBag)
variable.value = "c"
variable.value = "d"
}

View File

@ -21,7 +21,7 @@ Transform the items emitted by an Observable by applying a function to each item
example("map") {
let originalSequence = sequenceOf(Character("A"), Character("B"), Character("C"))
originalSequence
_ = originalSequence
.map { char in
char.hashValue
}
@ -43,7 +43,7 @@ example("flatMap") {
let sequenceString = sequenceOf("A", "B", "C", "D", "E", "F", "--")
sequenceInt
_ = sequenceInt
.flatMap { int in
sequenceString
}
@ -65,7 +65,7 @@ Apply a function to each item emitted by an Observable, sequentially, and emit e
example("scan") {
let sequenceToSum = sequenceOf(0, 1, 2, 3, 4, 5)
sequenceToSum
_ = sequenceToSum
.scan(0) { acum, elem in
acum + elem
}

View File

@ -13,4 +13,19 @@ public func delay(delay:Double, closure:()->()) {
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
}
#if NOT_IN_PLAYGROUND
public func playgroundShouldContinueIndefinitely() {
}
#else
import XCPlayground
public func playgroundShouldContinueIndefinitely() {
XCPSetExecutionShouldContinueIndefinitely(true)
}
#endif

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='6.0' target-platform='osx' requires-full-environment='true' display-mode='rendered'>
<playground version='6.0' target-platform='osx' display-mode='rendered'>
<pages>
<page name='Index'/>
<page name='Introduction'/>

File diff suppressed because it is too large Load Diff

View File

@ -28,26 +28,7 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D20034311BB9AFA70035511A"
BuildableName = "RxSwift-tvOSTests.xctest"
BlueprintName = "RxSwift-tvOSTests"
ReferencedContainer = "container:Rx.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D2EA280B1BB9B5A200880ED3"
BuildableName = "RxSwift.framework"
BlueprintName = "RxSwift-tvOS"
ReferencedContainer = "container:Rx.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>

View File

@ -1,9 +1,9 @@
Pod::Spec.new do |s|
s.name = "RxBlocking"
s.version = "2.0.0-alpha.4"
s.version = "2.0.0-beta.3"
s.summary = "RxSwift Blocking operatos"
s.description = <<-DESC
Set of blocking operators for unit testing
Set of blocking operators for RxSwift.
DESC
s.homepage = "https://github.com/ReactiveX/RxSwift"
s.license = 'MIT'
@ -19,5 +19,5 @@ Pod::Spec.new do |s|
s.source_files = 'RxBlocking/**/*.swift'
s.dependency 'RxSwift', '~> 2.0.0-alpha'
s.dependency 'RxSwift', '~> 2.0.0-beta'
end

View File

@ -0,0 +1,221 @@
//
// BlockingObservable+Operators.swift
// Rx
//
// Created by Krunoslav Zaher on 10/19/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
extension BlockingObservable {
/**
Blocks current thread until sequence terminates.
If sequence terminates with error, terminating error will be thrown.
- returns: All elements of sequence.
*/
public func toArray() throws -> [E] {
var elements: [E] = Array<E>()
var error: ErrorType?
let lock = RunLoopLock()
let d = SingleAssignmentDisposable()
lock.dispatch {
d.disposable = self.source.subscribe { e in
switch e {
case .Next(let element):
elements.append(element)
case .Error(let e):
error = e
lock.stop()
case .Completed:
lock.stop()
}
}
}
lock.run()
d.dispose()
if let error = error {
throw error
}
return elements
}
}
extension BlockingObservable {
/**
Blocks current thread until sequence produces first element.
If sequence terminates with error before producing first element, terminating error will be thrown.
- returns: First element of sequence. If sequence is empty `nil` is returned.
*/
public func first() throws -> E? {
var element: E?
var error: ErrorType?
let d = SingleAssignmentDisposable()
let lock = RunLoopLock()
lock.dispatch {
d.disposable = self.source.subscribe { e in
switch e {
case .Next(let e):
if element == nil {
element = e
}
break
case .Error(let e):
error = e
default:
break
}
lock.stop()
}
}
lock.run()
d.dispose()
if let error = error {
throw error
}
return element
}
}
extension BlockingObservable {
/**
Blocks current thread until sequence terminates.
If sequence terminates with error, terminating error will be thrown.
- returns: Last element in the sequence. If sequence is empty `nil` is returned.
*/
public func last() throws -> E? {
var element: E?
var error: ErrorType?
let d = SingleAssignmentDisposable()
let lock = RunLoopLock()
lock.dispatch {
d.disposable = self.source.subscribe { e in
switch e {
case .Next(let e):
element = e
return
case .Error(let e):
error = e
default:
break
}
lock.stop()
}
}
lock.run()
d.dispose()
if let error = error {
throw error
}
return element
}
}
extension BlockingObservable {
/**
Blocks current thread until sequence terminates.
If sequence terminates with error before producing first element, terminating error will be thrown.
- returns: Returns the only element of an sequence, and reports an error if there is not exactly one element in the observable sequence.
*/
public func single() throws -> E? {
return try single { _ in true }
}
/**
Blocks current thread until sequence terminates.
If sequence terminates with error before producing first element, terminating error will be thrown.
- parameter predicate: A function to test each source element for a condition.
- returns: Returns the only element of an sequence that satisfies the condition in the predicate, and reports an error if there is not exactly one element in the sequence.
*/
public func single(predicate: (E) throws -> Bool) throws -> E? {
var element: E?
var error: ErrorType?
let d = SingleAssignmentDisposable()
let lock = RunLoopLock()
lock.dispatch {
d.disposable = self.source.subscribe { e in
if d.disposed {
return
}
switch e {
case .Next(let e):
do {
if try !predicate(e) {
return
}
if element == nil {
element = e
} else {
throw RxError.MoreThanOneElement
}
} catch (let err) {
error = err
d.dispose()
lock.stop()
}
return
case .Error(let e):
error = e
case .Completed:
if element == nil {
error = RxError.NoElements
}
}
lock.stop()
}
}
lock.run()
d.dispose()
if let error = error {
throw error
}
return element
}
}

View File

@ -0,0 +1,24 @@
//
// BlockingObservable.swift
// Rx
//
// Created by Krunoslav Zaher on 10/19/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
/**
`BlockingObservable` is a variety of `Observable` that provides blocking operators.
It can be useful for testing and demo purposes, but is generally inappropriate for production applications.
If you think you need to use a `BlockingObservable` this is usually a sign that you should rethink your
design.
*/
public struct BlockingObservable<E> {
let source: Observable<E>
}

View File

@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>Krunoslav-Zaher.$(PRODUCT_NAME:rfc1034identifier)</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>2.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

View File

@ -1,164 +0,0 @@
//
// Observable+Blocking.swift
// RxBlocking
//
// Created by Krunoslav Zaher on 7/12/15.
// Copyright (c) 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
extension ObservableType {
/**
Blocks current thread until sequence terminates.
If sequence terminates with error, terminating error will be thrown.
- returns: All elements of sequence.
*/
public func toArray() throws -> [E] {
let condition = NSCondition()
var elements = [E]()
var error: ErrorType?
var ended = false
_ = self.subscribe { e in
switch e {
case .Next(let element):
elements.append(element)
case .Error(let e):
error = e
condition.lock()
ended = true
condition.signal()
condition.unlock()
case .Completed:
condition.lock()
ended = true
condition.signal()
condition.unlock()
}
}
condition.lock()
while !ended {
condition.wait()
}
condition.unlock()
if let error = error {
throw error
}
return elements
}
}
extension ObservableType {
/**
Blocks current thread until sequence produces first element.
If sequence terminates with error before producing first element, terminating error will be thrown.
- returns: First element of sequence. If sequence is empty `nil` is returned.
*/
public func first() throws -> E? {
let condition = NSCondition()
var element: E?
var error: ErrorType?
var ended = false
let d = SingleAssignmentDisposable()
d.disposable = self.subscribe { e in
switch e {
case .Next(let e):
if element == nil {
element = e
}
break
case .Error(let e):
error = e
default:
break
}
condition.lock()
ended = true
condition.signal()
condition.unlock()
}
condition.lock()
while !ended {
condition.wait()
}
d.dispose()
condition.unlock()
if let error = error {
throw error
}
return element
}
}
extension ObservableType {
/**
Blocks current thread until sequence terminates.
If sequence terminates with error, terminating error will be thrown.
- returns: Last element in the sequence. If sequence is empty `nil` is returned.
*/
public func last() throws -> E? {
let condition = NSCondition()
var element: E?
var error: ErrorType?
var ended = false
let d = SingleAssignmentDisposable()
d.disposable = self.subscribe { e in
switch e {
case .Next(let e):
element = e
return
case .Error(let e):
error = e
default:
break
}
condition.lock()
ended = true
condition.signal()
condition.unlock()
}
condition.lock()
while !ended {
condition.wait()
}
d.dispose()
condition.unlock()
if let error = error {
throw error
}
return element
}
}

View File

@ -0,0 +1,24 @@
//
// Observable+Blocking.swift
// RxBlocking
//
// Created by Krunoslav Zaher on 7/12/15.
// Copyright (c) 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
extension ObservableConvertibleType {
/**
Converts an Observable into a `BlockingObservable` (an Observable with blocking operators).
- returns: `BlockingObservable` version of `self`
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func toBlocking() -> BlockingObservable<E> {
return BlockingObservable(source: self.asObservable())
}
}

View File

@ -0,0 +1,46 @@
//
// RunLoopLock.swift
// Rx
//
// Created by Krunoslav Zaher on 11/5/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
class RunLoopLock : NSObject {
let currentRunLoop: CFRunLoopRef
override init() {
currentRunLoop = CFRunLoopGetCurrent()
}
func dispatch(action: () -> ()) {
CFRunLoopPerformBlock(currentRunLoop, kCFRunLoopDefaultMode) {
if CurrentThreadScheduler.isScheduleRequired {
CurrentThreadScheduler.instance.schedule(()) { _ in
action()
return NopDisposable.instance
}
}
else {
action()
}
}
CFRunLoopWakeUp(currentRunLoop)
}
func stop() {
CFRunLoopPerformBlock(currentRunLoop, kCFRunLoopDefaultMode) {
CFRunLoopStop(self.currentRunLoop)
}
CFRunLoopWakeUp(currentRunLoop)
}
func run() {
CFRunLoopRun()
}
}

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "RxCocoa"
s.version = "2.0.0-alpha.4"
s.version = "2.0.0-beta.3"
s.summary = "RxSwift Cocoa extensions"
s.description = <<-DESC
* UI extensions
@ -25,5 +25,5 @@ Pod::Spec.new do |s|
s.watchos.source_files = 'RxCocoa/iOS/**/*.swift'
s.tvos.source_files = 'RxCocoa/iOS/**/*.swift'
s.dependency 'RxSwift', '~> 2.0.0-alpha'
s.dependency 'RxSwift', '~> 2.0.0-beta'
end

View File

@ -39,7 +39,7 @@ public struct ControlEvent<PropertyType> : ControlEventType {
let source: Observable<PropertyType>
init(source: Observable<PropertyType>) {
self.source = source
self.source = source.subscribeOn(ConcurrentMainScheduler.sharedInstance)
}
/**
@ -55,6 +55,7 @@ public struct ControlEvent<PropertyType> : ControlEventType {
/**
- returns: `Observable` interface.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func asObservable() -> Observable<E> {
return self.source
}
@ -62,6 +63,7 @@ public struct ControlEvent<PropertyType> : ControlEventType {
/**
- returns: `ControlEvent` interface.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func asControlEvent() -> ControlEvent<E> {
return self
}

View File

@ -38,10 +38,10 @@ public struct ControlProperty<PropertyType> : ControlPropertyType {
public typealias E = PropertyType
let source: Observable<PropertyType>
let observer: ObserverOf<PropertyType>
let observer: AnyObserver<PropertyType>
init(source: Observable<PropertyType>, observer: ObserverOf<PropertyType>) {
self.source = source
init(source: Observable<PropertyType>, observer: AnyObserver<PropertyType>) {
self.source = source.subscribeOn(ConcurrentMainScheduler.sharedInstance)
self.observer = observer
}
@ -58,6 +58,7 @@ public struct ControlProperty<PropertyType> : ControlPropertyType {
/**
- returns: `Observable` interface.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func asObservable() -> Observable<E> {
return self.source
}
@ -65,6 +66,7 @@ public struct ControlProperty<PropertyType> : ControlPropertyType {
/**
- returns: `ControlProperty` interface.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func asControlProperty() -> ControlProperty<E> {
return self
}

View File

@ -0,0 +1,30 @@
//
// ControlEvent+Driver.swift
// Rx
//
// Created by Krunoslav Zaher on 9/19/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
extension ControlEvent {
/**
Converts `ControlEvent` to `Driver` unit.
`ControlEvent` already can't fail, so no special case needs to be handled.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func asDriver() -> Driver<E> {
return self.asDriver { (error) -> Driver<E> in
#if DEBUG
rxFatalError("Somehow driver received error from a source that shouldn't fail.")
#else
return Drive.empty()
#endif
}
}
}

View File

@ -0,0 +1,30 @@
//
// ControlProperty+Driver.swift
// Rx
//
// Created by Krunoslav Zaher on 9/19/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
extension ControlProperty {
/**
Converts `ControlProperty` to `Driver` unit.
`ControlProperty` already can't fail, so no special case needs to be handled.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func asDriver() -> Driver<E> {
return self.asDriver { (error) -> Driver<E> in
#if DEBUG
rxFatalError("Somehow driver received error from a source that shouldn't fail.")
#else
return Drive.empty()
#endif
}
}
}

View File

@ -0,0 +1,295 @@
// This file is autogenerated.
// Take a look at `Preprocessor` target in RxSwift project
//
// Driver+Operators+arity.swift
// Rx
//
// Created by Krunoslav Zaher on 10/14/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
// 2
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index.
- parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func zip<O1: DriverConvertibleType, O2: DriverConvertibleType, R>
(source1: O1, _ source2: O2, resultSelector: (O1.E, O2.E) throws -> R)
-> Driver<R> {
let source = zip(
source1.asDriver().asObservable(), source2.asDriver().asObservable(),
resultSelector: resultSelector
)
return Driver(source)
}
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func combineLatest<O1: DriverConvertibleType, O2: DriverConvertibleType, R>
(source1: O1, _ source2: O2, resultSelector: (O1.E, O2.E) throws -> R)
-> Driver<R> {
let source = combineLatest(
source1.asDriver().asObservable(), source2.asDriver().asObservable(),
resultSelector: resultSelector
)
return Driver(source)
}
// 3
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index.
- parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func zip<O1: DriverConvertibleType, O2: DriverConvertibleType, O3: DriverConvertibleType, R>
(source1: O1, _ source2: O2, _ source3: O3, resultSelector: (O1.E, O2.E, O3.E) throws -> R)
-> Driver<R> {
let source = zip(
source1.asDriver().asObservable(), source2.asDriver().asObservable(), source3.asDriver().asObservable(),
resultSelector: resultSelector
)
return Driver(source)
}
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func combineLatest<O1: DriverConvertibleType, O2: DriverConvertibleType, O3: DriverConvertibleType, R>
(source1: O1, _ source2: O2, _ source3: O3, resultSelector: (O1.E, O2.E, O3.E) throws -> R)
-> Driver<R> {
let source = combineLatest(
source1.asDriver().asObservable(), source2.asDriver().asObservable(), source3.asDriver().asObservable(),
resultSelector: resultSelector
)
return Driver(source)
}
// 4
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index.
- parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func zip<O1: DriverConvertibleType, O2: DriverConvertibleType, O3: DriverConvertibleType, O4: DriverConvertibleType, R>
(source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, resultSelector: (O1.E, O2.E, O3.E, O4.E) throws -> R)
-> Driver<R> {
let source = zip(
source1.asDriver().asObservable(), source2.asDriver().asObservable(), source3.asDriver().asObservable(), source4.asDriver().asObservable(),
resultSelector: resultSelector
)
return Driver(source)
}
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func combineLatest<O1: DriverConvertibleType, O2: DriverConvertibleType, O3: DriverConvertibleType, O4: DriverConvertibleType, R>
(source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, resultSelector: (O1.E, O2.E, O3.E, O4.E) throws -> R)
-> Driver<R> {
let source = combineLatest(
source1.asDriver().asObservable(), source2.asDriver().asObservable(), source3.asDriver().asObservable(), source4.asDriver().asObservable(),
resultSelector: resultSelector
)
return Driver(source)
}
// 5
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index.
- parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func zip<O1: DriverConvertibleType, O2: DriverConvertibleType, O3: DriverConvertibleType, O4: DriverConvertibleType, O5: DriverConvertibleType, R>
(source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, resultSelector: (O1.E, O2.E, O3.E, O4.E, O5.E) throws -> R)
-> Driver<R> {
let source = zip(
source1.asDriver().asObservable(), source2.asDriver().asObservable(), source3.asDriver().asObservable(), source4.asDriver().asObservable(), source5.asDriver().asObservable(),
resultSelector: resultSelector
)
return Driver(source)
}
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func combineLatest<O1: DriverConvertibleType, O2: DriverConvertibleType, O3: DriverConvertibleType, O4: DriverConvertibleType, O5: DriverConvertibleType, R>
(source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, resultSelector: (O1.E, O2.E, O3.E, O4.E, O5.E) throws -> R)
-> Driver<R> {
let source = combineLatest(
source1.asDriver().asObservable(), source2.asDriver().asObservable(), source3.asDriver().asObservable(), source4.asDriver().asObservable(), source5.asDriver().asObservable(),
resultSelector: resultSelector
)
return Driver(source)
}
// 6
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index.
- parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func zip<O1: DriverConvertibleType, O2: DriverConvertibleType, O3: DriverConvertibleType, O4: DriverConvertibleType, O5: DriverConvertibleType, O6: DriverConvertibleType, R>
(source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, resultSelector: (O1.E, O2.E, O3.E, O4.E, O5.E, O6.E) throws -> R)
-> Driver<R> {
let source = zip(
source1.asDriver().asObservable(), source2.asDriver().asObservable(), source3.asDriver().asObservable(), source4.asDriver().asObservable(), source5.asDriver().asObservable(), source6.asDriver().asObservable(),
resultSelector: resultSelector
)
return Driver(source)
}
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func combineLatest<O1: DriverConvertibleType, O2: DriverConvertibleType, O3: DriverConvertibleType, O4: DriverConvertibleType, O5: DriverConvertibleType, O6: DriverConvertibleType, R>
(source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, resultSelector: (O1.E, O2.E, O3.E, O4.E, O5.E, O6.E) throws -> R)
-> Driver<R> {
let source = combineLatest(
source1.asDriver().asObservable(), source2.asDriver().asObservable(), source3.asDriver().asObservable(), source4.asDriver().asObservable(), source5.asDriver().asObservable(), source6.asDriver().asObservable(),
resultSelector: resultSelector
)
return Driver(source)
}
// 7
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index.
- parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func zip<O1: DriverConvertibleType, O2: DriverConvertibleType, O3: DriverConvertibleType, O4: DriverConvertibleType, O5: DriverConvertibleType, O6: DriverConvertibleType, O7: DriverConvertibleType, R>
(source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, resultSelector: (O1.E, O2.E, O3.E, O4.E, O5.E, O6.E, O7.E) throws -> R)
-> Driver<R> {
let source = zip(
source1.asDriver().asObservable(), source2.asDriver().asObservable(), source3.asDriver().asObservable(), source4.asDriver().asObservable(), source5.asDriver().asObservable(), source6.asDriver().asObservable(), source7.asDriver().asObservable(),
resultSelector: resultSelector
)
return Driver(source)
}
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func combineLatest<O1: DriverConvertibleType, O2: DriverConvertibleType, O3: DriverConvertibleType, O4: DriverConvertibleType, O5: DriverConvertibleType, O6: DriverConvertibleType, O7: DriverConvertibleType, R>
(source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, resultSelector: (O1.E, O2.E, O3.E, O4.E, O5.E, O6.E, O7.E) throws -> R)
-> Driver<R> {
let source = combineLatest(
source1.asDriver().asObservable(), source2.asDriver().asObservable(), source3.asDriver().asObservable(), source4.asDriver().asObservable(), source5.asDriver().asObservable(), source6.asDriver().asObservable(), source7.asDriver().asObservable(),
resultSelector: resultSelector
)
return Driver(source)
}
// 8
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index.
- parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func zip<O1: DriverConvertibleType, O2: DriverConvertibleType, O3: DriverConvertibleType, O4: DriverConvertibleType, O5: DriverConvertibleType, O6: DriverConvertibleType, O7: DriverConvertibleType, O8: DriverConvertibleType, R>
(source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, _ source8: O8, resultSelector: (O1.E, O2.E, O3.E, O4.E, O5.E, O6.E, O7.E, O8.E) throws -> R)
-> Driver<R> {
let source = zip(
source1.asDriver().asObservable(), source2.asDriver().asObservable(), source3.asDriver().asObservable(), source4.asDriver().asObservable(), source5.asDriver().asObservable(), source6.asDriver().asObservable(), source7.asDriver().asObservable(), source8.asDriver().asObservable(),
resultSelector: resultSelector
)
return Driver(source)
}
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func combineLatest<O1: DriverConvertibleType, O2: DriverConvertibleType, O3: DriverConvertibleType, O4: DriverConvertibleType, O5: DriverConvertibleType, O6: DriverConvertibleType, O7: DriverConvertibleType, O8: DriverConvertibleType, R>
(source1: O1, _ source2: O2, _ source3: O3, _ source4: O4, _ source5: O5, _ source6: O6, _ source7: O7, _ source8: O8, resultSelector: (O1.E, O2.E, O3.E, O4.E, O5.E, O6.E, O7.E, O8.E) throws -> R)
-> Driver<R> {
let source = combineLatest(
source1.asDriver().asObservable(), source2.asDriver().asObservable(), source3.asDriver().asObservable(), source4.asDriver().asObservable(), source5.asDriver().asObservable(), source6.asDriver().asObservable(), source7.asDriver().asObservable(), source8.asDriver().asObservable(),
resultSelector: resultSelector
)
return Driver(source)
}

View File

@ -0,0 +1,54 @@
//
// Driver+Operators+arity.swift
// Rx
//
// Created by Krunoslav Zaher on 10/14/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
<% for i in 2 ... 8 { %>
// <%= i %>
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index.
- parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func zip<<%= (Array(1...i).map { "O\($0): DriverConvertibleType" }).joinWithSeparator(", ") %>, R>
(<%= (Array(1...i).map { "source\($0): O\($0)" }).joinWithSeparator(", _ ") %>, resultSelector: (<%= (Array(1...i).map { "O\($0).E" }).joinWithSeparator(", ") %>) throws -> R)
-> Driver<R> {
let source = zip(
<%= (Array(1...i).map { "source\($0).asDriver().asObservable()" }).joinWithSeparator(", ") %>,
resultSelector: resultSelector
)
return Driver(source)
}
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func combineLatest<<%= (Array(1...i).map { "O\($0): DriverConvertibleType" }).joinWithSeparator(", ") %>, R>
(<%= (Array(1...i).map { "source\($0): O\($0)" }).joinWithSeparator(", _ ") %>, resultSelector: (<%= (Array(1...i).map { "O\($0).E" }).joinWithSeparator(", ") %>) throws -> R)
-> Driver<R> {
let source = combineLatest(
<%= (Array(1...i).map { "source\($0).asDriver().asObservable()" }).joinWithSeparator(", ") %>,
resultSelector: resultSelector
)
return Driver(source)
}
<% } %>

View File

@ -0,0 +1,381 @@
//
// Driver+Operators.swift
// Rx
//
// Created by Krunoslav Zaher on 9/19/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
extension DriverConvertibleType {
/**
Projects each element of an observable sequence into a new form.
- parameter selector: A transform function to apply to each source element.
- returns: An observable sequence whose elements are the result of invoking the transform function on each element of source.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func map<R>(selector: E -> R) -> Driver<R> {
let source = self
.asObservable()
.map(selector)
return Driver<R>(source)
}
/**
Filters the elements of an observable sequence based on a predicate.
- parameter predicate: A function to test each source element for a condition.
- returns: An observable sequence that contains elements from the input sequence that satisfy the condition.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func filter(predicate: (E) -> Bool) -> Driver<E> {
let source = self
.asObservable()
.filter(predicate)
return Driver(source)
}
}
extension DriverConvertibleType where E : DriverConvertibleType {
/**
Transforms an observable sequence of observable sequences into an observable sequence
producing values only from the most recent observable sequence.
Each time a new inner observable sequence is received, unsubscribe from the
previous inner observable sequence.
- returns: The observable sequence that at any point in time produces the elements of the most recent inner observable sequence that has been received.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func switchLatest() -> Driver<E.E> {
let source: Observable<E.E> = self
.asObservable()
.map { $0.asDriver() }
.switchLatest()
return Driver<E.E>(source)
}
}
extension DriverConvertibleType {
/**
Projects each element of an observable sequence into a new sequence of observable sequences and then
transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence.
It is a combination of `map` + `switchLatest` operator
- parameter selector: A transform function to apply to each element.
- returns: An observable sequence whose elements are the result of invoking the transform function on each element of source producing an
Observable of Observable sequences and that at any point in time produces the elements of the most recent inner observable sequence that has been received.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func flatMapLatest<R>(selector: (E) -> Driver<R>)
-> Driver<R> {
let source: Observable<R> = self
.asObservable()
.flatMapLatest(selector)
return Driver<R>(source)
}
}
extension DriverConvertibleType {
/**
Invokes an action for each event in the observable sequence, and propagates all observer messages through the result sequence.
- parameter eventHandler: Action to invoke for each event in the observable sequence.
- returns: The source sequence with the side-effecting behavior applied.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func doOn(eventHandler: (Event<E>) -> Void)
-> Driver<E> {
let source = self.asObservable()
.doOn(eventHandler)
return Driver(source)
}
/**
Invokes an action for each event in the observable sequence, and propagates all observer messages through the result sequence.
- parameter onNext: Action to invoke for each element in the observable sequence.
- parameter onError: Action to invoke upon errored termination of the observable sequence.
- parameter onCompleted: Action to invoke upon graceful termination of the observable sequence.
- returns: The source sequence with the side-effecting behavior applied.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func doOn(onNext onNext: (E -> Void)? = nil, onError: (ErrorType -> Void)? = nil, onCompleted: (() -> Void)? = nil)
-> Driver<E> {
let source = self.asObservable()
.doOn(onNext: onNext, onError: onError, onCompleted: onCompleted)
return Driver(source)
}
}
extension DriverConvertibleType {
/**
Prints received events for all observers on standard output.
- parameter identifier: Identifier that is printed together with event description to standard output.
- returns: An observable sequence whose events are printed to standard output.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func debug(identifier: String = "\(__FILE__):\(__LINE__)") -> Driver<E> {
let source = self.asObservable()
.debug(identifier)
return Driver(source)
}
}
extension DriverConvertibleType where E: Equatable {
/**
Returns an observable sequence that contains only distinct contiguous elements according to equality operator.
- returns: An observable sequence only containing the distinct contiguous elements, based on equality operator, from the source sequence.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func distinctUntilChanged()
-> Driver<E> {
let source = self.asObservable()
.distinctUntilChanged({ $0 }, comparer: { ($0 == $1) })
return Driver(source)
}
}
extension DriverConvertibleType {
/**
Returns an observable sequence that contains only distinct contiguous elements according to the `keySelector`.
- parameter keySelector: A function to compute the comparison key for each element.
- returns: An observable sequence only containing the distinct contiguous elements, based on a computed key value, from the source sequence.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func distinctUntilChanged<K: Equatable>(keySelector: (E) -> K) -> Driver<E> {
let source = self.asObservable()
.distinctUntilChanged(keySelector, comparer: { $0 == $1 })
return Driver(source)
}
/**
Returns an observable sequence that contains only distinct contiguous elements according to the `comparer`.
- parameter comparer: Equality comparer for computed key values.
- returns: An observable sequence only containing the distinct contiguous elements, based on `comparer`, from the source sequence.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func distinctUntilChanged(comparer: (lhs: E, rhs: E) -> Bool) -> Driver<E> {
let source = self.asObservable()
.distinctUntilChanged({ $0 }, comparer: comparer)
return Driver(source)
}
/**
Returns an observable sequence that contains only distinct contiguous elements according to the keySelector and the comparer.
- parameter keySelector: A function to compute the comparison key for each element.
- parameter comparer: Equality comparer for computed key values.
- returns: An observable sequence only containing the distinct contiguous elements, based on a computed key value and the comparer, from the source sequence.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func distinctUntilChanged<K>(keySelector: (E) -> K, comparer: (lhs: K, rhs: K) -> Bool) -> Driver<E> {
let source = self.asObservable()
.distinctUntilChanged(keySelector, comparer: comparer)
return Driver(source)
}
}
extension DriverConvertibleType {
/**
Projects each element of an observable sequence to an observable sequence and merges the resulting observable sequences into one observable sequence.
- parameter selector: A transform function to apply to each element.
- returns: An observable sequence whose elements are the result of invoking the one-to-many transform function on each element of the input sequence.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func flatMap<R>(selector: (E) -> Driver<R>) -> Driver<R> {
let source = self.asObservable()
.flatMap(selector)
return Driver<R>(source)
}
}
// merge
extension DriverConvertibleType where E : DriverConvertibleType {
/**
Merges elements from all observable sequences in the given enumerable sequence into a single observable sequence.
- parameter maxConcurrent: Maximum number of inner observable sequences being subscribed to concurrently.
- returns: The observable sequence that merges the elements of the observable sequences.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func merge() -> Driver<E.E> {
let source = self.asObservable()
.map { $0.asDriver() }
.merge()
return Driver<E.E>(source)
}
/**
Merges elements from all inner observable sequences into a single observable sequence, limiting the number of concurrent subscriptions to inner sequences.
- returns: The observable sequence that merges the elements of the inner sequences.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func merge(maxConcurrent maxConcurrent: Int)
-> Driver<E.E> {
let source = self.asObservable()
.map { $0.asDriver() }
.merge(maxConcurrent: maxConcurrent)
return Driver<E.E>(source)
}
}
// throttle
extension DriverConvertibleType {
/**
Ignores elements from an observable sequence which are followed by another element within a specified relative time duration, using the specified scheduler to run throttling timers.
`throttle` and `debounce` are synonyms.
- parameter dueTime: Throttling duration for each element.
- parameter scheduler: Scheduler to run the throttle timers and send events on.
- returns: The throttled sequence.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func throttle<S: SchedulerType>(dueTime: S.TimeInterval, _ scheduler: S)
-> Driver<E> {
let source = self.asObservable()
.throttle(dueTime, scheduler)
return Driver(source)
}
/**
Ignores elements from an observable sequence which are followed by another element within a specified relative time duration, using the specified scheduler to run throttling timers.
`throttle` and `debounce` are synonyms.
- parameter dueTime: Throttling duration for each element.
- parameter scheduler: Scheduler to run the throttle timers and send events on.
- returns: The throttled sequence.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func debounce<S: SchedulerType>(dueTime: S.TimeInterval, _ scheduler: S)
-> Driver<E> {
let source = self.asObservable()
.debounce(dueTime, scheduler)
return Driver(source)
}
}
// scan
extension DriverConvertibleType {
/**
Applies an accumulator function over an observable sequence and returns each intermediate result. The specified seed value is used as the initial accumulator value.
For aggregation behavior with no intermediate results, see `reduce`.
- parameter seed: The initial accumulator value.
- parameter accumulator: An accumulator function to be invoked on each element.
- returns: An observable sequence containing the accumulated values.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func scan<A>(seed: A, accumulator: (A, E) -> A)
-> Driver<A> {
let source = self.asObservable()
.scan(seed, accumulator: accumulator)
return Driver<A>(source)
}
}
extension SequenceType where Generator.Element : DriverConvertibleType {
/**
Concatenates all observable sequences in the given sequence, as long as the previous observable sequence terminated successfully.
- returns: An observable sequence that contains the elements of each given sequence, in sequential order.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func concat()
-> Driver<Generator.Element.E> {
let source: Observable<Generator.Element.E> = self.lazy.map { $0.asDriver() }.concat()
return Driver<Generator.Element.E>(source)
}
}
extension CollectionType where Generator.Element : DriverConvertibleType {
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index.
- parameter resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func zip<R>(resultSelector: [Generator.Element.E] throws -> R) -> Driver<R> {
let source: Observable<R> = self.map { $0.asDriver() }.zip(resultSelector)
return Driver<R>(source)
}
}
extension CollectionType where Generator.Element : DriverConvertibleType {
/**
Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
- parameter resultSelector: Function to invoke whenever any of the sources produces an element.
- returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func combineLatest<R>(resultSelector: [Generator.Element.E] throws -> R) -> Driver<R> {
let source : Observable<R> = self.map { $0.asDriver() }.combineLatest(resultSelector)
return Driver<R>(source)
}
}
extension DriverConvertibleType {
/**
Merges two observable sequences into one observable sequence by combining each element from self with the latest element from the second source, if any.
- parameter second: Second observable source.
- parameter resultSelector: Function to invoke for each element from the self combined with the latest element from the second source, if any.
- returns: An observable sequence containing the result of combining each element of the self with the latest element from the second source, if any, using the specified result selector function.
*/
public func withLatestFrom<SecondO: DriverConvertibleType, ResultType>(second: SecondO, resultSelector: (E, SecondO.E) -> ResultType) -> Driver<ResultType> {
let source = self.asObservable()
.withLatestFrom(second.asDriver(), resultSelector: resultSelector)
return Driver<ResultType>(source)
}
/**
Merges two observable sequences into one observable sequence by using latest element from the second sequence every time when `self` emitts an element.
- parameter second: Second observable source.
- returns: An observable sequence containing the result of combining each element of the self with the latest element from the second source, if any, using the specified result selector function.
*/
public func withLatestFrom<SecondO: DriverConvertibleType>(second: SecondO) -> Driver<SecondO.E> {
let source = self.asObservable()
.withLatestFrom(second.asDriver())
return Driver<SecondO.E>(source)
}
}

View File

@ -0,0 +1,85 @@
//
// Driver+Extensions.swift
// Rx
//
// Created by Krunoslav Zaher on 9/19/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
extension DriverConvertibleType {
/**
Creates new subscription and sends elements to observer.
In this form it's equivalent to `subscribe` method, but it communicates intent better.
- parameter observer: Observer that receives events.
- returns: Disposable object that can be used to unsubscribe the observer from the subject.
*/
@warn_unused_result(message="http://git.io/rxs.ud")
public func drive<O: ObserverType where O.E == E>(observer: O) -> Disposable {
return self.asObservable().subscribe(observer)
}
/**
Subscribes to observable sequence using custom binder function.
- parameter with: Function used to bind elements from `self`.
- returns: Object representing subscription.
*/
@warn_unused_result(message="http://git.io/rxs.ud")
public func drive<R>(transformation: Observable<E> -> R) -> R {
return transformation(self.asObservable())
}
/**
Subscribes to observable sequence using custom binder function and final parameter passed to binder function
after `self` is passed.
public func drive<R1, R2>(with: Self -> R1 -> R2, curriedArgument: R1) -> R2 {
return with(self)(curriedArgument)
}
- parameter with: Function used to bind elements from `self`.
- parameter curriedArgument: Final argument passed to `binder` to finish binding process.
- returns: Object representing subscription.
*/
@warn_unused_result(message="http://git.io/rxs.ud")
public func drive<R1, R2>(with: Observable<E> -> R1 -> R2, curriedArgument: R1) -> R2 {
return with(self.asObservable())(curriedArgument)
}
/**
Subscribes an element handler, a completion handler and disposed handler to an observable sequence.
Error callback is not exposed because `Driver` can't error out.
- parameter onNext: Action to invoke for each element in the observable sequence.
- parameter onCompleted: Action to invoke upon graceful termination of the observable sequence.
gracefully completed, errored, or if the generation is cancelled by disposing subscription)
- parameter onDisposed: Action to invoke upon any type of termination of sequence (if the sequence has
gracefully completed, errored, or if the generation is cancelled by disposing subscription)
- returns: Subscription object used to unsubscribe from the observable sequence.
*/
@warn_unused_result(message="http://git.io/rxs.ud")
public func drive(onNext onNext: ((E) -> Void)? = nil, onCompleted: (() -> Void)? = nil, onDisposed: (() -> Void)? = nil) -> Disposable {
return self.asObservable().subscribe(onNext: onNext, onCompleted: onCompleted, onDisposed: onDisposed)
}
/**
Subscribes an element handler to an observable sequence.
- parameter onNext: Action to invoke for each element in the observable sequence.
- returns: Subscription object used to unsubscribe from the observable sequence.
*/
@warn_unused_result(message="http://git.io/rxs.ud")
public func driveNext(onNext: E -> Void) -> Disposable {
return self.asObservable().subscribeNext(onNext)
}
}

View File

@ -0,0 +1,178 @@
//
// Driver.swift
// Rx
//
// Created by Krunoslav Zaher on 8/27/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
/**
A type that can be converted to `Driver`.
*/
public protocol DriverConvertibleType : ObservableConvertibleType {
/**
Converts self to `Driver`.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
func asDriver() -> Driver<E>
}
extension DriverConvertibleType {
@warn_unused_result(message="http://git.io/rxs.uo")
public func asObservable() -> Observable<E> {
return asDriver().asObservable()
}
}
/**
Unit that represents observable sequence with following properties:
- it never fails
- it delivers events on `MainScheduler.sharedInstance`
- `shareReplay(1)` behavior
- all observers share sequence computation resources
- it's stateful, upon subscription (calling subscribe) last element is immediatelly replayed if it was produced
- computation of elements is reference counted with respect to the number of observers
- if there are no subscribers, it will release sequence computation resources
`Driver<Element>` can be considered a builder pattern for observable sequences that drive the application.
To find out more about units and how to use them, please go to `Documentation/Units.md`.
*/
public struct Driver<Element> : DriverConvertibleType {
public typealias E = Element
let _source: Observable<E>
init(_ source: Observable<E>) {
self._source = source.shareReplay(1)
}
init(raw: Observable<E>) {
self._source = raw
}
#if EXPANDABLE_DRIVER
public static func createUnsafe<O: ObservableType>(source: O) -> Driver<O.E> {
return Driver<O.E>(raw: source.asObservable())
}
#endif
/**
- returns: Built observable sequence.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func asObservable() -> Observable<E> {
return _source
}
/**
- returns: `self`
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func asDriver() -> Driver<E> {
return self
}
}
public struct Drive {
#if !RX_NO_MODULE
/**
Returns an empty observable sequence, using the specified scheduler to send out the single `Completed` message.
- returns: An observable sequence with no elements.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public static func empty<E>() -> Driver<E> {
return Driver(raw: RxSwift.empty().subscribeOn(ConcurrentMainScheduler.sharedInstance))
}
/**
Returns a non-terminating observable sequence, which can be used to denote an infinite duration.
- returns: An observable sequence whose observers will never get called.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public static func never<E>() -> Driver<E> {
return Driver(raw: RxSwift.never().subscribeOn(ConcurrentMainScheduler.sharedInstance))
}
/**
Returns an observable sequence that contains a single element.
- parameter element: Single element in the resulting observable sequence.
- returns: An observable sequence containing the single specified element.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public static func just<E>(element: E) -> Driver<E> {
return Driver(raw: RxSwift.just(element).subscribeOn(ConcurrentMainScheduler.sharedInstance))
}
#else
/**
Returns an empty observable sequence, using the specified scheduler to send out the single `Completed` message.
- returns: An observable sequence with no elements.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public static func empty<E>() -> Driver<E> {
return Driver(raw: _empty().subscribeOn(ConcurrentMainScheduler.sharedInstance))
}
/**
Returns a non-terminating observable sequence, which can be used to denote an infinite duration.
- returns: An observable sequence whose observers will never get called.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public static func never<E>() -> Driver<E> {
return Driver(raw: _never().subscribeOn(ConcurrentMainScheduler.sharedInstance))
}
/**
Returns an observable sequence that contains a single element.
- parameter element: Single element in the resulting observable sequence.
- returns: An observable sequence containing the single specified element.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public static func just<E>(element: E) -> Driver<E> {
return Driver(raw: _just(element).subscribeOn(ConcurrentMainScheduler.sharedInstance))
}
#endif
@warn_unused_result(message="http://git.io/rxs.uo")
public static func sequenceOf<E>(elements: E ...) -> Driver<E> {
let source = elements.toObservable().subscribeOn(ConcurrentMainScheduler.sharedInstance)
return Driver(raw: source)
}
}
// name clashes :(
#if RX_NO_MODULE
func _empty<E>() -> Observable<E> {
return empty()
}
func _never<E>() -> Observable<E> {
return never()
}
func _just<E>(element: E) -> Observable<E> {
return just(element)
}
#endif

View File

@ -0,0 +1,63 @@
//
// ObservableConvertibleType+Driver.swift
// Rx
//
// Created by Krunoslav Zaher on 9/19/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
extension ObservableConvertibleType {
/**
Converts anything convertible to `Observable` to `Driver` unit.
- parameter onErrorJustReturn: Element to return in case of error and after that complete the sequence.
- returns: Driving observable sequence.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func asDriver(onErrorJustReturn onErrorJustReturn: E) -> Driver<E> {
let source = self
.asObservable()
.observeOn(MainScheduler.sharedInstance)
.catchErrorJustReturn(onErrorJustReturn)
return Driver(source)
}
/**
Converts anything convertible to `Observable` to `Driver` unit.
- parameter onErrorDriveWith: Driver that continues to drive the sequence in case of error.
- returns: Driving observable sequence.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func asDriver(onErrorDriveWith onErrorDriveWith: Driver<E>) -> Driver<E> {
let source = self
.asObservable()
.observeOn(MainScheduler.sharedInstance)
.catchError { _ in
onErrorDriveWith.asObservable()
}
return Driver(source)
}
/**
Converts anything convertible to `Observable` to `Driver` unit.
- parameter onErrorRecover: Calculates driver that continues to drive the sequence in case of error.
- returns: Driving observable sequence.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func asDriver(onErrorRecover onErrorRecover: (error: ErrorType) -> Driver<E>) -> Driver<E> {
let source = self
.asObservable()
.observeOn(MainScheduler.sharedInstance)
.catchError { error in
onErrorRecover(error: error).asObservable()
}
return Driver(source)
}
}

View File

@ -23,7 +23,7 @@ public class DelegateProxy : _RXDelegateProxy {
private var subjectsForSelector = [Selector: PublishSubject<[AnyObject]>]()
unowned let parentObject: AnyObject
weak var parentObject: AnyObject?
/**
Initializes new instance.

View File

@ -160,7 +160,7 @@ Returns existing proxy for object or installs new instance of delegate proxy.
return proxyForObject(self) as RxSearchBarDelegateProxy
}
public var rx_searchText: ControlProperty<String> {
public var rx_text: ControlProperty<String> {
let source: Observable<String> = self.rx_delegate.observe("searchBar:textDidChange:")
...
}
@ -224,25 +224,28 @@ extension ObservableType {
let proxy: P = proxyForObject(object)
let disposable = installDelegate(proxy, delegate: dataSource, retainDelegate: retainDataSource, onProxyForObject: object)
// we should never let the subscriber to complete because it should retain data source
let source = sequenceOf(self.asObservable(), never()) as Observable<Observable<E>>
let subscription = source.concat().subscribe { (event: Event<E>) in
MainScheduler.ensureExecutingOnScheduler()
assert(proxy === P.currentDelegateFor(object), "Proxy changed from the time it was first set.\nOriginal: \(proxy)\nExisting: \(P.currentDelegateFor(object))")
binding(proxy, event)
switch event {
case .Error(let error):
bindingErrorToInterface(error)
disposable.dispose()
case .Completed:
disposable.dispose()
default:
break
let subscription = self.asObservable()
// source can't ever end, otherwise it will release the subscriber
.concat(never())
.subscribe { [weak object] (event: Event<E>) in
MainScheduler.ensureExecutingOnScheduler()
if let object = object {
assert(proxy === P.currentDelegateFor(object), "Proxy changed from the time it was first set.\nOriginal: \(proxy)\nExisting: \(P.currentDelegateFor(object))")
}
binding(proxy, event)
switch event {
case .Error(let error):
bindingErrorToInterface(error)
disposable.dispose()
case .Completed:
disposable.dispose()
default:
break
}
}
}
return StableCompositeDisposable.create(subscription, disposable)
}

View File

@ -0,0 +1,71 @@
//
// KVORepresentable+CoreGraphics.swift
// Rx
//
// Created by Krunoslav Zaher on 11/14/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
import CoreGraphics
#if arch(x86_64) || arch(arm64)
let CGRectType = "{CGRect={CGPoint=dd}{CGSize=dd}}"
let CGSizeType = "{CGSize=dd}"
let CGPointType = "{CGPoint=dd}"
#elseif arch(i386) || arch(arm)
let CGRectType = "{CGRect={CGPoint=ff}{CGSize=ff}}"
let CGSizeType = "{CGSize=ff}"
let CGPointType = "{CGPoint=ff}"
#endif
extension CGRect : KVORepresentable {
public typealias KVOType = NSValue
/**
Constructs self from `NSValue`.
*/
public init?(KVOValue: KVOType) {
if strcmp(KVOValue.objCType, CGRectType) != 0 {
return nil
}
var typedValue = CGRect(x: 0, y: 0, width: 0, height: 0)
KVOValue.getValue(&typedValue)
self = typedValue
}
}
extension CGPoint : KVORepresentable {
public typealias KVOType = NSValue
/**
Constructs self from `NSValue`.
*/
public init?(KVOValue: KVOType) {
if strcmp(KVOValue.objCType, CGPointType) != 0 {
return nil
}
var typedValue = CGPoint(x: 0, y: 0)
KVOValue.getValue(&typedValue)
self = typedValue
}
}
extension CGSize : KVORepresentable {
public typealias KVOType = NSValue
/**
Constructs self from `NSValue`.
*/
public init?(KVOValue: KVOType) {
if strcmp(KVOValue.objCType, CGSizeType) != 0 {
return nil
}
var typedValue = CGSize(width: 0, height: 0)
KVOValue.getValue(&typedValue)
self = typedValue
}
}

View File

@ -0,0 +1,93 @@
//
// KVORepresentable+Swift.swift
// Rx
//
// Created by Krunoslav Zaher on 11/14/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
extension Int : KVORepresentable {
public typealias KVOType = NSNumber
/**
Constructs `Self` using KVO value.
*/
public init?(KVOValue: KVOType) {
self.init(KVOValue.integerValue)
}
}
extension Int32 : KVORepresentable {
public typealias KVOType = NSNumber
/**
Constructs `Self` using KVO value.
*/
public init?(KVOValue: KVOType) {
self.init(KVOValue.intValue)
}
}
extension Int64 : KVORepresentable {
public typealias KVOType = NSNumber
/**
Constructs `Self` using KVO value.
*/
public init?(KVOValue: KVOType) {
self.init(KVOValue.longLongValue)
}
}
extension UInt : KVORepresentable {
public typealias KVOType = NSNumber
/**
Constructs `Self` using KVO value.
*/
public init?(KVOValue: KVOType) {
self.init(KVOValue.unsignedLongValue)
}
}
extension UInt32 : KVORepresentable {
public typealias KVOType = NSNumber
/**
Constructs `Self` using KVO value.
*/
public init?(KVOValue: KVOType) {
self.init(KVOValue.unsignedIntValue)
}
}
extension UInt64 : KVORepresentable {
public typealias KVOType = NSNumber
/**
Constructs `Self` using KVO value.
*/
public init?(KVOValue: KVOType) {
self.init(KVOValue.unsignedLongLongValue)
}
}
extension RawRepresentable where RawValue: KVORepresentable {
/**
Constructs `Self` using optional KVO value.
*/
init?(KVOValue: RawValue.KVOType?) {
guard let KVOValue = KVOValue else {
return nil
}
guard let rawValue = RawValue(KVOValue: KVOValue) else {
return nil
}
self.init(rawValue: rawValue)
}
}

View File

@ -0,0 +1,35 @@
//
// KVORepresentable.swift
// Rx
//
// Created by Krunoslav Zaher on 11/14/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
/**
Type that is KVO representable (KVO mechanism can be used to observe it).
*/
public protocol KVORepresentable {
/**
Associated KVO type.
*/
typealias KVOType
/**
Constructs `Self` using KVO value.
*/
init?(KVOValue: KVOType)
}
extension KVORepresentable {
init?(KVOValue: KVOType?) {
guard let KVOValue = KVOValue else {
return nil
}
self.init(KVOValue: KVOValue)
}
}

View File

@ -1,5 +1,5 @@
//
// Observable+Extensions.swift
// Observable+Bind.swift
// Rx
//
// Created by Krunoslav Zaher on 8/29/15.
@ -21,6 +21,7 @@ extension ObservableType {
- parameter observer: Observer that receives events.
- returns: Disposable object that can be used to unsubscribe the observer from the subject.
*/
@warn_unused_result(message="http://git.io/rxs.ud")
public func bindTo<O: ObserverType where O.E == E>(observer: O) -> Disposable {
return self.subscribe(observer)
}
@ -31,6 +32,7 @@ extension ObservableType {
- parameter binder: Function used to bind elements from `self`.
- returns: Object representing subscription.
*/
@warn_unused_result(message="http://git.io/rxs.ud")
public func bindTo<R>(binder: Self -> R) -> R {
return binder(self)
}
@ -47,8 +49,20 @@ extension ObservableType {
- parameter curriedArgument: Final argument passed to `binder` to finish binding process.
- returns: Object representing subscription.
*/
@warn_unused_result(message="http://git.io/rxs.ud")
public func bindTo<R1, R2>(binder: Self -> R1 -> R2, curriedArgument: R1) -> R2 {
return binder(self)(curriedArgument)
}
/**
Subscribes an element handler to an observable sequence.
- parameter onNext: Action to invoke for each element in the observable sequence.
- returns: Subscription object used to unsubscribe from the observable sequence.
*/
@warn_unused_result(message="http://git.io/rxs.ud")
public func bindNext(onNext: E -> Void) -> Disposable {
return subscribeNext(onNext)
}
}

View File

@ -30,7 +30,7 @@ class ControlTarget: RxTarget {
let selector: Selector = "eventHandler:"
unowned let control: Control
weak var control: Control?
#if os(iOS) || os(tvOS)
let controlEvents: UIControlEvents
#endif
@ -72,7 +72,7 @@ class ControlTarget: RxTarget {
#endif
func eventHandler(sender: Control!) {
if let callback = self.callback {
if let callback = self.callback, control = self.control {
callback(control)
}
}
@ -80,10 +80,10 @@ class ControlTarget: RxTarget {
override func dispose() {
super.dispose()
#if os(iOS) || os(tvOS)
self.control.removeTarget(self, action: self.selector, forControlEvents: self.controlEvents)
self.control?.removeTarget(self, action: self.selector, forControlEvents: self.controlEvents)
#elseif os(OSX)
self.control.target = nil
self.control.action = nil
self.control?.target = nil
self.control?.action = nil
#endif
self.callback = nil
}

View File

@ -11,8 +11,11 @@ import Foundation
import RxSwift
#endif
class KVOObservable<Element> : _Producer<Element?>
, KVOObservableProtocol {
class KVOObservable<Element>
: ObservableType
, KVOObservableProtocol {
typealias E = Element?
unowned var target: AnyObject
var strongTarget: AnyObject?
@ -30,7 +33,7 @@ class KVOObservable<Element> : _Producer<Element?>
}
}
override func run<O : ObserverType where O.E == Element?>(observer: O, cancel: Disposable, setSink: (Disposable) -> Void) -> Disposable {
func subscribe<O : ObserverType where O.E == Element?>(observer: O) -> Disposable {
let observer = KVOObserver(parent: self) { (value) in
if value as? NSNull != nil {
observer.on(.Next(nil))
@ -99,7 +102,7 @@ func observeWeaklyKeyPathFor(
let property = class_getProperty(object_getClass(target), propertyName);
if property == nil {
return failWith(rxError(.KeyPathInvalid, "Object \(target) doesn't have property named `\(propertyName)`"))
return failWith(RxCocoaError.InvalidPropertyName(object: target, propertyName: propertyName))
}
let propertyAttributes = property_getAttributes(property);
@ -109,7 +112,7 @@ func observeWeaklyKeyPathFor(
// KVO recursion for value changes
return propertyObservable
.map { (nextTarget: AnyObject?) -> Observable<AnyObject?> in
.flatMapLatest { (nextTarget: AnyObject?) -> Observable<AnyObject?> in
if nextTarget == nil {
return just(nil)
}
@ -118,7 +121,7 @@ func observeWeaklyKeyPathFor(
let strongTarget: AnyObject? = weakTarget
if nextObject == nil {
return failWith(rxError(.KeyPathInvalid, "Observed \(nextTarget) as property `\(propertyName)` on `\(strongTarget)` which is not `NSObject`."))
return failWith(RxCocoaError.InvalidObjectOnKeyPath(object: nextTarget!, sourceObject: strongTarget ?? NSNull(), propertyName: propertyName))
}
// if target is alive, then send change
@ -139,7 +142,6 @@ func observeWeaklyKeyPathFor(
return nextElementsObservable
}
}
.switchLatest()
}
#endif

View File

@ -19,6 +19,7 @@ extension NSNotificationCenter {
- parameter object: Optional object used to filter notifications.
- returns: Observable sequence of posted notifications.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func rx_notification(name: String, object: AnyObject? = nil) -> Observable<NSNotification> {
return create { observer in
let nsObserver = self.addObserverForName(name, object: object, queue: nil) { notification in

View File

@ -12,38 +12,19 @@ import RxSwift
#endif
import CoreGraphics
#if arch(x86_64) || arch(arm64)
let CGRectType = "{CGRect={CGPoint=dd}{CGSize=dd}}"
let CGSizeType = "{CGSize=dd}"
let CGPointType = "{CGPoint=dd}"
#elseif arch(i386) || arch(arm)
let CGRectType = "{CGRect={CGPoint=ff}{CGSize=ff}}"
let CGSizeType = "{CGSize=ff}"
let CGPointType = "{CGPoint=ff}"
#endif
// MARK: Deprecated, CGPoint, CGRect, CGSize are now KVORepresentable
// rx_observe + CoreGraphics
extension NSObject {
/**
Specialization of generic `rx_observe` method.
For more information take a look at `rx_observe` method.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
@available(*, deprecated=2.0.0, message="Please use version that takes type as first argument.")
public func rx_observe(keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial], retainSelf: Bool = true) -> Observable<CGRect?> {
return rx_observe(keyPath, options: options, retainSelf: retainSelf)
.map { (value: NSValue?) in
if let value = value {
if strcmp(value.objCType, CGRectType) != 0 {
return nil
}
var typedValue = CGRect(x: 0, y: 0, width: 0, height: 0)
value.getValue(&typedValue)
return typedValue
}
else {
return nil
}
}
return rx_observe(NSValue.self, keyPath, options: options, retainSelf: retainSelf)
.map(CGRect.init)
}
/**
@ -51,21 +32,11 @@ extension NSObject {
For more information take a look at `rx_observe` method.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
@available(*, deprecated=2.0.0, message="Please use version that takes type as first argument.")
public func rx_observe(keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial], retainSelf: Bool = true) -> Observable<CGSize?> {
return rx_observe(keyPath, options: options, retainSelf: retainSelf)
.map { (value: NSValue?) in
if let value = value {
if strcmp(value.objCType, CGSizeType) != 0 {
return nil
}
var typedValue = CGSize(width: 0, height: 0)
value.getValue(&typedValue)
return typedValue
}
else {
return nil
}
}
return rx_observe(NSValue.self, keyPath, options: options, retainSelf: retainSelf)
.map(CGSize.init)
}
/**
@ -73,21 +44,11 @@ extension NSObject {
For more information take a look at `rx_observe` method.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
@available(*, deprecated=2.0.0, message="Please use version that takes type as first argument.")
public func rx_observe(keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial], retainSelf: Bool = true) -> Observable<CGPoint?> {
return rx_observe(keyPath, options: options, retainSelf: retainSelf)
.map { (value: NSValue?) in
if let value = value {
if strcmp(value.objCType, CGPointType) != 0 {
return nil
}
var typedValue = CGPoint(x: 0, y: 0)
value.getValue(&typedValue)
return typedValue
}
else {
return nil
}
}
return rx_observe(NSValue.self, keyPath, options: options, retainSelf: retainSelf)
.map(CGPoint.init)
}
}
@ -101,21 +62,11 @@ extension NSObject {
For more information take a look at `rx_observeWeakly` method.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
@available(*, deprecated=2.0.0, message="Please use version that takes type as first argument.")
public func rx_observeWeakly(keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial]) -> Observable<CGRect?> {
return rx_observeWeakly(keyPath, options: options)
.map { (value: NSValue?) in
if let value = value {
if strcmp(value.objCType, CGRectType) != 0 {
return nil
}
var typedValue = CGRect(x: 0, y: 0, width: 0, height: 0)
value.getValue(&typedValue)
return typedValue
}
else {
return nil
}
}
return rx_observeWeakly(NSValue.self, keyPath, options: options)
.map(CGRect.init)
}
/**
@ -123,21 +74,11 @@ extension NSObject {
For more information take a look at `rx_observeWeakly` method.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
@available(*, deprecated=2.0.0, message="Please use version that takes type as first argument.")
public func rx_observeWeakly(keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial]) -> Observable<CGSize?> {
return rx_observeWeakly(keyPath, options: options)
.map { (value: NSValue?) in
if let value = value {
if strcmp(value.objCType, CGSizeType) != 0 {
return nil
}
var typedValue = CGSize(width: 0, height: 0)
value.getValue(&typedValue)
return typedValue
}
else {
return nil
}
}
return rx_observeWeakly(NSValue.self, keyPath, options: options)
.map(CGSize.init)
}
/**
@ -145,21 +86,11 @@ extension NSObject {
For more information take a look at `rx_observeWeakly` method.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
@available(*, deprecated=2.0.0, message="Please use version that takes type as first argument.")
public func rx_observeWeakly(keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial]) -> Observable<CGPoint?> {
return rx_observeWeakly(keyPath, options: options)
.map { (value: NSValue?) in
if let value = value {
if strcmp(value.objCType, CGPointType) != 0 {
return nil
}
var typedValue = CGPoint(x: 0, y: 0)
value.getValue(&typedValue)
return typedValue
}
else {
return nil
}
}
return rx_observeWeakly(NSValue.self, keyPath, options: options)
.map(CGPoint.init)
}
}

View File

@ -0,0 +1,45 @@
//
// NSObject+Rx+KVORepresentable.swift
// Rx
//
// Created by Krunoslav Zaher on 11/14/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
extension NSObject {
/**
Specialization of generic `rx_observe` method.
This is a special overload because to observe values of some type (for example `Int`), first values of KVO type
need to be observed (`NSNumber`), and then converted to result type.
For more information take a look at `rx_observe` method.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func rx_observe<E: KVORepresentable>(type: E.Type, _ keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial], retainSelf: Bool = true) -> Observable<E?> {
return rx_observe(E.KVOType.self, keyPath, options: options, retainSelf: retainSelf)
.map(E.init)
}
}
#if !DISABLE_SWIZZLING
// KVO
extension NSObject {
/**
Specialization of generic `rx_observeWeakly` method.
For more information take a look at `rx_observeWeakly` method.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func rx_observeWeakly<E: KVORepresentable>(type: E.Type, _ keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial]) -> Observable<E?> {
return rx_observeWeakly(E.KVOType.self, keyPath, options: options)
.map(E.init)
}
}
#endif

View File

@ -0,0 +1,51 @@
//
// NSObject+Rx+RawRepresentable.swift
// Rx
//
// Created by Krunoslav Zaher on 11/9/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
extension NSObject {
/**
Specialization of generic `rx_observe` method.
This specialization first observes `KVORepresentable` value and then converts it to `RawRepresentable` value.
It is useful for observing bridged ObjC enum values.
For more information take a look at `rx_observe` method.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func rx_observe<E: RawRepresentable where E.RawValue: KVORepresentable>(type: E.Type, _ keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial], retainSelf: Bool = true) -> Observable<E?> {
return rx_observe(E.RawValue.KVOType.self, keyPath, options: options, retainSelf: retainSelf)
.map(E.init)
}
}
#if !DISABLE_SWIZZLING
// rx_observeWeakly + RawRepresentable
extension NSObject {
/**
Specialization of generic `rx_observeWeakly` method.
This specialization first observes `KVORepresentable` value and then converts it to `RawRepresentable` value.
It is useful for observing bridged ObjC enum values.
For more information take a look at `rx_observeWeakly` method.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func rx_observeWeakly<E: RawRepresentable where E.RawValue: KVORepresentable>(type: E.Type, _ keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial]) -> Observable<E?> {
return rx_observeWeakly(E.RawValue.KVOType.self, keyPath, options: options)
.map(E.init)
}
}
#endif

View File

@ -18,29 +18,52 @@ var deallocatingSubjectContext: UInt8 = 0
var deallocatedSubjectTriggerContext: UInt8 = 0
var deallocatedSubjectContext: UInt8 = 0
// KVO is a tricky mechanism.
//
// When observing child in a ownership hierarchy, usually retaining observing target is wanted behavior.
// When observing parent in a ownership hierarchy, usually retaining target isn't wanter behavior.
//
// KVO with weak references is especially tricky. For it to work, some kind of swizzling is required.
// That can be done by
// * replacing object class dynamically (like KVO does)
// * by swizzling `dealloc` method on all instances for a class.
// * some third method ...
//
// Both approaches can fail in certain scenarios:
// * problems arise when swizzlers return original object class (like KVO does when nobody is observing)
// * Problems can arise because replacing dealloc method isn't atomic operation (get implementation,
// set implementation).
//
// Second approach is chosen. It can fail in case there are multiple libraries dynamically trying
// to replace dealloc method. In case that isn't the case, it should be ok.
//
/**
KVO is a tricky mechanism.
// KVO
When observing child in a ownership hierarchy, usually retaining observing target is wanted behavior.
When observing parent in a ownership hierarchy, usually retaining target isn't wanter behavior.
KVO with weak references is especially tricky. For it to work, some kind of swizzling is required.
That can be done by
* replacing object class dynamically (like KVO does)
* by swizzling `dealloc` method on all instances for a class.
* some third method ...
Both approaches can fail in certain scenarios:
* problems arise when swizzlers return original object class (like KVO does when nobody is observing)
* Problems can arise because replacing dealloc method isn't atomic operation (get implementation,
set implementation).
Second approach is chosen. It can fail in case there are multiple libraries dynamically trying
to replace dealloc method. In case that isn't the case, it should be ok.
*/
extension NSObject {
/**
Observes values on `keyPath` starting from `self` with `options` and retains `self` if `retainSelf` is set.
`rx_observe` is just a simple and performant wrapper around KVO mechanism.
* it can be used to observe paths starting from `self` or from ancestors in ownership graph (`retainSelf = false`)
* it can be used to observe paths starting from descendants in ownership graph (`retainSelf = true`)
* the paths have to consist only of `strong` properties, otherwise you are risking crashing the system by not unregistering KVO observer before dealloc.
If support for weak properties is needed or observing arbitrary or unknown relationships in the
ownership tree, `rx_observeWeakly` is the preferred option.
- parameter keyPath: Key path of property names to observe.
- parameter options: KVO mechanism notification options.
- parameter retainSelf: Retains self during observation if set `true`.
- returns: Observable sequence of objects on `keyPath`.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func rx_observe<E>(type: E.Type, _ keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial], retainSelf: Bool = true) -> Observable<E?> {
return KVOObservable(object: self, keyPath: keyPath, options: options, retainTarget: retainSelf).asObservable()
}
/**
Observes values on `keyPath` starting from `self` with `options` and retains `self` if `retainSelf` is set.
@ -58,8 +81,10 @@ extension NSObject {
- parameter retainSelf: Retains self during observation if set `true`.
- returns: Observable sequence of objects on `keyPath`.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
@available(*, deprecated=2.0.0, message="Please use version that takes type as first argument `rx_observe<Element>(type: Element.Type, _ keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial], retainSelf: Bool = true) -> Observable<Element?>`")
public func rx_observe<Element>(keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial], retainSelf: Bool = true) -> Observable<Element?> {
return KVOObservable(object: self, keyPath: keyPath, options: options, retainTarget: retainSelf)
return KVOObservable(object: self, keyPath: keyPath, options: options, retainTarget: retainSelf).asObservable()
}
}
@ -67,7 +92,28 @@ extension NSObject {
#if !DISABLE_SWIZZLING
// KVO
extension NSObject {
/**
Observes values on `keyPath` starting from `self` with `options` and doesn't retain `self`.
It can be used in all cases where `rx_observe` can be used and additionally
* because it won't retain observed target, it can be used to observe arbitrary object graph whose ownership relation is unknown
* it can be used to observe `weak` properties
**Since it needs to intercept object deallocation process it needs to perform swizzling of `dealloc` method on observed object.**
- parameter keyPath: Key path of property names to observe.
- parameter options: KVO mechanism notification options.
- returns: Observable sequence of objects on `keyPath`.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func rx_observeWeakly<E>(type: E.Type, _ keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial]) -> Observable<E?> {
return observeWeaklyKeyPathFor(self, keyPath: keyPath, options: options)
.map { n in
return n as? E
}
}
/**
Observes values on `keyPath` starting from `self` with `options` and doesn't retain `self`.
@ -82,6 +128,8 @@ extension NSObject {
- parameter options: KVO mechanism notification options.
- returns: Observable sequence of objects on `keyPath`.
*/
@available(*, deprecated=2.0.0, message="Please use version that takes type as first argument `rx_observeWeakly<Element>(type: Element.Type, keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial]) -> Observable<Element?>`")
@warn_unused_result(message="http://git.io/rxs.uo")
public func rx_observeWeakly<Element>(keyPath: String, options: NSKeyValueObservingOptions = [.New, .Initial]) -> Observable<Element?> {
return observeWeaklyKeyPathFor(self, keyPath: keyPath, options: options)
.map { n in

View File

@ -11,6 +11,48 @@ import Foundation
import RxSwift
#endif
/**
RxCocoa URL errors.
*/
public enum RxCocoaURLError
: ErrorType
, CustomDebugStringConvertible {
/**
Unknown error occurred.
*/
case Unknown
/**
Response is not NSHTTPURLResponse
*/
case NonHTTPResponse(response: NSURLResponse)
/**
Response is not successful. (not in `200 ..< 300` range)
*/
case HTTPRequestFailed(response: NSHTTPURLResponse, data: NSData?)
/**
Deserialization error.
*/
case DeserializationError(error: ErrorType)
}
public extension RxCocoaURLError {
/**
A textual representation of `self`, suitable for debugging.
*/
public var debugDescription: String {
switch self {
case .Unknown:
return "Unknown error has occurred."
case let .NonHTTPResponse(response):
return "Response is not NSHTTPURLResponse `\(response)`."
case let .HTTPRequestFailed(response, _):
return "HTTP request failed with `\(response.statusCode)`."
case let .DeserializationError(error):
return "Error during deserialization of the response: \(error)"
}
}
}
func escapeTerminalString(value: String) -> String {
return value.stringByReplacingOccurrencesOfString("\"", withString: "\\\"", options:[], range: nil)
}
@ -74,7 +116,8 @@ extension NSURLSession {
- parameter request: URL request.
- returns: Observable sequence of URL responses.
*/
public func rx_response(request: NSURLRequest) -> Observable<(NSData!, NSURLResponse!)> {
@warn_unused_result(message="http://git.io/rxs.uo")
public func rx_response(request: NSURLRequest) -> Observable<(NSData!, NSHTTPURLResponse)> {
return create { observer in
// smart compiler should be able to optimize this out
@ -92,13 +135,18 @@ extension NSURLSession {
print(convertResponseToString(data, response, error, interval))
}
if data == nil || response == nil {
observer.on(.Error(error ?? RxError.UnknownError))
guard let response = response, data = data else {
observer.on(.Error(error ?? RxCocoaURLError.Unknown))
return
}
else {
observer.on(.Next(data as NSData!, response as NSURLResponse!))
observer.on(.Completed)
guard let httpResponse = response as? NSHTTPURLResponse else {
observer.on(.Error(RxCocoaURLError.NonHTTPResponse(response: response)))
return
}
observer.on(.Next(data as NSData!, httpResponse))
observer.on(.Completed)
}
@ -126,20 +174,14 @@ extension NSURLSession {
- parameter request: URL request.
- returns: Observable sequence of response data.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func rx_data(request: NSURLRequest) -> Observable<NSData> {
return rx_response(request).map { (data, response) -> NSData in
guard let response = response as? NSHTTPURLResponse else {
throw RxError.UnknownError
}
if 200 ..< 300 ~= response.statusCode {
return data ?? NSData()
}
else {
throw rxError(.NetworkError, message: "Server returned failure", userInfo: [
RxCocoaErrorHTTPResponseKey: response,
RxCocoaErrorHTTPResponseDataKey: data ?? NSData()
])
throw RxCocoaURLError.HTTPRequestFailed(response: response, data: data)
}
}
}
@ -161,9 +203,14 @@ extension NSURLSession {
- parameter request: URL request.
- returns: Observable sequence of response JSON.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func rx_JSON(request: NSURLRequest) -> Observable<AnyObject!> {
return rx_data(request).map { (data) -> AnyObject! in
return try NSJSONSerialization.JSONObjectWithData(data ?? NSData(), options: [])
do {
return try NSJSONSerialization.JSONObjectWithData(data ?? NSData(), options: [])
} catch let error {
throw RxCocoaURLError.DeserializationError(error: error)
}
}
}
@ -184,6 +231,7 @@ extension NSURLSession {
- parameter URL: URL of `NSURLRequest` request.
- returns: Observable sequence of response JSON.
*/
@warn_unused_result(message="http://git.io/rxs.uo")
public func rx_JSON(URL: NSURL) -> Observable<AnyObject!> {
return rx_JSON(NSURLRequest(URL: URL))
}

View File

@ -14,45 +14,52 @@ import RxSwift
import UIKit
#endif
public enum RxCocoaError : Int {
case Unknown = 0
case NetworkError = 1
case InvalidOperation = 2
case KeyPathInvalid = 3
/**
RxCocoa errors.
*/
public enum RxCocoaError
: ErrorType
, CustomDebugStringConvertible {
/**
Unknown error has occurred.
*/
case Unknown
/**
Invalid operation was attempted.
*/
case InvalidOperation(object: AnyObject)
/**
Items are not yet bound to user interface but have been requested.
*/
case ItemsNotYetBound(object: AnyObject)
/**
Invalid KVO Path.
*/
case InvalidPropertyName(object: AnyObject, propertyName: String)
/**
Invalid object on key path.
*/
case InvalidObjectOnKeyPath(object: AnyObject, sourceObject: AnyObject, propertyName: String)
}
/**
Error domain for internal RxCocoa errors.
*/
public let RxCocoaErrorDomain = "RxCocoaError"
/**
`userInfo` key for `NSURLResponse` object when `RxCocoaError.NetworkError` happens.
*/
public let RxCocoaErrorHTTPResponseKey = "RxCocoaErrorHTTPResponseKey"
/**
`userInfo` key for `NSData` object when `RxCocoaError.NetworkError` happens.
*/
public let RxCocoaErrorHTTPResponseDataKey = "RxCocoaErrorHTTPResponseDataKey"
func rxError(errorCode: RxCocoaError, _ message: String) -> NSError {
return NSError(domain: RxCocoaErrorDomain, code: errorCode.rawValue, userInfo: [NSLocalizedDescriptionKey: message])
}
#if !RELEASE
public func _rxError(errorCode: RxCocoaError, message: String, userInfo: NSDictionary) -> NSError {
return rxError(errorCode, message: message, userInfo: userInfo)
}
#endif
func rxError(errorCode: RxCocoaError, message: String, userInfo: NSDictionary) -> NSError {
var resultInfo: [NSObject: AnyObject] = [:]
resultInfo[NSLocalizedDescriptionKey] = message
for k in userInfo.allKeys {
resultInfo[k as! NSObject] = userInfo[k as! NSCopying]
public extension RxCocoaError {
/**
A textual representation of `self`, suitable for debugging.
*/
public var debugDescription: String {
switch self {
case .Unknown:
return "Unknown error occurred"
case let .InvalidOperation(object):
return "Invalid operation was attempted on `\(object)`"
case let .ItemsNotYetBound(object):
return "Data source is set, but items are not yet bound to user interface for `\(object)`"
case let .InvalidPropertyName(object, propertyName):
return "Object `\(object)` dosn't have a property named `\(propertyName)`"
case let .InvalidObjectOnKeyPath(object, sourceObject, propertyName):
return "Unobservable object `\(object)` was observed as `\(propertyName)` of `\(sourceObject)`"
}
}
return NSError(domain: RxCocoaErrorDomain, code: Int(errorCode.rawValue), userInfo: resultInfo)
}
func bindingErrorToInterface(error: ErrorType) {
@ -65,11 +72,11 @@ func bindingErrorToInterface(error: ErrorType) {
}
func rxAbstractMethodWithMessage<T>(message: String) -> T {
return rxFatalErrorAndDontReturn(message)
rxFatalError(message)
}
func rxAbstractMethod<T>() -> T {
return rxFatalErrorAndDontReturn("Abstract method")
rxFatalError("Abstract method")
}
// workaround for Swift compiler bug, cheers compiler team :)
@ -85,7 +92,6 @@ func castOrFatalError<T>(value: AnyObject!, message: String) -> T {
let maybeResult: T? = value as? T
guard let result = maybeResult else {
rxFatalError(message)
return maybeResult!
}
return result
@ -95,7 +101,6 @@ func castOrFatalError<T>(value: AnyObject!) -> T {
let maybeResult: T? = value as? T
guard let result = maybeResult else {
rxFatalError("Failure converting from \(value) to \(T.self)")
return maybeResult!
}
return result
@ -109,14 +114,9 @@ let delegateNotSet = "Delegate not set"
// }
func rxFatalErrorAndDontReturn<T>(lastMessage: String) -> T {
rxFatalError(lastMessage)
return (nil as T!)!
}
#if !RX_NO_MODULE
func rxFatalError(lastMessage: String) {
@noreturn func rxFatalError(lastMessage: String) {
// The temptation to comment this line is great, but please don't, it's for your own good. The choice is yours.
fatalError(lastMessage)
}

View File

@ -19,11 +19,26 @@ class RxTarget : NSObject
override init() {
super.init()
self.retainSelf = self
#if TRACE_RESOURCES
OSAtomicIncrement32(&resourceCount)
#endif
#if DEBUG
MainScheduler.ensureExecutingOnScheduler()
#endif
}
func dispose() {
#if DEBUG
MainScheduler.ensureExecutingOnScheduler()
#endif
self.retainSelf = nil
}
#if TRACE_RESOURCES
deinit {
OSAtomicDecrement32(&resourceCount)
}
#endif
}

View File

@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>Krunoslav-Zaher.$(PRODUCT_NAME:rfc1034identifier)</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>2.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

View File

@ -12,37 +12,75 @@ import Cocoa
import RxSwift
#endif
var rx_value_key: UInt8 = 0
var rx_control_events_key: UInt8 = 0
extension NSControl {
/**
Reactive wrapper for control event.
*/
public var rx_controlEvents: ControlEvent<Void> {
let source: Observable<Void> = AnonymousObservable { observer in
MainScheduler.ensureExecutingOnScheduler()
let observer = ControlTarget(control: self) { control in
observer.on(.Next())
}
return observer
}.takeUntil(rx_deallocated)
MainScheduler.ensureExecutingOnScheduler()
let source = rx_lazyInstanceObservable(&rx_control_events_key) { () -> Observable<Void> in
create { [weak self] observer in
MainScheduler.ensureExecutingOnScheduler()
guard let control = self else {
observer.on(.Completed)
return NopDisposable.instance
}
let observer = ControlTarget(control: control) { control in
observer.on(.Next())
}
return observer
}.takeUntil(self.rx_deallocated)
}
return ControlEvent(source: source)
}
/**
Helper to make sure that `Observable` returned from `createCachedObservable` is only created once.
This is important because on OSX there is only one `target` and `action` properties on `NSControl`.
*/
func rx_lazyInstanceObservable<T: AnyObject>(key: UnsafePointer<Void>, createCachedObservable: () -> T) -> T {
if let value = objc_getAssociatedObject(self, key) {
return value as! T
}
let observable = createCachedObservable()
objc_setAssociatedObject(self, key, observable, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return observable
}
func rx_value<T>(getter getter: () -> T, setter: T -> Void) -> ControlProperty<T> {
let source: Observable<T> = AnonymousObservable { observer in
observer.on(.Next(getter()))
let observer = ControlTarget(control: self) { control in
MainScheduler.ensureExecutingOnScheduler()
let source = rx_lazyInstanceObservable(&rx_value_key) { () -> Observable<T> in
return create { [weak self] observer in
guard let control = self else {
observer.on(.Completed)
return NopDisposable.instance
}
observer.on(.Next(getter()))
}
return observer
}.takeUntil(rx_deallocated)
return ControlProperty(source: source, observer: ObserverOf { event in
let observer = ControlTarget(control: control) { control in
observer.on(.Next(getter()))
}
return observer
}.takeUntil(self.rx_deallocated)
}
return ControlProperty(source: source, observer: AnyObserver { event in
switch event {
case .Next(let value):
setter(value)

View File

@ -17,7 +17,7 @@ extension NSImageView {
/**
Bindable sink for `image` property.
*/
public var rx_image: ObserverOf<NSImage!> {
public var rx_image: AnyObserver<NSImage!> {
return self.rx_imageAnimated(nil)
}
@ -26,8 +26,8 @@ extension NSImageView {
- parameter transitionType: Optional transition type while setting the image (kCATransitionFade, kCATransitionMoveIn, ...)
*/
public func rx_imageAnimated(transitionType: String?) -> ObserverOf<NSImage!> {
return ObserverOf { [weak self] event in
public func rx_imageAnimated(transitionType: String?) -> AnyObserver<NSImage!> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {

View File

@ -15,13 +15,12 @@ import RxSwift
class RxTextFieldDelegate : DelegateProxy
, NSTextFieldDelegate
, DelegateProxyType {
let textField: NSTextField
let textSubject = ReplaySubject<String>.create(bufferSize: 1)
let textSubject = PublishSubject<String>()
required init(parentObject: AnyObject) {
self.textField = parentObject as! NSTextField
let textField = parentObject as! NSTextField
super.init(parentObject: parentObject)
self.textSubject.on(.Next(self.textField.stringValue))
self.textSubject.on(.Next(textField.stringValue))
}
override func controlTextDidChange(notification: NSNotification) {
@ -59,9 +58,11 @@ extension NSTextField {
public var rx_text: ControlProperty<String> {
let delegate = proxyForObject(self) as RxTextFieldDelegate
let source = delegate.textSubject
let source = deferred { [weak self] in
delegate.textSubject.startWith(self?.stringValue ?? "")
}.takeUntil(rx_deallocated)
return ControlProperty(source: source, observer: ObserverOf { [weak self] event in
return ControlProperty(source: source, observer: AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {

View File

@ -20,19 +20,20 @@ class RxScrollViewDelegateProxy : DelegateProxy
, DelegateProxyType {
private var _contentOffsetSubject: ReplaySubject<CGPoint>?
unowned let scrollView: UIScrollView
weak var scrollView: UIScrollView?
var contentOffsetSubject: Observable<CGPoint> {
if _contentOffsetSubject == nil {
_contentOffsetSubject = ReplaySubject.create(bufferSize: 1)
_contentOffsetSubject!.on(.Next(self.scrollView.contentOffset))
let replaySubject = ReplaySubject<CGPoint>.create(bufferSize: 1)
_contentOffsetSubject = replaySubject
replaySubject.on(.Next(self.scrollView?.contentOffset ?? CGPointZero))
}
return _contentOffsetSubject!
}
required init(parentObject: AnyObject) {
self.scrollView = parentObject as! UIScrollView
self.scrollView = (parentObject as! UIScrollView)
super.init(parentObject: parentObject)
}
@ -40,7 +41,7 @@ class RxScrollViewDelegateProxy : DelegateProxy
func scrollViewDidScroll(scrollView: UIScrollView) {
if let contentOffset = _contentOffsetSubject {
contentOffset.on(.Next(self.scrollView.contentOffset))
contentOffset.on(.Next(scrollView.contentOffset))
}
self._forwardToDelegate?.scrollViewDidScroll?(scrollView)
}
@ -48,7 +49,7 @@ class RxScrollViewDelegateProxy : DelegateProxy
// delegate proxy
override class func createProxyForObject(object: AnyObject) -> AnyObject {
let scrollView = object as! UIScrollView
let scrollView = (object as! UIScrollView)
return castOrFatalError(scrollView.rx_createDelegateProxy())
}

View File

@ -18,8 +18,8 @@ extension UIBarButtonItem {
/**
Bindable sink for `enabled` property.
*/
public var rx_enabled: ObserverOf<Bool> {
return ObserverOf { [weak self] event in
public var rx_enabled: AnyObserver<Bool> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
@ -38,8 +38,14 @@ extension UIBarButtonItem {
Reactive wrapper for target action pattern on `self`.
*/
public var rx_tap: ControlEvent<Void> {
let source: Observable<Void> = AnonymousObservable { observer in
let target = BarButtonItemTarget(barButtonItem: self) {
let source: Observable<Void> = create { [weak self] observer in
guard let control = self else {
observer.on(.Completed)
return NopDisposable.instance
}
let target = BarButtonItemTarget(barButtonItem: control) {
observer.on(.Next())
}
return target
@ -52,7 +58,7 @@ extension UIBarButtonItem {
@objc
class BarButtonItemTarget: NSObject, Disposable {
class BarButtonItemTarget: RxTarget {
typealias Callback = () -> Void
weak var barButtonItem: UIBarButtonItem?
@ -66,12 +72,11 @@ class BarButtonItemTarget: NSObject, Disposable {
barButtonItem.action = Selector("action:")
}
deinit {
dispose()
}
func dispose() {
override func dispose() {
super.dispose()
#if DEBUG
MainScheduler.ensureExecutingOnScheduler()
#endif
barButtonItem?.target = nil
barButtonItem?.action = nil

View File

@ -6,7 +6,7 @@
// Copyright (c) 2015 Krunoslav Zaher. All rights reserved.
//
#if os(iOS) || os(tvOS)
#if os(iOS)
import Foundation
#if !RX_NO_MODULE
@ -26,3 +26,23 @@ extension UIButton {
}
#endif
#if os(tvOS)
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
import UIKit
extension UIButton {
/**
Reactive wrapper for `PrimaryActionTriggered` control event.
*/
public var rx_primaryAction: ControlEvent<Void> {
return rx_controlEvents(.PrimaryActionTriggered)
}
}
#endif

View File

@ -143,14 +143,49 @@ extension UICollectionView {
*/
public func rx_modelSelected<T>() -> ControlEvent<T> {
let source: Observable<T> = rx_itemSelected .map { indexPath in
let dataSource: RxCollectionViewReactiveArrayDataSource<T> = castOrFatalError(self.rx_dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx_itemsWith*` methods was used.")
return dataSource.modelAtIndex(indexPath.item)!
let source: Observable<T> = rx_itemSelected.flatMap { [weak self] indexPath -> Observable<T> in
guard let view = self else {
return empty()
}
return just(try view.rx_modelAtIndexPath(indexPath))
}
return ControlEvent(source: source)
}
/**
Syncronous helper method for retrieving a model at indexPath through a reactive data source
*/
public func rx_modelAtIndexPath<T>(indexPath: NSIndexPath) throws -> T {
let dataSource: RxCollectionViewReactiveArrayDataSource<T> = castOrFatalError(self.rx_dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx_itemsWith*` methods was used.")
guard let element = dataSource.modelAtIndex(indexPath.item) else {
throw RxCocoaError.ItemsNotYetBound(object: self)
}
return element
}
}
#endif
#if os(tvOS)
extension UICollectionView {
/**
Reactive wrapper for `delegate` message `collectionView:didUpdateFocusInContext:withAnimationCoordinator:`.
*/
public var rx_didUpdateFocusInContextWithAnimationCoordinator: ControlEvent<(context: UIFocusUpdateContext, animationCoordinator: UIFocusAnimationCoordinator)> {
let source = rx_delegate.observe("collectionView:didUpdateFocusInContext:withAnimationCoordinator:")
.map { a -> (context: UIFocusUpdateContext, animationCoordinator: UIFocusAnimationCoordinator) in
let context = a[1] as! UIFocusUpdateContext
let animationCoordinator = a[2] as! UIFocusAnimationCoordinator
return (context: context, animationCoordinator: animationCoordinator)
}
return ControlEvent(source: source)
}
}
#endif

View File

@ -19,8 +19,8 @@ extension UIControl {
/**
Bindable sink for `enabled` property.
*/
public var rx_enabled: ObserverOf<Bool> {
return ObserverOf { [weak self] event in
public var rx_enabled: AnyObserver<Bool> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
@ -41,10 +41,15 @@ extension UIControl {
- parameter controlEvents: Filter for observed event types.
*/
public func rx_controlEvents(controlEvents: UIControlEvents) -> ControlEvent<Void> {
let source: Observable<Void> = AnonymousObservable { observer in
let source: Observable<Void> = create { [weak self] observer in
MainScheduler.ensureExecutingOnScheduler()
let controlTarget = ControlTarget(control: self, controlEvents: controlEvents) {
guard let control = self else {
observer.on(.Completed)
return NopDisposable.instance
}
let controlTarget = ControlTarget(control: control, controlEvents: controlEvents) {
control in
observer.on(.Next())
}
@ -58,11 +63,15 @@ extension UIControl {
}
func rx_value<T>(getter getter: () -> T, setter: T -> Void) -> ControlProperty<T> {
let source: Observable<T> = AnonymousObservable { observer in
let source: Observable<T> = create { [weak self] observer in
guard let control = self else {
observer.on(.Completed)
return NopDisposable.instance
}
observer.on(.Next(getter()))
let controlTarget = ControlTarget(control: self, controlEvents: [.EditingChanged, .ValueChanged]) { control in
let controlTarget = ControlTarget(control: control, controlEvents: [.EditingChanged, .ValueChanged]) { control in
observer.on(.Next(getter()))
}
@ -71,9 +80,9 @@ extension UIControl {
}
}.takeUntil(rx_deallocated)
return ControlProperty<T>(source: source, observer: ObserverOf { event in
return ControlProperty<T>(source: source, observer: AnyObserver { event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
setter(value)

View File

@ -20,7 +20,7 @@ class GestureTarget: RxTarget {
let selector = Selector("eventHandler:")
unowned let gestureRecognizer: UIGestureRecognizer
weak var gestureRecognizer: UIGestureRecognizer?
var callback: Callback?
init(_ gestureRecognizer: UIGestureRecognizer, callback: Callback) {
@ -38,15 +38,15 @@ class GestureTarget: RxTarget {
}
func eventHandler(sender: UIGestureRecognizer!) {
if let callback = self.callback {
callback(self.gestureRecognizer)
if let callback = self.callback, gestureRecognizer = self.gestureRecognizer {
callback(gestureRecognizer)
}
}
override func dispose() {
super.dispose()
self.gestureRecognizer.removeTarget(self, action: self.selector)
self.gestureRecognizer?.removeTarget(self, action: self.selector)
self.callback = nil
}
}
@ -57,12 +57,17 @@ extension UIGestureRecognizer {
Reactive wrapper for gesture recognizer events.
*/
public var rx_event: ControlEvent<UIGestureRecognizer> {
let source: Observable<UIGestureRecognizer> = AnonymousObservable { [weak self] observer in
let source: Observable<UIGestureRecognizer> = create { [weak self] observer in
MainScheduler.ensureExecutingOnScheduler()
guard let control = self else {
observer.on(.Completed)
return NopDisposable.instance
}
let observer = GestureTarget(self!) {
let observer = GestureTarget(control) {
control in
observer.on(.Next(self!))
observer.on(.Next(control))
}
return observer

View File

@ -19,7 +19,7 @@ extension UIImageView {
/**
Bindable sink for `image` property.
*/
public var rx_image: ObserverOf<UIImage!> {
public var rx_image: AnyObserver<UIImage?> {
return self.rx_imageAnimated(nil)
}
@ -28,8 +28,8 @@ extension UIImageView {
- parameter transitionType: Optional transition type while setting the image (kCATransitionFade, kCATransitionMoveIn, ...)
*/
public func rx_imageAnimated(transitionType: String?) -> ObserverOf<UIImage!> {
return ObserverOf { [weak self] event in
public func rx_imageAnimated(transitionType: String?) -> AnyObserver<UIImage?> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {

View File

@ -19,8 +19,8 @@ extension UILabel {
/**
Bindable sink for `text` property.
*/
public var rx_text: ObserverOf<String> {
return ObserverOf { [weak self] event in
public var rx_text: AnyObserver<String> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
@ -34,6 +34,25 @@ extension UILabel {
}
}
}
/**
Bindable sink for `attributedText` property.
*/
public var rx_attributedText: AnyObserver<NSAttributedString?> {
return AnyObserver { [weak self] event in
MainScheduler.ensureExecutingOnScheduler()
switch event {
case .Next(let value):
self?.attributedText = value
case .Error(let error):
bindingErrorToInterface(error)
break
case .Completed:
break
}
}
}
}

View File

@ -40,7 +40,7 @@ extension UIScrollView {
public var rx_contentOffset: ControlProperty<CGPoint> {
let proxy = proxyForObject(self) as RxScrollViewDelegateProxy
return ControlProperty(source: proxy.contentOffsetSubject, observer: ObserverOf { [weak self] event in
return ControlProperty(source: proxy.contentOffsetSubject, observer: AnyObserver { [weak self] event in
switch event {
case .Next(let value):
self?.contentOffset = value

View File

@ -41,7 +41,7 @@ extension UISearchBar {
.startWith(text)
}
return ControlProperty(source: source, observer: ObserverOf { [weak self] event in
return ControlProperty(source: source, observer: AnyObserver { [weak self] event in
switch event {
case .Next(let value):
self?.text = value

View File

@ -54,7 +54,7 @@ extension UITableView {
return cell
}
return self.rx_itemsWithDataSource(dataSource)(source: source)
return self.rx_itemsWithDataSource(dataSource)(source: source)
}
/**
@ -187,15 +187,50 @@ extension UITableView {
*/
public func rx_modelSelected<T>() -> ControlEvent<T> {
let source: Observable<T> = rx_itemSelected.map { ip in
let dataSource: RxTableViewReactiveArrayDataSource<T> = castOrFatalError(self.rx_dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx_subscribeItemsTo` methods was used.")
return dataSource.modelAtIndex(ip.item)!
let source: Observable<T> = rx_itemSelected.flatMap { [weak self] indexPath -> Observable<T> in
guard let view = self else {
return empty()
}
return just(try view.rx_modelAtIndexPath(indexPath))
}
return ControlEvent(source: source)
}
/**
Synchronous helper method for retrieving a model at indexPath through a reactive data source
*/
public func rx_modelAtIndexPath<T>(indexPath: NSIndexPath) throws -> T {
let dataSource: RxTableViewReactiveArrayDataSource<T> = castOrFatalError(self.rx_dataSource.forwardToDelegate(), message: "This method only works in case one of the `rx_items*` methods was used.")
guard let element = dataSource.modelAtIndex(indexPath.item) else {
throw RxCocoaError.ItemsNotYetBound(object: self)
}
return element
}
}
#endif
#if os(tvOS)
extension UITableView {
/**
Reactive wrapper for `delegate` message `tableView:didUpdateFocusInContext:withAnimationCoordinator:`.
*/
public var rx_didUpdateFocusInContextWithAnimationCoordinator: ControlEvent<(context: UIFocusUpdateContext, animationCoordinator: UIFocusAnimationCoordinator)> {
let source = rx_delegate.observe("tableView:didUpdateFocusInContext:withAnimationCoordinator:")
.map { a -> (context: UIFocusUpdateContext, animationCoordinator: UIFocusAnimationCoordinator) in
let context = a[1] as! UIFocusUpdateContext
let animationCoordinator = a[2] as! UIFocusAnimationCoordinator
return (context: context, animationCoordinator: animationCoordinator)
}
return ControlEvent(source: source)
}
}
#endif

View File

@ -38,7 +38,7 @@ extension UITextView {
.startWith(text)
}
return ControlProperty(source: source, observer: ObserverOf { [weak self] event in
return ControlProperty(source: source, observer: AnyObserver { [weak self] event in
switch event {
case .Next(let value):
self?.text = value

View File

@ -13,20 +13,22 @@ import RxSwift
import RxCocoa
#endif
struct ItemPath : CustomStringConvertible {
struct ItemPath : CustomDebugStringConvertible {
let sectionIndex: Int
let itemIndex: Int
var description : String {
var debugDescription : String {
get {
return "(\(sectionIndex), \(itemIndex))"
}
}
}
public struct Changeset<S: SectionModelType> : CustomStringConvertible {
public struct Changeset<S: SectionModelType> : CustomDebugStringConvertible {
typealias I = S.Item
var reloadData: Bool = false
var finalSections: [S] = []
var insertedSections: [Int] = []
@ -43,10 +45,12 @@ public struct Changeset<S: SectionModelType> : CustomStringConvertible {
var initialValue = Changeset<S>()
initialValue.insertedSections = Array(0 ..< sections.count)
initialValue.finalSections = sections
initialValue.reloadData = true
return initialValue
}
public var description : String {
public var debugDescription : String {
get {
let serializedSections = "[\n" + finalSections.map { "\($0)" }.joinWithSeparator(",\n") + "\n]\n"
return " >> Final sections"

View File

@ -55,7 +55,7 @@ public class RxCollectionViewSectionedDataSource<S: SectionModelType> : _RxColle
public typealias CellFactory = (UICollectionView, NSIndexPath, I) -> UICollectionViewCell
public typealias SupplementaryViewFactory = (UICollectionView, String, NSIndexPath) -> UICollectionReusableView
public typealias IncrementalUpdateObserver = ObserverOf<Changeset<S>>
public typealias IncrementalUpdateObserver = AnyObserver<Changeset<S>>
public typealias IncrementalUpdateDisposeKey = Bag<IncrementalUpdateObserver>.KeyType

View File

@ -23,7 +23,12 @@ class RxTableViewSectionedAnimatedDataSource<S: SectionModelType> : RxTableViewS
for c in element {
//print("Animating ==============================\n\(c)\n===============================\n")
setSections(c.finalSections)
tableView.performBatchUpdates(c)
if c.reloadData {
tableView.reloadData()
}
else {
tableView.performBatchUpdates(c)
}
}
case .Error(let error):
bindingErrorToInterface(error)

View File

@ -64,7 +64,7 @@ public class RxTableViewSectionedDataSource<S: SectionModelType> : _RxTableViewS
public typealias Section = S
public typealias CellFactory = (UITableView, NSIndexPath, I) -> UITableViewCell
public typealias IncrementalUpdateObserver = ObserverOf<Changeset<S>>
public typealias IncrementalUpdateObserver = AnyObserver<Changeset<S>>
public typealias IncrementalUpdateDisposeKey = Bag<IncrementalUpdateObserver>.KeyType

View File

@ -8,14 +8,31 @@
import Foundation
enum EditEvent : CustomStringConvertible {
public enum DifferentiatorError
: ErrorType
, CustomDebugStringConvertible {
case DuplicateItem(item: Any)
}
extension DifferentiatorError {
public var debugDescription: String {
switch self {
case let .DuplicateItem(item):
return "Duplicate item \(item)"
}
}
}
enum EditEvent : CustomDebugStringConvertible {
case Inserted // can't be found in old sections
case Deleted // Was in old, not in new, in it's place is something "not new" :(, otherwise it's Updated
case Moved // same item, but was on different index, and needs explicit move
case MovedAutomatically // don't need to specify any changes for those rows
case Untouched
var description: String {
}
extension EditEvent {
var debugDescription: String {
get {
switch self {
case .Inserted:
@ -33,39 +50,48 @@ enum EditEvent : CustomStringConvertible {
}
}
struct SectionAdditionalInfo : CustomStringConvertible {
struct SectionAdditionalInfo : CustomDebugStringConvertible {
var event: EditEvent
var indexAfterDelete: Int?
var description: String {
}
extension SectionAdditionalInfo {
var debugDescription: String {
get {
return "\(event), \(indexAfterDelete)"
}
}
}
struct ItemAdditionalInfo : CustomStringConvertible {
struct ItemAdditionalInfo : CustomDebugStringConvertible {
var event: EditEvent
var indexAfterDelete: Int?
var description: String {
}
extension ItemAdditionalInfo {
var debugDescription: String {
get {
return "\(event) \(indexAfterDelete)"
}
}
}
func indexSections<S: SectionModelType where S: Hashable, S.Item: Hashable>(sections: [S]) -> [S : Int] {
func indexSections<S: SectionModelType where S: Hashable, S.Item: Hashable>(sections: [S]) throws -> [S : Int] {
var indexedSections: [S : Int] = [:]
for (i, section) in sections.enumerate() {
precondition(indexedSections[section] == nil, "Section \(section) has already been indexed at \(indexedSections[section]!)")
guard indexedSections[section] == nil else {
#if DEBUG
precondition(indexedSections[section] == nil, "Section \(section) has already been indexed at \(indexedSections[section]!)")
#endif
throw DifferentiatorError.DuplicateItem(item: section)
}
indexedSections[section] = i
}
return indexedSections
}
func indexSectionItems<S: SectionModelType where S: Hashable, S.Item: Hashable>(sections: [S]) -> [S.Item : (Int, Int)] {
func indexSectionItems<S: SectionModelType where S: Hashable, S.Item: Hashable>(sections: [S]) throws -> [S.Item : (Int, Int)] {
var totalItems = 0
for i in 0 ..< sections.count {
totalItems += sections[i].items.count
@ -76,7 +102,12 @@ func indexSectionItems<S: SectionModelType where S: Hashable, S.Item: Hashable>(
for i in 0 ..< sections.count {
for (j, item) in sections[i].items.enumerate() {
precondition(indexedItems[item] == nil, "Item \(item) has already been indexed at \(indexedItems[item]!)" )
guard indexedItems[item] == nil else {
#if DEBUG
precondition(indexedItems[item] == nil, "Item \(item) has already been indexed at \(indexedItems[item]!)" )
#endif
throw DifferentiatorError.DuplicateItem(item: item)
}
indexedItems[item] = (i, j)
}
}
@ -185,7 +216,6 @@ to = [
]
*/
// Generates differential changes suitable for sectioned view consumption.
// It will not only detect changes between two states, but it will also try to compress those changes into
// almost minimal set of changes.
@ -209,11 +239,11 @@ to = [
//
// There maybe exists a better division, but time will tell.
//
func differentiate<S: SectionModelType where S: Hashable, S.Item: Hashable>(
func differencesForSectionedView<S: SectionModelType where S: Hashable, S.Item: Hashable>(
initialSections: [S],
finalSections: [S]
)
-> [Changeset<S>] {
throws -> [Changeset<S>] {
typealias I = S.Item
@ -236,11 +266,11 @@ func differentiate<S: SectionModelType where S: Hashable, S.Item: Hashable>(
return [ItemAdditionalInfo](count: s.items.count, repeatedValue: defaultItemInfo)
}
initialSectionIndexes = indexSections(initialSections)
finalSectionIndexes = indexSections(finalSections)
initialSectionIndexes = try indexSections(initialSections)
finalSectionIndexes = try indexSections(finalSections)
var initialItemIndexes: [I: (Int, Int)] = indexSectionItems(initialSections)
var finalItemIndexes: [I: (Int, Int)] = indexSectionItems(finalSections)
var initialItemIndexes: [I: (Int, Int)] = try indexSectionItems(initialSections)
var finalItemIndexes: [I: (Int, Int)] = try indexSectionItems(finalSections)
// mark deleted sections {
// 1rst stage

View File

@ -0,0 +1,44 @@
//
// ObservableConvertibleType+Differentiator.swift
// RxExample
//
// Created by Krunoslav Zaher on 11/14/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
import RxCocoa
#endif
extension ObservableConvertibleType where E: SequenceType, E.Generator.Element : protocol<SectionModelType, Hashable>, E.Generator.Element.Item: Hashable {
typealias Section = E.Generator.Element
func differentiateForSectionedView()
-> Observable<[Changeset<Section>]> {
return self.asObservable().multicast({
return PublishSubject()
}) { (sharedSource: Observable<E>) in
let newValues = sharedSource.skip(1)
let initialValueSequence: Observable<[Changeset<Section>]>= sharedSource
.take(1)
.map { [Changeset.initialValue(Array($0))] }
let differences = zip(sharedSource, newValues) { oldSections, newSections -> [Changeset<Section>] in
do {
return try differencesForSectionedView(Array(oldSections), finalSections: Array(newSections))
}
// in case of error, print it to terminal only
catch let e {
print(e)
return [Changeset.initialValue(Array(newSections))]
}
}
return sequenceOf(initialValueSequence, differences).merge()
}
}
}

View File

@ -0,0 +1,54 @@
//
// UISectionedView+RxAnimatedDataSource.swift
// RxExample
//
// Created by Krunoslav Zaher on 11/14/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
import UIKit
#if !RX_NO_MODULE
import RxSwift
import RxCocoa
#endif
extension UITableView {
public func rx_itemsAnimatedWithDataSource<
DataSource: protocol<RxTableViewDataSourceType, UITableViewDataSource>,
S: SequenceType,
O: ObservableConvertibleType,
Section: protocol<SectionModelType, Hashable>
where
DataSource.Element == [Changeset<Section>],
O.E == S,
S.Generator.Element == Section,
Section.Item: Hashable
>
(dataSource: DataSource)
(source: O)
-> Disposable {
let differences = source.differentiateForSectionedView()
return self.rx_itemsWithDataSource(dataSource)(source: differences)
}
}
extension UICollectionView {
public func rx_itemsAnimatedWithDataSource<
DataSource: protocol<RxCollectionViewDataSourceType, UICollectionViewDataSource>,
S: SequenceType,
O: ObservableConvertibleType,
Section: protocol<SectionModelType, Hashable>
where
DataSource.Element == [Changeset<Section>],
O.E == S,
S.Generator.Element == Section,
Section.Item: Hashable
>
(dataSource: DataSource)
(source: O)
-> Disposable {
let differences = source.differentiateForSectionedView()
return self.rx_itemsWithDataSource(dataSource)(source: differences)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,264 @@
//
// GitHubSearchRepositoriesAPI.swift
// RxExample
//
// Created by Krunoslav Zaher on 10/18/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
/**
Parsed GitHub respository.
*/
struct Repository: CustomDebugStringConvertible {
var name: String
var url: String
init(name: String, url: String) {
self.name = name
self.url = url
}
}
extension Repository {
var debugDescription: String {
return "\(name) | \(url)"
}
}
/**
ServiceState state.
*/
enum ServiceState {
case Online
case Offline
}
/**
Raw response from GitHub API
*/
enum SearchRepositoryResponse {
/**
New repositories just fetched
*/
case Repositories(repositories: [Repository], nextURL: NSURL?)
/**
In case there was some problem fetching data from service, this will be returned.
It really doesn't matter if that is a failure in network layer, parsing error or something else.
In case data can't be read and parsed properly, something is wrong with server response.
*/
case ServiceOffline
/**
This example uses unauthenticated GitHub API. That API does have throttling policy and you won't
be able to make more then 10 requests per minute.
That is actually an awesome scenario to demonstrate complex retries using alert views and combination of timers.
Just search like mad, and everything will be handled right.
*/
case LimitExceeded
}
/**
This is the final result of loading. Crème de la crème.
*/
struct RepositoriesState {
/**
List of parsed repositories ready to be shown in the UI.
*/
let repositories: [Repository]
/**
Current network state.
*/
let serviceState: ServiceState?
/**
Limit exceeded
*/
let limitExceeded: Bool
static let empty = RepositoriesState(repositories: [], serviceState: nil, limitExceeded: false)
}
class GitHubSearchRepositoriesAPI {
static let sharedAPI = GitHubSearchRepositoriesAPI(wireframe: DefaultWireframe())
let activityIndicator = ActivityIndicator()
// Why would network service have wireframe service? It's here to abstract promting user
// Do we really want to make this example project factory/fascade/service competition? :)
private let _wireframe: Wireframe
private init(wireframe: Wireframe) {
_wireframe = wireframe
}
private static let parseLinksPattern = "\\s*,?\\s*<([^\\>]*)>\\s*;\\s*rel=\"([^\"]*)\""
private static let linksRegex = try! NSRegularExpression(pattern: parseLinksPattern, options: [.AllowCommentsAndWhitespace])
private static func parseLinks(links: String) throws -> [String: String] {
let length = (links as NSString).length
let matches = GitHubSearchRepositoriesAPI.linksRegex.matchesInString(links, options: NSMatchingOptions(), range: NSRange(location: 0, length: length))
var result: [String: String] = [:]
for m in matches {
let matches = (1 ..< m.numberOfRanges).map { rangeIndex -> String in
let range = m.rangeAtIndex(rangeIndex)
let startIndex = links.startIndex.advancedBy(range.location)
let endIndex = startIndex.advancedBy(range.length)
let stringRange = Range(start: startIndex, end: endIndex)
return links.substringWithRange(stringRange)
}
if matches.count != 2 {
throw exampleError("Error parsing links")
}
result[matches[1]] = matches[0]
}
return result
}
private static func parseNextURL(httpResponse: NSHTTPURLResponse) throws -> NSURL? {
guard let serializedLinks = httpResponse.allHeaderFields["Link"] as? String else {
return nil
}
let links = try GitHubSearchRepositoriesAPI.parseLinks(serializedLinks)
guard let nextPageURL = links["next"] else {
return nil
}
guard let nextUrl = NSURL(string: nextPageURL) else {
throw exampleError("Error parsing next url `\(nextPageURL)`")
}
return nextUrl
}
/**
Public fascade for search.
*/
func search(query: String, loadNextPageTrigger: Observable<Void>) -> Observable<RepositoriesState> {
let escapedQuery = URLEscape(query)
let url = NSURL(string: "https://api.github.com/search/repositories?q=\(escapedQuery)")!
return recursivelySearch([], loadNextURL: url, loadNextPageTrigger: loadNextPageTrigger)
// Here we go again
.startWith(RepositoriesState.empty)
}
private func recursivelySearch(loadedSoFar: [Repository], loadNextURL: NSURL, loadNextPageTrigger: Observable<Void>) -> Observable<RepositoriesState> {
return loadSearchURL(loadNextURL).flatMap { searchResponse -> Observable<RepositoriesState> in
switch searchResponse {
/**
If service is offline, that's ok, that means that this isn't the last thing we've heard from that API.
It will retry until either battery drains, you become angry and close the app or evil machine comes back
from the future, steals your device and Googles Sarah Connor's address.
*/
case .ServiceOffline:
return just(RepositoriesState(repositories: loadedSoFar, serviceState: .Offline, limitExceeded: false))
case .LimitExceeded:
return just(RepositoriesState(repositories: loadedSoFar, serviceState: .Online, limitExceeded: true))
case .Repositories:
break
}
// Repositories without next url? The party is done.
guard case .Repositories(let newPageRepositories, let maybeNextURL) = searchResponse else {
fatalError("Some fourth case?")
}
var loadedRepositories = loadedSoFar
loadedRepositories.appendContentsOf(newPageRepositories)
let appenedRepositories = RepositoriesState(repositories: loadedRepositories, serviceState: .Online, limitExceeded: false)
// if next page can't be loaded, just return what was loaded, and stop
guard let nextURL = maybeNextURL else {
return just(appenedRepositories)
}
return [
// return loaded immediately
just(appenedRepositories),
// wait until next page can be loaded
never().takeUntil(loadNextPageTrigger),
// load next page
self.recursivelySearch(loadedRepositories, loadNextURL: nextURL, loadNextPageTrigger: loadNextPageTrigger)
].concat()
}
}
/**
Displays UI that prompts the user when to retry.
*/
private func buildRetryPrompt() -> Observable<Void> {
return _wireframe.promptFor(
"Exceeded limit of 10 non authenticated requests per minute for GitHub API. Please wait a minute. :(\nhttps://developer.github.com/v3/#rate-limiting",
cancelAction: RetryResult.Cancel,
actions: [RetryResult.Retry]
)
.filter { (x: RetryResult) in x == .Retry }
.map { _ in () }
}
private func loadSearchURL(searchURL: NSURL) -> Observable<SearchRepositoryResponse> {
return NSURLSession.sharedSession()
.rx_response(NSURLRequest(URL: searchURL))
.retry(3)
.trackActivity(self.activityIndicator)
.observeOn(Dependencies.sharedDependencies.backgroundWorkScheduler)
.map { data, httpResponse -> SearchRepositoryResponse in
if httpResponse.statusCode == 403 {
return .LimitExceeded
}
let jsonRoot = try GitHubSearchRepositoriesAPI.parseJSON(httpResponse, data: data)
guard let json = jsonRoot as? [String: AnyObject] else {
throw exampleError("Casting to dictionary failed")
}
let repositories = try GitHubSearchRepositoriesAPI.parseRepositories(json)
let nextURL = try GitHubSearchRepositoriesAPI.parseNextURL(httpResponse)
return .Repositories(repositories: repositories, nextURL: nextURL)
}
.retryOnBecomesReachable(.ServiceOffline, reachabilityService: ReachabilityService.sharedReachabilityService)
}
private static func parseJSON(httpResponse: NSHTTPURLResponse, data: NSData) throws -> AnyObject {
if !(200 ..< 300 ~= httpResponse.statusCode) {
throw exampleError("Call failed")
}
return try NSJSONSerialization.JSONObjectWithData(data ?? NSData(), options: [])
}
private static func parseRepositories(json: [String: AnyObject]) throws -> [Repository] {
guard let items = json["items"] as? [[String: AnyObject]] else {
throw exampleError("Can't find items")
}
return try items.map { item in
guard let name = item["name"] as? String,
url = item["url"] as? String else {
throw exampleError("Can't parse repository")
}
return Repository(name: name, url: url)
}
}
}

View File

@ -12,160 +12,29 @@ import RxSwift
import RxCocoa
#endif
struct Repository: CustomStringConvertible {
var name: String
var url: String
init(name: String, url: String) {
self.name = name
self.url = url
}
var description: String {
return "\(name) | \(url)"
}
struct Colors {
static let OfflineColor = UIColor(red: 1.0, green: 0.6, blue: 0.6, alpha: 1.0)
static let OnlineColor = nil as UIColor?
}
enum SearchRepositoryResponse {
case Repositories([Repository])
case LimitExceeded
}
extension UINavigationController {
var rx_serviceState: AnyObserver<ServiceState?> {
return AnyObserver { event in
switch event {
case .Next(let maybeServiceState):
// if nil is being bound, then don't change color, it's not perfect, but :)
if let serviceState = maybeServiceState {
let isOffline = serviceState ?? .Online == .Offline
class GitHubSearchRepositoriesAPI {
static let sharedAPI = GitHubSearchRepositoriesAPI()
private init() {}
private static let parseLinksPattern = "\\s*,?\\s*<([^\\>]*)>\\s*;\\s*rel=\"([^\"]*)\""
private static let linksRegex = try! NSRegularExpression(pattern: parseLinksPattern, options: [.AllowCommentsAndWhitespace])
private static func parseLinks(links: String) throws -> [String: String] {
let length = (links as NSString).length
let matches = GitHubSearchRepositoriesAPI.linksRegex.matchesInString(links, options: NSMatchingOptions(), range: NSRange(location: 0, length: length))
var result: [String: String] = [:]
for m in matches {
let matches = (1 ..< m.numberOfRanges).map { rangeIndex -> String in
let range = m.rangeAtIndex(rangeIndex)
let startIndex = links.startIndex.advancedBy(range.location)
let endIndex = startIndex.advancedBy(range.length)
let stringRange = Range(start: startIndex, end: endIndex)
return links.substringWithRange(stringRange)
}
if matches.count != 2 {
throw exampleError("Error parsing links")
}
result[matches[1]] = matches[0]
}
return result
}
private static func parseNextURL(httpResponse: NSHTTPURLResponse) throws -> NSURL? {
guard let serializedLinks = httpResponse.allHeaderFields["Link"] as? String else {
return nil
}
let links = try GitHubSearchRepositoriesAPI.parseLinks(serializedLinks)
guard let nextPageURL = links["next"] else {
return nil
}
guard let nextUrl = NSURL(string: nextPageURL) else {
throw exampleError("Error parsing next url `\(nextPageURL)`")
}
return nextUrl
}
/**
Public fascade for search.
*/
func search(query: String, loadNextPageTrigger: Observable<Void>) -> Observable<SearchRepositoryResponse> {
let escapedQuery = URLEscape(query)
let url = NSURL(string: "https://api.github.com/search/repositories?q=\(escapedQuery)")!
return recursivelySearch([], loadNextURL: url, loadNextPageTrigger: loadNextPageTrigger)
.startWith(.Repositories([]))
}
private func recursivelySearch(loadedSoFar: [Repository], loadNextURL: NSURL, loadNextPageTrigger: Observable<Void>) -> Observable<SearchRepositoryResponse> {
return loadSearchURL(loadNextURL).flatMap { (newPageRepositoriesResponse, nextURL) -> Observable<SearchRepositoryResponse> in
// in case access denied, just stop
guard case .Repositories(let newPageRepositories) = newPageRepositoriesResponse else {
return just(newPageRepositoriesResponse)
}
var loadedRepositories = loadedSoFar
loadedRepositories.appendContentsOf(newPageRepositories)
// if next page can't be loaded, just return what was loaded, and stop
guard let nextURL = nextURL else {
return just(.Repositories(loadedRepositories))
}
return [
// return loaded immediately
just(.Repositories(loadedRepositories)),
// wait until next page can be loaded
never().takeUntil(loadNextPageTrigger),
// load next page
self.recursivelySearch(loadedRepositories, loadNextURL: nextURL, loadNextPageTrigger: loadNextPageTrigger)
].concat()
}
}
private func loadSearchURL(searchURL: NSURL) -> Observable<(response: SearchRepositoryResponse, nextURL: NSURL?)> {
return NSURLSession.sharedSession()
.rx_response(NSURLRequest(URL: searchURL))
.observeOn(Dependencies.sharedDependencies.backgroundWorkScheduler)
.map { data, response in
guard let httpResponse = response as? NSHTTPURLResponse else {
throw exampleError("not getting http response")
self.navigationBar.backgroundColor = isOffline
? Colors.OfflineColor
: Colors.OnlineColor
}
if httpResponse.statusCode == 403 {
return (response: .LimitExceeded, nextURL: nil)
}
let jsonRoot = try GitHubSearchRepositoriesAPI.parseJSON(httpResponse, data: data)
guard let json = jsonRoot as? [String: AnyObject] else {
throw exampleError("Casting to dictionary failed")
}
let repositories = try GitHubSearchRepositoriesAPI.parseRepositories(json)
let nextURL = try GitHubSearchRepositoriesAPI.parseNextURL(httpResponse)
return (response: .Repositories(repositories), nextURL: nextURL)
case .Error(let error):
bindingErrorToInterface(error)
case .Completed:
break
}
.observeOn(Dependencies.sharedDependencies.mainScheduler)
}
private static func parseJSON(httpResponse: NSHTTPURLResponse, data: NSData) throws -> AnyObject {
if !(200 ..< 300 ~= httpResponse.statusCode) {
throw exampleError("Call failed")
}
return try NSJSONSerialization.JSONObjectWithData(data ?? NSData(), options: [])
}
private static func parseRepositories(json: [String: AnyObject]) throws -> [Repository] {
guard let items = json["items"] as? [[String: AnyObject]] else {
throw exampleError("Can't find items")
}
return try items.map { item in
guard let name = item["name"] as? String,
url = item["url"] as? String else {
throw exampleError("Can't parse repository")
}
return Repository(name: name, url: url)
}
}
}
@ -177,12 +46,10 @@ class GitHubSearchRepositoriesViewController: ViewController, UITableViewDelegat
return contentOffset.y + tableView.frame.size.height + startLoadingOffset > tableView.contentSize.height
}
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var searchBar: UISearchBar!
var disposeBag = DisposeBag()
let repositories = Variable([Repository]())
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, Repository>>()
override func viewDidLoad() {
@ -193,11 +60,6 @@ class GitHubSearchRepositoriesViewController: ViewController, UITableViewDelegat
let tableView = self.tableView
let searchBar = self.searchBar
let allRepositories = repositories
.map { repositories in
return [SectionModel(model: "Repositories", items: repositories)]
}
dataSource.cellFactory = { (tv, ip, repository: Repository) in
let cell = tv.dequeueReusableCellWithIdentifier("Cell")!
cell.textLabel?.text = repository.name
@ -210,10 +72,6 @@ class GitHubSearchRepositoriesViewController: ViewController, UITableViewDelegat
return section.items.count > 0 ? "Repositories (\(section.items.count))" : "No repositories found"
}
// reactive data source
allRepositories
.bindTo(tableView.rx_itemsWithDataSource(dataSource))
.addDisposableTo(disposeBag)
let loadNextPageTrigger = tableView.rx_contentOffset
.flatMap { offset in
@ -222,27 +80,32 @@ class GitHubSearchRepositoriesViewController: ViewController, UITableViewDelegat
: empty()
}
searchBar.rx_text
let searchResult = searchBar.rx_text.asDriver()
.throttle(0.3, $.mainScheduler)
.distinctUntilChanged()
.map { query -> Observable<SearchRepositoryResponse> in
.flatMapLatest { query -> Driver<RepositoriesState> in
if query.isEmpty {
return just(.Repositories([]))
return Drive.just(RepositoriesState.empty)
} else {
return GitHubSearchRepositoriesAPI.sharedAPI.search(query, loadNextPageTrigger: loadNextPageTrigger)
.retry(3)
.catchErrorJustReturn(.Repositories([]))
.asDriver(onErrorJustReturn: RepositoriesState.empty)
}
}
.switchLatest()
.subscribeNext { [unowned self] result in
switch result {
case .Repositories(let repositories):
self.repositories.value = repositories
case .LimitExceeded:
self.repositories.value = []
showAlert("Exceeded limit of 10 non authenticated requests per minute for GitHub API. Please wait a minute. :(\nhttps://developer.github.com/v3/#rate-limiting")
}
searchResult
.map { $0.serviceState }
.drive(navigationController!.rx_serviceState)
.addDisposableTo(disposeBag)
searchResult
.map { [SectionModel(model: "Repositories", items: $0.repositories)] }
.drive(tableView.rx_itemsWithDataSource(dataSource))
.addDisposableTo(disposeBag)
searchResult
.flatMap { $0.limitExceeded ? Drive.just() : Drive.empty() }
.driveNext { n in
showAlert("Exceeded limit of 10 non authenticated requests per minute for GitHub API. Please wait a minute. :(\nhttps://developer.github.com/v3/#rate-limiting")
}
.addDisposableTo(disposeBag)
@ -258,6 +121,16 @@ class GitHubSearchRepositoriesViewController: ViewController, UITableViewDelegat
// so normal delegate customization can also be used
tableView.rx_setDelegate(self)
.addDisposableTo(disposeBag)
// activity indicator in status bar
// {
GitHubSearchRepositoriesAPI.sharedAPI.activityIndicator
.distinctUntilChanged()
.driveNext { active in
UIApplication.sharedApplication().networkActivityIndicatorVisible = active
}
.addDisposableTo(disposeBag)
// }
}
// MARK: Table view delegate
@ -265,4 +138,9 @@ class GitHubSearchRepositoriesViewController: ViewController, UITableViewDelegat
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 30
}
deinit {
// I know, I know, this isn't a good place of truth, but it's no
self.navigationController?.navigationBar.backgroundColor = nil
}
}

View File

@ -67,7 +67,7 @@ class CalculatorViewController: ViewController {
let CLEAR_STATE = CalState(previousNumber: nil, action: .Clear, currentNumber: "0", inScreen: "0", replace: true)
let diposeBag = DisposeBag()
let disposeBag = DisposeBag()
override func viewDidLoad() {
let commands:[Observable<Action>] = [
@ -98,7 +98,7 @@ class CalculatorViewController: ViewController {
]
commands
.asObservable()
.toObservable()
.merge()
.scan(CLEAR_STATE) { [unowned self] a, x in
return self.tranformState(a, x)
@ -122,7 +122,7 @@ class CalculatorViewController: ViewController {
self?.lastSignLabel.text = ""
}
}
.addDisposableTo(diposeBag)
.addDisposableTo(disposeBag)
}
func tranformState(a: CalState, _ x: Action) -> CalState {

View File

@ -58,13 +58,8 @@ class GitHubAPI {
})
return self.URLSession.rx_response(request)
.map { (maybeData, maybeResponse) in
if let response = maybeResponse as? NSHTTPURLResponse {
return response.statusCode == 404
}
else {
return false
}
.map { (maybeData, response) in
return response.statusCode == 404
}
.observeOn(self.dataScheduler)
.catchErrorJustReturn(false)
@ -73,8 +68,8 @@ class GitHubAPI {
func signup(username: String, password: String) -> Observable<SignupState> {
// this is also just a mock
let signupResult = SignupState.SignedUp(signedUp: arc4random() % 5 == 0 ? false : true)
return [just(signupResult), never()]
.concat()
return just(signupResult)
.concat(never())
.throttle(2, MainScheduler.sharedInstance)
.startWith(SignupState.SigningUp)
}

View File

@ -13,12 +13,30 @@ import RxSwift
import RxCocoa
#endif
let okColor = UIColor(red: 138.0 / 255.0, green: 221.0 / 255.0, blue: 109.0 / 255.0, alpha: 1.0)
let errorColor = UIColor.redColor()
typealias ValidationResult = (valid: Bool?, message: String?)
typealias ValidationObservable = Observable<ValidationResult>
// Two way binding operator between control property and variable, that's all it takes {
infix operator <-> {
}
func <-> <T>(property: ControlProperty<T>, variable: Variable<T>) -> Disposable {
let bindToUIDisposable = variable
.bindTo(property)
let bindToVariable = property
.subscribe(onNext: { n in
variable.value = n
}, onCompleted: {
bindToUIDisposable.dispose()
})
return StableCompositeDisposable.create(bindToUIDisposable, bindToVariable)
}
// }
class ValidationService {
let API: GitHubAPI
@ -30,7 +48,7 @@ class ValidationService {
let minPasswordCount = 5
func validateUsername(username: String) -> Observable<ValidationResult> {
func validateUsername(username: String) -> ValidationObservable {
if username.characters.count == 0 {
return just((false, nil))
}
@ -93,6 +111,15 @@ class GitHubSignupViewController : ViewController {
@IBOutlet weak var signupOutlet: UIButton!
@IBOutlet weak var signingUpOulet: UIActivityIndicatorView!
let username = Variable("")
let password = Variable("")
let repeatedPassword = Variable("")
struct ValidationColors {
static let okColor = UIColor(red: 138.0 / 255.0, green: 221.0 / 255.0, blue: 109.0 / 255.0, alpha: 1.0)
static let errorColor = UIColor.redColor()
}
var disposeBag = DisposeBag()
@ -108,9 +135,8 @@ class GitHubSignupViewController : ViewController {
let validationColor: UIColor
if let valid = v.valid {
validationColor = valid ? okColor : errorColor
}
else {
validationColor = valid ? ValidationColors.okColor : ValidationColors.errorColor
} else {
validationColor = UIColor.grayColor()
}
@ -128,44 +154,42 @@ class GitHubSignupViewController : ViewController {
super.viewDidLoad()
let tapBackground = UITapGestureRecognizer(target: self, action: Selector("dismissKeyboard:"))
tapBackground.numberOfTouchesRequired = 1
view.addGestureRecognizer(tapBackground)
self.disposeBag = DisposeBag()
let API = self.API
let validationService = ValidationService(API: API)
let username = usernameOutlet.rx_text
let password = passwordOutlet.rx_text
let repeatPassword = repeatedPasswordOutlet.rx_text
let signupSampler = self.signupOutlet.rx_tap
// bind UI values to variables {
usernameOutlet.rx_text <-> username
passwordOutlet.rx_text <-> password
repeatedPasswordOutlet.rx_text <-> repeatedPassword
// }
let signupSampler = signupOutlet.rx_tap
let usernameValidation = username
.map { username in
.flatMapLatest { username in
return validationService.validateUsername(username)
}
.switchLatest()
.shareReplay(1)
let passwordValidation = password
.map { password in
return validationService.validatePassword(password)
}
.shareReplay(1)
let repeatPasswordValidation = combineLatest(password, repeatPassword) { (password, repeatedPassword) in
let repeatPasswordValidation = combineLatest(password, repeatedPassword) { (password, repeatedPassword) in
validationService.validateRepeatedPassword(password, repeatedPassword: repeatedPassword)
}
.shareReplay(1)
let signingProcess = combineLatest(username, password) { ($0, $1) }
.sampleLatest(signupSampler)
.map { (username, password) in
.flatMapLatest { (username, password) in
return API.signup(username, password: password)
}
.switchLatest()
.startWith(SignupState.InitialState)
.shareReplay(1)
@ -174,8 +198,11 @@ class GitHubSignupViewController : ViewController {
passwordValidation,
repeatPasswordValidation,
signingProcess
) { un, p, pr, signingState in
return (un.valid ?? false) && (p.valid ?? false) && (pr.valid ?? false) && signingState != SignupState.SigningUp
) { username, password, repeatPassword, signingState in
return (username.valid ?? false) &&
(password.valid ?? false) &&
(repeatPassword.valid ?? false) &&
signingState != SignupState.SigningUp
}
bindValidationResultToUI(
@ -220,15 +247,17 @@ class GitHubSignupViewController : ViewController {
}
}
.addDisposableTo(disposeBag)
}
// This is one of the reasons why it's a good idea for disposal to be detached from allocations.
// If resources weren't disposed before view controller is being deallocated, signup alert view
// could be presented on top of wrong screen or crash your app if it was being presented while
// navigation stack is popping.
// could be presented on top of the wrong screen or could crash your app if it was being presented
// while navigation stack is popping.
// This will work well with UINavigationController, but has an assumption that view controller will
// never be readded as a child view controller.
// It it was readded UI wouldn't be bound anymore.
// never be added as a child view controller. If we didn't recreate the dispose bag here,
// then our resources would never be properly released.
override func willMoveToParentViewController(parent: UIViewController?) {
if let parent = parent {
assert(parent.isKindOfClass(UINavigationController), "Please read comments")

View File

@ -116,19 +116,9 @@ class PartialUpdatesViewController : ViewController {
skinTableViewDataSource(tvAnimatedDataSource)
skinTableViewDataSource(reloadDataSource)
let newSections = self.sections .skip(1)
let initialState = [Changeset.initialValue(self.sections.value)]
// reactive data sources
let updates = zip(self.sections, newSections) { (old, new) in
return differentiate(old, finalSections: new)
}
.startWith(initialState)
updates
.bindTo(partialUpdatesTableViewOutlet.rx_itemsWithDataSource(tvAnimatedDataSource))
self.sections
.bindTo(partialUpdatesTableViewOutlet.rx_itemsAnimatedWithDataSource(tvAnimatedDataSource))
.addDisposableTo(disposeBag)
self.sections

View File

@ -1,7 +1,7 @@
import Foundation
struct User: Equatable, CustomStringConvertible {
struct User: Equatable, CustomDebugStringConvertible {
var firstName: String
var lastName: String
@ -12,8 +12,10 @@ struct User: Equatable, CustomStringConvertible {
self.lastName = lastName
self.imageURL = imageURL
}
var description: String {
}
extension User {
var debugDescription: String {
get {
return firstName + " " + lastName
}

View File

@ -15,8 +15,8 @@ import RxCocoa
class SearchResultViewModel {
let searchResult: WikipediaSearchResult
var title: Observable<String>
var imageURLs: Observable<[NSURL]>
var title: Driver<String>
var imageURLs: Driver<[NSURL]>
let API = DefaultWikipediaAPI.sharedAPI
let $: Dependencies = Dependencies.sharedDependencies
@ -24,13 +24,13 @@ class SearchResultViewModel {
init(searchResult: WikipediaSearchResult) {
self.searchResult = searchResult
self.title = never()
self.imageURLs = never()
self.title = Drive.never()
self.imageURLs = Drive.never()
let URLs = configureImageURLs()
self.imageURLs = URLs.catchErrorJustReturn([])
self.title = configureTitle(URLs).catchErrorJustReturn("Error during fetching")
self.imageURLs = URLs.asDriver(onErrorJustReturn: [])
self.title = configureTitle(URLs).asDriver(onErrorJustReturn: "Error during fetching")
}
// private methods
@ -51,6 +51,7 @@ class SearchResultViewModel {
return "\(searchResult.title) loading ..."
}
}
.retryOnBecomesReachable("⚠️ Service offline ⚠️", reachabilityService: ReachabilityService.sharedReachabilityService)
}
func configureImageURLs() -> Observable<[NSURL]> {
@ -64,7 +65,5 @@ class SearchResultViewModel {
return []
}
}
.observeOn($.mainScheduler)
.shareReplay(1)
}
}

View File

@ -15,14 +15,14 @@ import RxCocoa
class SearchViewModel {
// outputs
let rows: Observable<[SearchResultViewModel]>
let rows: Driver<[SearchResultViewModel]>
let subscriptions = DisposeBag()
// public methods
init(searchText: Observable<String>,
selectedResult: Observable<SearchResultViewModel>) {
init(searchText: Driver<String>,
selectedResult: Driver<SearchResultViewModel>) {
let $: Dependencies = Dependencies.sharedDependencies
let wireframe = Dependencies.sharedDependencies.wireframe
@ -31,13 +31,13 @@ class SearchViewModel {
self.rows = searchText
.throttle(0.3, $.mainScheduler)
.distinctUntilChanged()
.map { query in
.flatMapLatest { query in
API.getSearchResults(query)
.retry(3)
.retryOnBecomesReachable([], reachabilityService: ReachabilityService.sharedReachabilityService)
.startWith([]) // clears results on new search term
.catchErrorJustReturn([])
.asDriver(onErrorJustReturn: [])
}
.switchLatest()
.map { results in
results.map {
SearchResultViewModel(
@ -47,7 +47,7 @@ class SearchViewModel {
}
selectedResult
.subscribeNext { searchResult in
.driveNext { searchResult in
wireframe.openURL(searchResult.searchResult.URL)
}
.addDisposableTo(subscriptions)

View File

@ -17,15 +17,16 @@ public class CollectionViewImageCell: UICollectionViewCell {
@IBOutlet var imageOutlet: UIImageView!
var disposeBag: DisposeBag!
var image: Observable<UIImage!>! {
didSet {
var downloadableImage: Observable<DownloadableImage>?{
didSet{
let disposeBag = DisposeBag()
self.image
.subscribe(imageOutlet.rx_imageAnimated(kCATransitionFade))
self.downloadableImage?
.asDriver(onErrorJustReturn: DownloadableImage.OfflinePlaceholder)
.drive(imageOutlet.rxex_downloadableImageAnimated(kCATransitionFade))
.addDisposableTo(disposeBag)
self.disposeBag = disposeBag
}
}

View File

@ -33,21 +33,16 @@ public class WikipediaSearchCell: UITableViewCell {
didSet {
let disposeBag = DisposeBag()
(viewModel?.title ?? just(""))
.subscribe(self.titleOutlet.rx_text)
(viewModel?.title ?? Drive.just(""))
.drive(self.titleOutlet.rx_text)
.addDisposableTo(disposeBag)
self.URLOutlet.text = viewModel.searchResult.URL.absoluteString ?? ""
viewModel.imageURLs
.bindTo(self.imagesOutlet.rx_itemsWithCellIdentifier("ImageCell")) { [unowned self] (_, URL, cell: CollectionViewImageCell) in
let loadingPlaceholder: UIImage? = nil
cell.image = self.imageService.imageFromURL(URL)
.map { $0 as UIImage? }
.catchErrorJustReturn(nil)
.startWith(loadingPlaceholder)
}
.drive(self.imagesOutlet.rx_itemsWithCellIdentifier("ImageCell")) { [unowned self] (_, URL, cell: CollectionViewImageCell) in
cell.downloadableImage = self.imageService.imageFromURL(URL)
}
.addDisposableTo(disposeBag)
self.disposeBag = disposeBag
@ -62,4 +57,5 @@ public class WikipediaSearchCell: UITableViewCell {
deinit {
}
}

View File

@ -33,17 +33,15 @@ class WikipediaSearchViewController: ViewController {
resultsTableView.rowHeight = 194
let selectedResult: Observable<SearchResultViewModel> = resultsTableView.rx_modelSelected().asObservable()
let viewModel = SearchViewModel(
searchText: searchBar.rx_text.asObservable(),
selectedResult: selectedResult
searchText: searchBar.rx_text.asDriver(),
selectedResult: resultsTableView.rx_modelSelected().asDriver()
)
// map table view rows
// {
viewModel.rows
.bindTo(resultsTableView.rx_itemsWithCellIdentifier("WikipediaSearchCell")) { (_, viewModel, cell: WikipediaSearchCell) in
.drive(resultsTableView.rx_itemsWithCellIdentifier("WikipediaSearchCell")) { (_, viewModel, cell: WikipediaSearchCell) in
cell.viewModel = viewModel
}
.addDisposableTo(disposeBag)
@ -52,7 +50,8 @@ class WikipediaSearchViewController: ViewController {
// dismiss keyboard on scroll
// {
resultsTableView.rx_contentOffset
.subscribeNext { _ in
.asDriver()
.driveNext { _ in
if searchBar.isFirstResponder() {
_ = searchBar.resignFirstResponder()
}
@ -61,5 +60,18 @@ class WikipediaSearchViewController: ViewController {
self.viewModel = viewModel
// }
// activity indicator spinner
// {
combineLatest(
DefaultWikipediaAPI.sharedAPI.loadingWikipediaData,
DefaultImageService.sharedImageService.loadingImage
) { $0 || $1 }
.distinctUntilChanged()
.driveNext { active in
UIApplication.sharedApplication().networkActivityIndicatorVisible = active
}
.addDisposableTo(disposeBag)
// }
}
}

View File

@ -32,17 +32,24 @@ class DefaultWikipediaAPI: WikipediaAPI {
static let sharedAPI = DefaultWikipediaAPI() // Singleton
let $: Dependencies = Dependencies.sharedDependencies
let loadingWikipediaData = ActivityIndicator()
private init() {}
private func rx_JSON(URL: NSURL) -> Observable<AnyObject!> {
return $.URLSession
.rx_JSON(URL)
.trackActivity(loadingWikipediaData)
}
// Example wikipedia response http://en.wikipedia.org/w/api.php?action=opensearch&search=Rx
func getSearchResults(query: String) -> Observable<[WikipediaSearchResult]> {
let escapedQuery = URLEscape(query)
let urlContent = "http://en.wikipedia.org/w/api.php?action=opensearch&search=\(escapedQuery)"
let url = NSURL(string: urlContent)!
return $.URLSession
.rx_JSON(url)
return rx_JSON(url)
.observeOn($.backgroundWorkScheduler)
.map { json in
guard let json = json as? [AnyObject] else {
@ -61,7 +68,7 @@ class DefaultWikipediaAPI: WikipediaAPI {
return failWith(apiError("Can't create url"))
}
return $.URLSession.rx_JSON(url)
return rx_JSON(url)
.map { jsonResult in
guard let json = jsonResult as? NSDictionary else {
throw exampleError("Parsing error")

View File

@ -11,7 +11,7 @@ import Foundation
import RxSwift
#endif
struct WikipediaSearchResult: CustomStringConvertible {
struct WikipediaSearchResult: CustomDebugStringConvertible {
let title: String
let description: String
let URL: NSURL
@ -52,3 +52,9 @@ struct WikipediaSearchResult: CustomStringConvertible {
return searchResults
}
}
extension WikipediaSearchResult {
var debugDescription: String {
return "[\(title)](\(URL))"
}
}

View File

@ -0,0 +1,85 @@
//
// ActivityIndicator.swift
// RxExample
//
// Created by Krunoslav Zaher on 10/18/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
import RxCocoa
#endif
struct ActivityToken<E> : ObservableConvertibleType, Disposable {
private let _source: Observable<E>
private let _dispose: AnonymousDisposable
init(source: Observable<E>, disposeAction: () -> ()) {
_source = source
_dispose = AnonymousDisposable(disposeAction)
}
func dispose() {
_dispose.dispose()
}
func asObservable() -> Observable<E> {
return _source
}
}
/**
Enables monitoring of sequence computation.
If there is at least one sequence computation in progress, `true` will be sent.
When all activities complete `false` will be sent.
*/
class ActivityIndicator : DriverConvertibleType {
typealias E = Bool
private let _lock = NSRecursiveLock()
private let _variable = Variable(0)
private let _loading: Driver<Bool>
init() {
_loading = _variable
.map { $0 > 0 }
.asDriver { (error: ErrorType) -> Driver<Bool> in
_ = fatalError("Loader can't fail")
return Drive.empty()
}
}
func trackActivity<O: ObservableConvertibleType>(source: O) -> Observable<O.E> {
return using({ () -> ActivityToken<O.E> in
self.increment()
return ActivityToken(source: source.asObservable(), disposeAction: self.decrement)
}) { t in
return t.asObservable()
}
}
private func increment() {
_lock.lock()
_variable.value = _variable.value + 1
_lock.unlock()
}
private func decrement() {
_lock.lock()
_variable.value = _variable.value - 1
_lock.unlock()
}
func asDriver() -> Driver<E> {
return _loading
}
}
extension ObservableConvertibleType {
func trackActivity(activityIndicator: ActivityIndicator) -> Observable<E> {
return activityIndicator.trackActivity(self)
}
}

View File

@ -0,0 +1,23 @@
//
// DownloadableImage.swift
// RxExample
//
// Created by Vodovozov Gleb on 31.10.2015.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if !RX_NO_MODULE
import RxSwift
#endif
#if os(iOS)
import UIKit
#elseif os(OSX)
import Cocoa
#endif
enum DownloadableImage{
case Content(image:Image)
case OfflinePlaceholder
}

View File

@ -19,7 +19,7 @@ import RxCocoa
#endif
protocol ImageService {
func imageFromURL(URL: NSURL) -> Observable<Image>
func imageFromURL(URL: NSURL) -> Observable<DownloadableImage>
}
class DefaultImageService: ImageService {
@ -29,19 +29,21 @@ class DefaultImageService: ImageService {
let $: Dependencies = Dependencies.sharedDependencies
// 1st level cache
let imageCache = NSCache()
private let _imageCache = NSCache()
// 2nd level cache
let imageDataCache = NSCache()
private let _imageDataCache = NSCache()
let loadingImage = ActivityIndicator()
private init() {
// cost is approx memory usage
self.imageDataCache.totalCostLimit = 10 * MB
_imageDataCache.totalCostLimit = 10 * MB
self.imageCache.countLimit = 20
_imageCache.countLimit = 20
}
func decodeImage(imageData: NSData) -> Observable<Image> {
private func decodeImage(imageData: NSData) -> Observable<Image> {
return just(imageData)
.observeOn($.backgroundWorkScheduler)
.map { data in
@ -49,41 +51,56 @@ class DefaultImageService: ImageService {
// some error
throw apiError("Decoding image error")
}
return image
return image.forceLazyImageDecompression()
}
.observeOn($.mainScheduler)
}
func imageFromURL(URL: NSURL) -> Observable<Image> {
private func _imageFromURL(URL: NSURL) -> Observable<Image> {
return deferred {
let maybeImage = self.imageCache.objectForKey(URL) as? Image
let decodedImage: Observable<Image>
// best case scenario, it's already decoded an in memory
if let image = maybeImage {
decodedImage = just(image)
}
else {
let cachedData = self.imageDataCache.objectForKey(URL) as? NSData
let maybeImage = self._imageCache.objectForKey(URL) as? Image
let decodedImage: Observable<Image>
// does image data cache contain anything
if let cachedData = cachedData {
decodedImage = self.decodeImage(cachedData)
// best case scenario, it's already decoded an in memory
if let image = maybeImage {
decodedImage = just(image)
}
else {
// fetch from network
decodedImage = self.$.URLSession.rx_data(NSURLRequest(URL: URL))
.doOn(next: { data in
self.imageDataCache.setObject(data, forKey: URL)
})
.flatMap(self.decodeImage)
let cachedData = self._imageDataCache.objectForKey(URL) as? NSData
// does image data cache contain anything
if let cachedData = cachedData {
decodedImage = self.decodeImage(cachedData)
}
else {
// fetch from network
decodedImage = self.$.URLSession.rx_data(NSURLRequest(URL: URL))
.doOn(onNext: { data in
self._imageDataCache.setObject(data, forKey: URL)
})
.flatMap(self.decodeImage)
.trackActivity(self.loadingImage)
}
}
return decodedImage.doOn(onNext: { image in
self._imageCache.setObject(image, forKey: URL)
})
}
return decodedImage.doOn(next: { image in
self.imageCache.setObject(image, forKey: URL)
})
}.observeOn($.mainScheduler)
}
/**
Service that tries to download image from URL.
In case there were some problems with network connectivity and image wasn't downloaded, automatic retry will be fired when networks becomes
available.
After image is sucessfully downloaded, sequence is completed.
*/
func imageFromURL(URL: NSURL) -> Observable<DownloadableImage> {
return _imageFromURL(URL)
.map { DownloadableImage.Content(image: $0) }
.retryOnBecomesReachable(DownloadableImage.OfflinePlaceholder, reachabilityService: ReachabilityService.sharedReachabilityService)
.startWith(.Content(image: Image()))
}
}

View File

@ -0,0 +1,388 @@
/*
Copyright (c) 2014, Ashley Mills
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import SystemConfiguration
import Foundation
enum ReachabilityError: ErrorType {
case FailedToCreateWithAddress(sockaddr_in)
case FailedToCreateWithHostname(String)
case UnableToSetCallback
case UnableToSetDispatchQueue
}
public let ReachabilityChangedNotification = "ReachabilityChangedNotification"
func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutablePointer<Void>) {
let reachability = Unmanaged<Reachability>.fromOpaque(COpaquePointer(info)).takeUnretainedValue()
dispatch_async(dispatch_get_main_queue()) {
reachability.reachabilityChanged(flags)
}
}
public class Reachability: NSObject {
public typealias NetworkReachable = (Reachability) -> ()
public typealias NetworkUnreachable = (Reachability) -> ()
public enum NetworkStatus: CustomStringConvertible {
case NotReachable, ReachableViaWiFi, ReachableViaWWAN
public var description: String {
switch self {
case .ReachableViaWWAN:
return "Cellular"
case .ReachableViaWiFi:
return "WiFi"
case .NotReachable:
return "No Connection"
}
}
}
// MARK: - *** Public properties ***
public var whenReachable: NetworkReachable?
public var whenUnreachable: NetworkUnreachable?
public var reachableOnWWAN: Bool
public var notificationCenter = NSNotificationCenter.defaultCenter()
public var currentReachabilityStatus: NetworkStatus {
if isReachable() {
if isReachableViaWiFi() {
return .ReachableViaWiFi
}
if isRunningOnDevice {
return .ReachableViaWWAN
}
}
return .NotReachable
}
public var currentReachabilityString: String {
return "\(currentReachabilityStatus)"
}
// MARK: - *** Initialisation methods ***
required public init(reachabilityRef: SCNetworkReachability) {
reachableOnWWAN = true
self.reachabilityRef = reachabilityRef
}
public convenience init(hostname: String) throws {
let nodename = (hostname as NSString).UTF8String
guard let ref = SCNetworkReachabilityCreateWithName(nil, nodename) else { throw ReachabilityError.FailedToCreateWithHostname(hostname) }
self.init(reachabilityRef: ref)
}
public class func reachabilityForInternetConnection() throws -> Reachability {
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(sizeofValue(zeroAddress))
zeroAddress.sin_family = sa_family_t(AF_INET)
guard let ref = withUnsafePointer(&zeroAddress, {
SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
}) else { throw ReachabilityError.FailedToCreateWithAddress(zeroAddress) }
return Reachability(reachabilityRef: ref)
}
public class func reachabilityForLocalWiFi() throws -> Reachability {
var localWifiAddress: sockaddr_in = sockaddr_in(sin_len: __uint8_t(0), sin_family: sa_family_t(0), sin_port: in_port_t(0), sin_addr: in_addr(s_addr: 0), sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
localWifiAddress.sin_len = UInt8(sizeofValue(localWifiAddress))
localWifiAddress.sin_family = sa_family_t(AF_INET)
// IN_LINKLOCALNETNUM is defined in <netinet/in.h> as 169.254.0.0
let address: UInt32 = 0xA9FE0000
localWifiAddress.sin_addr.s_addr = in_addr_t(address.bigEndian)
guard let ref = withUnsafePointer(&localWifiAddress, {
SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
}) else { throw ReachabilityError.FailedToCreateWithAddress(localWifiAddress) }
return Reachability(reachabilityRef: ref)
}
// MARK: - *** Notifier methods ***
public func startNotifier() throws {
if notifierRunning { return }
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = UnsafeMutablePointer(Unmanaged.passUnretained(self).toOpaque())
if !SCNetworkReachabilitySetCallback(reachabilityRef!, callback, &context) {
stopNotifier()
throw ReachabilityError.UnableToSetCallback
}
if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef!, reachabilitySerialQueue) {
stopNotifier()
throw ReachabilityError.UnableToSetDispatchQueue
}
notifierRunning = true
}
public func stopNotifier() {
if let reachabilityRef = reachabilityRef {
SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil)
}
notifierRunning = false
}
// MARK: - *** Connection test methods ***
public func isReachable() -> Bool {
return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in
return self.isReachableWithFlags(flags)
})
}
public func isReachableViaWWAN() -> Bool {
if isRunningOnDevice {
return isReachableWithTest() { flags -> Bool in
// Check we're REACHABLE
if self.isReachable(flags) {
// Now, check we're on WWAN
if self.isOnWWAN(flags) {
return true
}
}
return false
}
}
return false
}
public func isReachableViaWiFi() -> Bool {
return isReachableWithTest() { flags -> Bool in
// Check we're reachable
if self.isReachable(flags) {
if self.isRunningOnDevice {
// Check we're NOT on WWAN
if self.isOnWWAN(flags) {
return false
}
}
return true
}
return false
}
}
// MARK: - *** Private methods ***
private var isRunningOnDevice: Bool = {
#if (arch(i386) || arch(x86_64)) && os(iOS)
return false
#else
return true
#endif
}()
private var notifierRunning = false
private var reachabilityRef: SCNetworkReachability?
private let reachabilitySerialQueue = dispatch_queue_create("uk.co.ashleymills.reachability", DISPATCH_QUEUE_SERIAL)
private func reachabilityChanged(flags: SCNetworkReachabilityFlags) {
if isReachableWithFlags(flags) {
if let block = whenReachable {
block(self)
}
} else {
if let block = whenUnreachable {
block(self)
}
}
notificationCenter.postNotificationName(ReachabilityChangedNotification, object:self)
}
private func isReachableWithFlags(flags: SCNetworkReachabilityFlags) -> Bool {
let reachable = isReachable(flags)
if !reachable {
return false
}
if isConnectionRequiredOrTransient(flags) {
return false
}
if isRunningOnDevice {
if isOnWWAN(flags) && !reachableOnWWAN {
// We don't want to connect when on 3G.
return false
}
}
return true
}
private func isReachableWithTest(test: (SCNetworkReachabilityFlags) -> (Bool)) -> Bool {
if let reachabilityRef = reachabilityRef {
var flags = SCNetworkReachabilityFlags(rawValue: 0)
let gotFlags = withUnsafeMutablePointer(&flags) {
SCNetworkReachabilityGetFlags(reachabilityRef, UnsafeMutablePointer($0))
}
if gotFlags {
return test(flags)
}
}
return false
}
// WWAN may be available, but not active until a connection has been established.
// WiFi may require a connection for VPN on Demand.
private func isConnectionRequired() -> Bool {
return connectionRequired()
}
private func connectionRequired() -> Bool {
return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in
return self.isConnectionRequired(flags)
})
}
// Dynamic, on demand connection?
private func isConnectionOnDemand() -> Bool {
return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in
return self.isConnectionRequired(flags) && self.isConnectionOnTrafficOrDemand(flags)
})
}
// Is user intervention required?
private func isInterventionRequired() -> Bool {
return isReachableWithTest({ (flags: SCNetworkReachabilityFlags) -> (Bool) in
return self.isConnectionRequired(flags) && self.isInterventionRequired(flags)
})
}
private func isOnWWAN(flags: SCNetworkReachabilityFlags) -> Bool {
#if os(iOS)
return flags.contains(.IsWWAN)
#else
return false
#endif
}
private func isReachable(flags: SCNetworkReachabilityFlags) -> Bool {
return flags.contains(.Reachable)
}
private func isConnectionRequired(flags: SCNetworkReachabilityFlags) -> Bool {
return flags.contains(.ConnectionRequired)
}
private func isInterventionRequired(flags: SCNetworkReachabilityFlags) -> Bool {
return flags.contains(.InterventionRequired)
}
private func isConnectionOnTraffic(flags: SCNetworkReachabilityFlags) -> Bool {
return flags.contains(.ConnectionOnTraffic)
}
private func isConnectionOnDemand(flags: SCNetworkReachabilityFlags) -> Bool {
return flags.contains(.ConnectionOnDemand)
}
func isConnectionOnTrafficOrDemand(flags: SCNetworkReachabilityFlags) -> Bool {
return !flags.intersect([.ConnectionOnTraffic, .ConnectionOnDemand]).isEmpty
}
private func isTransientConnection(flags: SCNetworkReachabilityFlags) -> Bool {
return flags.contains(.TransientConnection)
}
private func isLocalAddress(flags: SCNetworkReachabilityFlags) -> Bool {
return flags.contains(.IsLocalAddress)
}
private func isDirect(flags: SCNetworkReachabilityFlags) -> Bool {
return flags.contains(.IsDirect)
}
private func isConnectionRequiredOrTransient(flags: SCNetworkReachabilityFlags) -> Bool {
let testcase:SCNetworkReachabilityFlags = [.ConnectionRequired, .TransientConnection]
return flags.intersect(testcase) == testcase
}
private var reachabilityFlags: SCNetworkReachabilityFlags {
if let reachabilityRef = reachabilityRef {
var flags = SCNetworkReachabilityFlags(rawValue: 0)
let gotFlags = withUnsafeMutablePointer(&flags) {
SCNetworkReachabilityGetFlags(reachabilityRef, UnsafeMutablePointer($0))
}
if gotFlags {
return flags
}
}
return []
}
override public var description: String {
var W: String
if isRunningOnDevice {
W = isOnWWAN(reachabilityFlags) ? "W" : "-"
} else {
W = "X"
}
let R = isReachable(reachabilityFlags) ? "R" : "-"
let c = isConnectionRequired(reachabilityFlags) ? "c" : "-"
let t = isTransientConnection(reachabilityFlags) ? "t" : "-"
let i = isInterventionRequired(reachabilityFlags) ? "i" : "-"
let C = isConnectionOnTraffic(reachabilityFlags) ? "C" : "-"
let D = isConnectionOnDemand(reachabilityFlags) ? "D" : "-"
let l = isLocalAddress(reachabilityFlags) ? "l" : "-"
let d = isDirect(reachabilityFlags) ? "d" : "-"
return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)"
}
deinit {
stopNotifier()
reachabilityRef = nil
whenReachable = nil
whenUnreachable = nil
}
}

View File

@ -0,0 +1,56 @@
//
// ReachabilityService.swift
// RxExample
//
// Created by Vodovozov Gleb on 22.10.2015.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
#if !RX_NO_MODULE
import RxSwift
#endif
public enum ReachabilityStatus {
case Reachable, Unreachable
}
class ReachabilityService {
private let reachabilityRef = try! Reachability.reachabilityForInternetConnection()
private let _reachabilityChangedSubject = PublishSubject<ReachabilityStatus>()
private var reachabilityChanged: Observable<ReachabilityStatus> {
get {
return _reachabilityChangedSubject.asObservable()
}
}
// singleton
static let sharedReachabilityService = ReachabilityService()
init(){
reachabilityRef.whenReachable = { reachability in
self._reachabilityChangedSubject.on(.Next(.Reachable))
}
reachabilityRef.whenUnreachable = { reachability in
self._reachabilityChangedSubject.on(.Next(.Unreachable))
}
try! reachabilityRef.startNotifier()
}
}
extension ObservableConvertibleType {
func retryOnBecomesReachable(valueOnFailure:E, reachabilityService: ReachabilityService) -> Observable<E> {
return self.asObservable()
.catchError { (e) -> Observable<E> in
reachabilityService.reachabilityChanged
.filter { $0 == .Reachable }
.flatMap { _ in failWith(e) }
.startWith(valueOnFailure)
}
.retry()
}
}

View File

@ -0,0 +1,23 @@
//
// UIImage+Extensions.swift
// RxExample
//
// Created by Krunoslav Zaher on 11/1/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
import Foundation
#if os(iOS)
import UIKit
#endif
extension Image {
func forceLazyImageDecompression() -> Image {
#if os(iOS)
UIGraphicsBeginImageContext(CGSizeMake(1, 1))
self.drawAtPoint(CGPointZero)
UIGraphicsEndImageContext()
#endif
return self
}
}

Some files were not shown because too many files have changed in this diff Show More