diff --git a/CHANGELOG.md b/CHANGELOG.md index fc019768..3210febe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,50 @@ All notable changes to this project will be documented in this file. --- +## [2.2.0](https://github.com/ReactiveX/RxSwift/releases/tag/2.2.0) + +#### Public Interface anomalies + +* Fixes problem with `timer` operator. Changes return type from `Observable` to `Observable`. This could potentially cause code breakage, but it was an API anomaly. +* Curried functions were marked deprecated so they were replaced in `UITableView` and `UICollectionView` extensions with equivalent lambdas. This shouldn't break anyone's code, but it is a change in public interface. + +This is example of those changes: + +```swift +- public func rx_itemsWithCellFactory + (source: O) + (cellFactory: (UITableView, Int, S.Generator.Element) -> UITableViewCell) -> Disposable ++ public func rx_itemsWithCellFactory + (source: O) + -> (cellFactory: (UITableView, Int, S.Generator.Element) -> UITableViewCell) -> Disposable +``` + +* Fixes anomaly in `CLLocationManager` extensions + +```swift +- public var rx_didFinishDeferredUpdatesWithError: RxSwift.Observable { get } ++ public var rx_didFinishDeferredUpdatesWithError: RxSwift.Observable { get } +``` + +#### Features + +* Adds `UIBindingObserver`. +* Adds `doOnNext` convenience operator (also added to `Driver`). +* Adds `doOnError` convenience operator. +* Adds `doOnCompleted` convenience operator (also added to `Driver`). +* Adds `skip`, `startWith` to `Driver`. +* Adds `rx_active` extension to `NSLayoutConstraint`. +* Adds `rx_refreshing` extension to `UIRefreshControl`. +* Adds `interval` and `timer` to `Driver`. +* Adds `rx_itemAccessoryButtonTapped` to `UITableView` extensions. +* Adds `rx_networkActivityIndicatorVisible` to `UIApplication`. +* Adds `rx_selected` to `UIControl`. + +#### Anomalies + +* Fixes anomaly with registering multiple observers to `UIBarButtonItem`. +* Fixes anomaly with blocking operators possibly over-stopping the `RunLoop`. + ## [2.1.0](https://github.com/ReactiveX/RxSwift/releases/tag/2.1.0) #### Features @@ -20,7 +64,7 @@ All notable changes to this project will be documented in this file. #### Anomalies * Removes usage of `OSSpinLock`s from all `Darwin` platforms because of problems with inversion of priority on iOS. [Original thread on swift mailing list is here](https://lists.swift.org/pipermail/swift-dev/Week-of-Mon-20151214/000321.html) -* Reduces verbose output from `RxCocoa` project in debug mode. `TRACE_RESOURCES` is now also treated as a verbosity level setting. It is possible to get old output by using `TRACE_RESOURCES` with verbosity level `>= 2`. [#397](https://github.com/ReactiveX/RxSwift/issues/397) +* Reduces verbose output from `RxCocoa` project in debug mode. `TRACE_RESOURCES` is now also treated as a verbosity level setting. It is possible to get old output by using `TRACE_RESOURCES` with verbosity level `>= 2`. [#397](https://github.com/ReactiveX/RxSwift/issues/397) * Fixes anomaly with logging of HTTP body of requests in `RxCocoa` project. ## [2.0.0](https://github.com/ReactiveX/RxSwift/releases/tag/2.0.0) diff --git a/Documentation/ComparisonWithOtherLibraries.md b/Documentation/ComparisonWithOtherLibraries.md new file mode 100644 index 00000000..03b3a660 --- /dev/null +++ b/Documentation/ComparisonWithOtherLibraries.md @@ -0,0 +1,19 @@ +## Comparison with ReactiveCocoa + +RxSwift is somewhat similar to ReactiveCocoa since ReactiveCocoa borrows a large number of concepts from Rx. + +One of the main goals of this project was to create a significantly simpler interface that is more aligned with other Rx implementations, offers richer concurrency model, offers more optimization opportunities and is more aligned with built-in Swift error handling mechanisms. + +We've also decided to only rely on Swift/llvm compiler and not introduce any external dependencies. + +Probably the main difference between these projects is the difference of approach in building abstractions. + +The main goal of RxSwift project is to provide environment agnostic compositional computation glue abstracted in the form of observable sequences. +We then aim to improve the experience of using RxSwift on specific platforms. To do this RxCocoa uses those generic computations to build more practical abstractions and wrap Foundation/Cocoa/UKit frameworks. That means that other libraries give context and semantics to the generic computation engine RxSwift provides such as `Driver`, `ControlProperty`, `ControlEvent`s and more. + +One of the benefits to representing all these abstractions as a single concept; ​_observable sequences_​, is that all computation abstractions built on top of them are also composable in the same fundamental way. They all follow the same contract and implement the same interface. + It is also easy to create flexible subscription (resource) sharing strategies or use one of the built-in ones: `share`, `shareReplay`, `publish`, `multicast`, `shareReplayLatestWhileConnected`... + +This library also offers fine-tunable concurrency model. In case concurrent schedulers are used, observable sequence operators will preserve sequence properties. The same observable sequence operators will also know how to detect and optimally use known serial schedulers. ReactiveCocoa has a more limited concurrency model and only allows serial schedulers. + +Multithreaded programming is really hard and detecting non trivial loops is even harder. That's why all operators are built in a fault tolerant way. Even in case element generation occurs during element processing (recursion), operators will try to handle that situation and prevent deadlocks. That means that in worst possible case programming error will cause stack overflow, but users won't have to manually kill the app, and you will get crash report in error reporting systems so you can find and fix the problem. diff --git a/Documentation/ExampleApp.md b/Documentation/ExampleApp.md new file mode 100644 index 00000000..9e649d2c --- /dev/null +++ b/Documentation/ExampleApp.md @@ -0,0 +1,13 @@ + +## RxExamples + +To run the example app: + +* Open `Rx.xcworkspace` +* Choose one of example schemes (RxExample-iOS, RxExample-OSX) and hit `Run`. + +You can also run the example app using CocoaPods. + +``` +pod try RxSwift +``` diff --git a/Documentation/Examples.md b/Documentation/Examples.md index 1817db49..ae81a65b 100644 --- a/Documentation/Examples.md +++ b/Documentation/Examples.md @@ -5,7 +5,7 @@ Examples 1. [Simple UI bindings](#simple-ui-bindings) 1. [Autocomplete](#autocomplete) 1. [more examples](../RxExample) -1. [Playgrounds](../README.md#playgrounds) +1. [Playgrounds](Playgrounds.md) ## Calculated variable diff --git a/Documentation/Installation.md b/Documentation/Installation.md new file mode 100644 index 00000000..2be43295 --- /dev/null +++ b/Documentation/Installation.md @@ -0,0 +1,56 @@ +## Build / Install / Run + +Rx doesn't contain any external dependencies. + +These are currently supported options: + +### Manual + +Open Rx.xcworkspace, choose `RxExample` and hit run. This method will build everything and run sample app + +### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html) + +**:warning: IMPORTANT! For tvOS support CocoaPods `0.39` is required. :warning:** + +``` +# Podfile +use_frameworks! + +target 'YOUR_TARGET_NAME' do + pod 'RxSwift', '~> 2.0' + pod 'RxCocoa', '~> 2.0' + pod 'RxBlocking', '~> 2.0' + pod 'RxTests', '~> 2.0' +end +``` + +replace `YOUR_TARGET_NAME`, then type in the `Podfile` directory: + +``` +$ pod install +``` + +### [Carthage](https://github.com/Carthage/Carthage) + +**Xcode 7.1 required** + +Add this to `Cartfile` + +``` +github "ReactiveX/RxSwift" ~> 2.0 +``` + +``` +$ carthage update +``` + +### Manually using git submodules + +* Add RxSwift as a submodule + +``` +$ git submodule add git@github.com:ReactiveX/RxSwift.git +``` + +* Drag `Rx.xcodeproj` into Project Navigator +* Go to `Project > Targets > Build Phases > Link Binary With Libraries`, click `+` and select `RxSwift-[Platform]` and `RxCocoa-[Platform]` targets diff --git a/Documentation/IssueTemplate.md b/Documentation/IssueTemplate.md new file mode 100644 index 00000000..6967f334 --- /dev/null +++ b/Documentation/IssueTemplate.md @@ -0,0 +1,47 @@ +**Please copy the following template [here](https://github.com/ReactiveX/RxSwift/issues/new) and fill in the missing fields so we can help you as soon as possible.** + +**If you don't have something to report in the following format, maybe you don't really have an issue an it's easier and faster to resolve doubts in [slack channel](http://http://slack.rxswift.org/) first.** + +``` +*Short description*: + + description here + +*Code that reproduces the issue*: + + code goes here + +*Xcode version*: + + Xcode version goes here + +*Expected outcome*: + + what do you expect to happen goes here + +*What actually happens*: + + what actually happens goes here + +// filling additional information below is optional, but resolving your issue could be potentially a lot faster + +*Integration method*: + (so we don't lose time investigating wrong integration) + * CocoaPods + * Carthage + * Git submodules + +*I have multiple versions of Xcode installed*: + (so we can understand can this cause your issue) + * yes (which ones) + * no + +*How long have I been using this project*: + (this is so we can understand your level of knowledge + and formulate the response in an appropriate manner) + * just starting + * I have a small code base + * I have a significant code base + + +``` diff --git a/Documentation/MathBehindRx.md b/Documentation/MathBehindRx.md index 847844e0..dfa44038 100644 --- a/Documentation/MathBehindRx.md +++ b/Documentation/MathBehindRx.md @@ -3,19 +3,18 @@ Math Behind Rx ## Duality between Observer and Iterator / Enumerator / Generator / Sequences -There is a duality between observer and generator pattern. That's what enables transition from async callback world to synchronous world of sequence transformations. +There is a duality between the observer and generator patterns. This is what enables us to transition from the async callback world to synchronous world of sequence transformations. -In short, enumerator and observer pattern both describe sequences. It's pretty obvious why does enumerator defined sequence, but what about observer. +In short, the enumerator and observer patterns both describe sequences. It's fairly obvious why the enumerator defines a sequence, but the observer is slightly more complicated. -There is also a pretty simple explanation that doesn't include a lot of math. Assume that you are observing mouse movements. Every received mouse movement is an element of a sequence of mouse movements over time. +There is, however, a pretty simple example that doesn't require a lot of mathematical knowledge. Assume that you are observing the position of your mouse cursor on screen at given time periods. Over time, these mouse positions form a sequence. This is, in essence, an observer sequence. -In short, there are two basic ways elements of a sequence can be accessed. +There are two basic ways elements of a sequence can be accessed: * Push interface - Observer (observed elements over time make a sequence) * Pull interface - Iterator / Enumerator / Generator -To learn more about this, these videos should help +You can also see a more formal explanation in this video: -You can also see a more formal explanation explained in a fun way in this video: - -[Expert to Expert: Brian Beckman and Erik Meijer - Inside the .NET Reactive Framework (Rx) (video)](https://www.youtube.com/watch?v=looJcaeboBY) +* [Expert to Expert: Brian Beckman and Erik Meijer - Inside the .NET Reactive Framework (Rx) (video)](https://www.youtube.com/watch?v=looJcaeboBY) +* [Reactive Programming Overview (Jafar Husain from Netflix)](https://www.youtube.com/watch?v=dwP1TNXE6fc) diff --git a/Documentation/NewFeatureRequestTemplate.md b/Documentation/NewFeatureRequestTemplate.md new file mode 100644 index 00000000..8711bfb5 --- /dev/null +++ b/Documentation/NewFeatureRequestTemplate.md @@ -0,0 +1,20 @@ +**Please copy the following template [here](https://github.com/ReactiveX/RxSwift/issues/new) and fill in the missing fields so we can help you as soon as possible.** + +``` +*Short description of missing functionality*: + + E.g. I want this library to implement xxx operator. + +*Short code example of how you would like to use the API*: + + code goes here + +*The reason why I need this functionality*: + + E.g. I was trying to .... + +*Code I have right now*: + + code snippet that demonstrates how you've attempted to solve the problem + +``` diff --git a/Documentation/Playgrounds.md b/Documentation/Playgrounds.md new file mode 100644 index 00000000..f3f6a061 --- /dev/null +++ b/Documentation/Playgrounds.md @@ -0,0 +1,8 @@ +## Playgrounds + +To use playgrounds: + +* Open `Rx.xcworkspace` +* Build `RxSwift-OSX` scheme +* And then open `Rx` playground in `Rx.xcworkspace` tree view. +* Choose `View > Show Debug Area` diff --git a/Documentation/Why.md b/Documentation/Why.md new file mode 100644 index 00000000..a76d2d2a --- /dev/null +++ b/Documentation/Why.md @@ -0,0 +1,295 @@ +## Why + +**Rx enables building apps in a declarative way.** + +### Bindings + +```swift +Observable.combineLatest(firstName.rx_text, lastName.rx_text) { $0 + " " + $1 } + .map { "Greeting \($0)" } + .bindTo(greetingLabel.rx_text) +``` + +This also works with `UITableView`s and `UICollectionView`s. + +```swift +viewModel + .rows + .bindTo(resultsTableView.rx_itemsWithCellIdentifier("WikipediaSearchCell", cellType: WikipediaSearchCell.self)) { (_, viewModel, cell) in + cell.title = viewModel.title + cell.url = viewModel.url + } + .addDisposableTo(disposeBag) +``` + +** Official suggestion is to always use `.addDisposableTo(disposeBag)` even though that's not necessary for simple bindings.** + +### Retries + +It would be great if APIs wouldn't fail, but unfortunately they do. Let's say there is an API method + +```swift +func doSomethingIncredible(forWho: String) throws -> IncredibleThing +``` + +If you are using this function as it is, it's really hard to do retries in case it fails. Not to mention complexities modeling [exponential backoffs](https://en.wikipedia.org/wiki/Exponential_backoff). Sure it's possible, but code would probably contain a lot of transient states that you really don't care about, and it won't be reusable. + +You would ideally want to capture the essence of retrying, and to be able to apply it to any operation. + +This is how you can do simple retries with Rx + +```swift + doSomethingIncredible("me") + .retry(3) +``` + +You can also easily create custom retry operators. + +### Delegates + +Instead of doing the tedious and non-expressive + +```swift +public func scrollViewDidScroll(scrollView: UIScrollView) { // what scroll view is this bound to? + self.leftPositionConstraint.constant = scrollView.contentOffset.x +} +``` + +... write + +```swift +self.resultsTableView + .rx_contentOffset + .map { $0.x } + .bindTo(self.leftPositionConstraint.rx_constant) +``` + +### KVO + +Instead of + +``` +`TickTock` was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. +``` + +and + +```objc +-(void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +``` + +Use [`rx_observe` and `rx_observeWeakly`](GettingStarted.md#kvo) + +This is how they can be used: + +```swift +view.rx_observe(CGRect.self, "frame") + .subscribeNext { frame in + print("Got new frame \(frame)") + } +``` + +or + +```swift +someSuspiciousViewController + .rx_observeWeakly(Bool.self, "behavingOk") + .subscribeNext { behavingOk in + print("Cats can purr? \(behavingOk)") + } +``` + +### Notifications + +Instead of using +```swift + @available(iOS 4.0, *) + public func addObserverForName(name: String?, object obj: AnyObject?, queue: NSOperationQueue?, usingBlock block: (NSNotification) -> Void) -> NSObjectProtocol +``` + +... just write + +```swift + NSNotificationCenter.defaultCenter() + .rx_notification(UITextViewTextDidBeginEditingNotification, object: myTextView) + .map { /*do something with data*/ } + .... +``` + +### Transient state + +There is also a lot of problems with transient state when writing async programs. Typical example is autocomplete search box. + +If you were to write the autocomplete code without Rx, first problem that probably needs to be solved is when `c` in `abc` is typed, and there is a pending request for `ab`, pending request gets cancelled. Ok, that shouldn't be too hard to solve, you just create additional variable to hold reference to pending request. + +The next problem is if the request fails, you need to do that messy retry logic. But ok, a couple of more fields that capture number of retries that need to be cleaned up. + +It would be great if program would wait for some time before firing that request to server, after all, we don't want to spam our servers in case somebody is in the process of fast typing something very long. Additional timer field maybe? + +There is also a question of what needs to be shown on screen while that search is executing, and also what needs to be shown in case we fail even with all of the retries. + +Writing all of this and properly testing it would be tedious. This is that same logic written with Rx. + +```swift + searchTextField.rx_text + .throttle(0.3, scheduler: MainScheduler.instance) + .distinctUntilChanged() + .flatMapLatest { query in + API.getSearchResults(query) + .retry(3) + .startWith([]) // clears results on new search term + .catchErrorJustReturn([]) + } + .subscribeNext { results in + // bind to ui + } +``` + +There is no additional flags or fields required. Rx takes care of all that transient mess. + +### Compositional disposal + +Lets assume that there is a scenario where you want to display blurred images in a table view. The images should be first fetched from URL, then decoded and then blurred. + +It would also be nice if that entire process could be cancelled if a cell exits the visible table view area because bandwidth and processor time for blurring are expensive. + +It would also be nice if we didn't just immediately start to fetch image once the cell enters visible area because if user swipes really fast there could be a lot of requests fired and cancelled. + +It would be also nice if we could limit the number of concurrent image operations because blurring images is an expensive operation. + +This is how we can do it using Rx. + +```swift +// this is conceptual solution +let imageSubscription = imageURLs + .throttle(0.2, scheduler: MainScheduler.instance) + .flatMapLatest { imageURL in + API.fetchImage(imageURL) + } + .observeOn(operationScheduler) + .map { imageData in + return decodeAndBlurImage(imageData) + } + .observeOn(MainScheduler.instance) + .subscribeNext { blurredImage in + imageView.image = blurredImage + } + .addDisposableTo(reuseDisposeBag) +``` + +This code will do all that, and when `imageSubscription` is disposed it will cancel all dependent async operations and make sure no rogue image is bound to UI. + +### Aggregating network requests + +What if you need to fire two requests, and aggregate results when they have both finished? + +Well, there is of course `zip` operator + +```swift + let userRequest: Observable = API.getUser("me") + let friendsRequest: Observable = API.getFriends("me") + + Observable.zip(userRequest, friendsRequest) { user, friends in + return (user, friends) + } + .subscribeNext { user, friends in + // bind them to user interface + } +``` + +So what if those APIs return results on a background thread, and binding has to happen on main UI thread? There is `observeOn`. + +```swift + let userRequest: Observable = API.getUser("me") + let friendsRequest: Observable<[Friend]> = API.getFriends("me") + + Observable.zip(userRequest, friendsRequest) { user, friends in + return (user, friends) + } + .observeOn(MainScheduler.instance) + .subscribeNext { user, friends in + // bind them to user interface + } +``` + +There are many more practical use cases where Rx really shines. + +### State + +Languages that allow mutation make it easy to access global state and mutate it. Uncontrolled mutations of shared global state can easily cause [combinatorial explosion] (https://en.wikipedia.org/wiki/Combinatorial_explosion#Computing). + +But on the other hand, when used in smart way, imperative languages can enable writing more efficient code closer to hardware. + +The usual way to battle combinatorial explosion is to keep state as simple as possible, and use [unidirectional data flows](https://developer.apple.com/videos/play/wwdc2014-229) to model derived data. + +This is what Rx really shines at. + +Rx is that sweet spot between functional and imperative world. It enables you to use immutable definitions and pure functions to process snapshots of mutable state in a reliable composable way. + +So what are some of the practical examples? + +### Easy integration + +And what if you need to create your own observable? It's pretty easy. This code is taken from RxCocoa and that's all you need to wrap HTTP requests with `NSURLSession` + +```swift +extension NSURLSession { + public func rx_response(request: NSURLRequest) -> Observable<(NSData, NSURLResponse)> { + return Observable.create { observer in + let task = self.dataTaskWithRequest(request) { (data, response, error) in + guard let response = response, data = data else { + observer.on(.Error(error ?? RxCocoaURLError.Unknown)) + return + } + + guard let httpResponse = response as? NSHTTPURLResponse else { + observer.on(.Error(RxCocoaURLError.NonHTTPResponse(response: response))) + return + } + + observer.on(.Next(data, httpResponse)) + observer.on(.Completed) + } + + task.resume() + + return AnonymousDisposable { + task.cancel() + } + } + } +} +``` + +### Benefits + +In short using Rx will make your code: + +* composable <- because Rx is composition's nick name +* reusable <- because it's composable +* declarative <- because definitions are immutable and only data changes +* understandable and concise <- raising level of abstraction and removing transient states +* stable <- because Rx code is thoroughly unit tested +* less stateful <- because you are modeling application as unidirectional data flows +* without leaks <- because resource management is easy + +### It's not all or nothing + +It is usually a good idea to model as much of your application as possible using Rx. + +But what if you don't know all of the operators and does there even exist some operator that models your particular case? + +Well, all of the Rx operators are based on math and should be intuitive. + +The good news is that about 10-15 operators cover most typical use cases. And that list already includes some of the familiar ones like `map`, `filter`, `zip`, `observeOn` ... + +There is a huge list of [all Rx operators](http://reactivex.io/documentation/operators.html) and list of all of the [currently supported RxSwift operators](API.md). + +For each operator there is [marble diagram](http://reactivex.io/documentation/operators/retry.html) that helps to explain how does it work. + +But what if you need some operator that isn't on that list? Well, you can make your own operator. + +What if creating that kind of operator is really hard for some reason, or you have some legacy stateful piece of code that you need to work with? Well, you've got yourself in a mess, but you can [jump out of Rx monad](GettingStarted.md#life-happens) easily, process the data, and return back into it. diff --git a/README.md b/README.md index 7e1338dc..cdb4b7c0 100644 --- a/README.md +++ b/README.md @@ -2,58 +2,9 @@ ====================================== [![Travis CI](https://travis-ci.org/ReactiveX/RxSwift.svg?branch=master)](https://travis-ci.org/ReactiveX/RxSwift) ![platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20OSX%20%7C%20tvOS%20%7C%20watchOS%20%7C%20Linux%28experimental%29-333333.svg) ![pod](https://img.shields.io/cocoapods/v/RxSwift.svg) - [![Slack channel](http://slack.rxswift.org/badge.svg)](http://slack.rxswift.org) Xcode 7 Swift 2.1 required -### Change Log (from 1.9 version) - -* Removes deprecated APIs -* Adds `ObservableType` -* Moved from using `>-` operator to protocol extensions `.` -* Adds support for Swift 2.0 error handling `try`/`do`/`catch` - -You can now just write - -```swift - API.fetchData(URL) - .map { rawData in - if invalidData(rawData) { - throw myParsingError - } - - ... - - return parsedData - } -``` - -* RxCocoa introduces `bindTo` extensions - -```swift - Observable.combineLatest(firstName.rx_text, lastName.rx_text) { $0 + " " + $1 } - .map { "Greeting \($0)" } - .bindTo(greetingLabel.rx_text) -``` - -... works for `UITableView`/`UICollectionView` too - -```swift -viewModel.rows - .bindTo(resultsTableView.rx_itemsWithCellIdentifier("WikipediaSearchCell", cellType: WikipediaSearchCell.self)) { (_, viewModel, cell) in - cell.viewModel = viewModel - } - .addDisposableTo(disposeBag) -``` - -* Adds new operators (array version of `zip`, array version of `combineLatest`, ...) -* Renames `catch` to `catchError` -* Change from `disposeBag.addDisposable` to `disposable.addDisposableTo` -* Deprecates `aggregate` in favor of `reduce` -* Deprecates `variable` in favor of `shareReplay(1)` (to be consistent with RxJS version) - -Check out [Migration guide to RxSwift 2.0](Documentation/Migration.md) - ## About Rx Rx is a [generic abstraction of computation](https://youtu.be/looJcaeboBY) expressed through `Observable` interface. @@ -68,479 +19,55 @@ Like the original Rx, its intention is to enable easy composition of asynchronou KVO observing, async operations and streams are all unified under [abstraction of sequence](Documentation/GettingStarted.md#observables-aka-sequences). This is the reason why Rx is so simple, elegant and powerful. +## I came here because I want to ... -``` -RxSwift -| -├-LICENSE.md -├-README.md -├-RxSwift - platform agnostic core -├-RxCocoa - extensions for UI, NSURLSession, KVO ... -├-RxBlocking - set of blocking operators for unit testing -├-RxExample - example apps: taste of Rx -└-Rx.xcworkspace - workspace that contains all of the projects hooked up -``` +###### ... understand -## Docs +* [why use rx?](Documentation/Why.md) +* how does RxSwift work? [Getting Started Guide](Documentation/GettingStarted.md) +* what is `Driver`, `ControlProperty`, and `Variable` ... and why do they exist? [Units](Documentation/Units.md) +* [the math behind Rx](Documentation/MathBehindRx.md) +* [what are hot and cold observable sequences?](Documentation/HotAndColdObservables.md) +* [what does the the public API look like?](Documentation/API.md) -1. [Getting started](Documentation/GettingStarted.md) -1. [Math behind](Documentation/MathBehindRx.md) -1. [Units](Documentation/Units.md) -1. [Simple Examples](Documentation/Examples.md) -1. [API - RxSwift operators / RxCocoa extensions](Documentation/API.md) -1. [Hot and cold observables](Documentation/HotAndColdObservables.md) +###### ... install -## Content +* Integrate RxSwift/RxCocoa with my app. [Installation Guide](Documentation/Installation.md) -1. [Why](#why) - 1. [State](#state) - 1. [Bindings](#bindings) - 1. [Retries](#retries) - 1. [Transient state](#transient-state) - 1. [Aggregating network requests](#aggregating-network-requests) - 1. [Easy integration](#easy-integration) - 1. [Compositional disposal](#compositional-disposal) - 1. [Delegates](#delegates) - 1. [Notifications](#notifications) - 1. [KVO](#kvo) - 1. [Benefits](#benefits) - 1. [It's not all or nothing](#its-not-all-or-nothing) -1. [Build / Install / Run](#build--install--run) -1. [Comparison with ReactiveCocoa](#comparison-with-reactivecocoa) -1. [Playgrounds](#playgrounds) -1. [RxExamples](#rxexamples) -1. [References](#references) +###### ... hack around -## Why +* with example app. [Running Example App](Documentation/ExampleApp.md) +* with operators in playgrounds. [Playgrounds](Documentation/Playgrounds.md) -Producing stable code fast is usually unexpectedly hard using just your vanilla language of choice. +###### ... interact -There are many unexpected pitfalls that can ruin all of your hard work and halt development of new features. +* All of this is great, but it would be nice to talk with other people using RxSwift and exchange experiences.
[![Slack channel](http://slack.rxswift.org/badge.svg)](http://slack.rxswift.org) [Join Slack Channel](http://slack.rxswift.org/) +* Report a problem using the library. [Open an Issue With Bug Template](Documentation/IssueTemplate.md) +* Request a new feature. [Open an Issue With Feature Request Template](Documentation/NewFeatureRequestTemplate.md) -### State -Languages that allow mutation make it easy to access global state and mutate it. Uncontrolled mutations of shared global state can easily cause [combinatorial explosion] (https://en.wikipedia.org/wiki/Combinatorial_explosion#Computing). +###### ... compare -But on the other hand, when used in smart way, imperative languages can enable writing more efficient code closer to hardware. +* [with other libraries](Documentation/ComparisonWithOtherLibraries.md). -The usual way to battle combinatorial explosion is to keep state as simple as possible, and use [unidirectional data flows](https://developer.apple.com/videos/play/wwdc2014-229) to model derived data. -This is what Rx really shines at. +###### ... find compatible -Rx is that sweet spot between functional and imperative world. It enables you to use immutable definitions and pure functions to process snapshots of mutable state in a reliable composable way. +* libraries from [RxSwiftCommunity](https://github.com/RxSwiftCommunity). +* [Pods using RxSwift](https://cocoapods.org/?q=uses%3Arxswift). -So what are some of the practical examples? +###### ... see the broader vision -### Bindings +* Does this exist for Android? [RxJava](https://github.com/ReactiveX/RxJava) +* Where is all of this going, what is the future, what about reactive architectures, how do you design entire apps this way? [Cycle.js](https://github.com/cyclejs/cycle-core) - this is javascript, but [RxJS](https://github.com/Reactive-Extensions/RxJS) is javascript version of Rx. -When writing embedded UI applications you would ideally want your program interface to be just a [pure function](https://en.wikipedia.org/wiki/Pure_function) of the [truth of the system](https://developer.apple.com/videos/play/wwdc2014-229). In that way user interface could be optimally redrawn only when truth changes, and there wouldn't be any inconsistencies. - -These are so called bindings and Rx can help you model your system that way. - -```swift -Observable.combineLatest(firstName.rx_text, lastName.rx_text) { $0 + " " + $1 } - .map { "Greeting \($0)" } - .bindTo(greetingLabel.rx_text) -``` - -** Official suggestion is to always use `.addDisposableTo(disposeBag)` even though that's not necessary for simple bindings.** - -### Retries - -It would be great if APIs wouldn't fail, but unfortunately they do. Let's say there is an API method - -```swift -func doSomethingIncredible(forWho: String) throws -> IncredibleThing -``` - -If you are using this function as it is, it's really hard to do retries in case it fails. Not to mention complexities modelling [exponential backoffs](https://en.wikipedia.org/wiki/Exponential_backoff). Sure it's possible, but code would probably contain a lot of transient states that you really don't care about, and it won't be reusable. - -You would ideally want to capture the essence of retrying, and to be able to apply it to any operation. - -This is how you can do simple retries with Rx - -```swift - doSomethingIncredible("me") - .retry(3) -``` - -You can also easily create custom retry operators. - -### Transient state - -There is also a lot of problems with transient state when writing async programs. Typical example is autocomplete search box. - -If you were to write the autocomplete code without Rx, first problem that probably needs to be solved is when `c` in `abc` is typed, and there is a pending request for `ab`, pending request gets cancelled. Ok, that shouldn't be too hard to solve, you just create additional variable to hold reference to pending request. - -The next problem is if the request fails, you need to do that messy retry logic. But ok, a couple of more fields that capture number of retries that need to be cleaned up. - -It would be great if program would wait for some time before firing that request to server, after all, we don't want to spam our servers in case somebody is in the process of fast typing something very long. Additional timer field maybe? - -There is also a question of what needs to be shown on screen while that search is executing, and also what needs to be shown in case we fail even with all of the retries. - -Writing all of this and properly testing it would be tedious. This is that same logic written with Rx. - -```swift - searchTextField.rx_text - .throttle(0.3, scheduler: MainScheduler.instance) - .distinctUntilChanged() - .flatMapLatest { query in - API.getSearchResults(query) - .retry(3) - .startWith([]) // clears results on new search term - .catchErrorJustReturn([]) - } - .subscribeNext { results in - // bind to ui - } -``` - -There is no additional flags or fields required. Rx takes care of all that transient mess. - -### Aggregating network requests - -What if you need to fire two requests, and aggregate results when they have both finished? - -Well, there is of course `zip` operator - -```swift - let userRequest: Observable = API.getUser("me") - let friendsRequest: Observable = API.getFriends("me") - - Observable.zip(userRequest, friendsRequest) { user, friends in - return (user, friends) - } - .subscribeNext { user, friends in - // bind them to user interface - } -``` - -So what if those APIs return results on a background thread, and binding has to happen on main UI thread? There is `observeOn`. - -```swift - let userRequest: Observable = API.getUser("me") - let friendsRequest: Observable<[Friend]> = API.getFriends("me") - - Observable.zip(userRequest, friendsRequest) { user, friends in - return (user, friends) - } - .observeOn(MainScheduler.instance) - .subscribeNext { user, friends in - // bind them to user interface - } -``` - -There are many more practical use cases where Rx really shines. - -### Easy integration - -And what if you need to create your own observable? It's pretty easy. This code is taken from RxCocoa and that's all you need to wrap HTTP requests with `NSURLSession` - -```swift -extension NSURLSession { - public func rx_response(request: NSURLRequest) -> Observable<(NSData, NSURLResponse)> { - return Observable.create { observer in - let task = self.dataTaskWithRequest(request) { (data, response, error) in - guard let response = response, data = data else { - observer.on(.Error(error ?? RxCocoaURLError.Unknown)) - return - } - - guard let httpResponse = response as? NSHTTPURLResponse else { - observer.on(.Error(RxCocoaURLError.NonHTTPResponse(response: response))) - return - } - - observer.on(.Next(data, httpResponse)) - observer.on(.Completed) - } - - task.resume() - - return AnonymousDisposable { - task.cancel() - } - } - } -} -``` - -### Compositional disposal - -Lets assume that there is a scenario where you want to display blurred images in a table view. The images should be first fetched from URL, then decoded and then blurred. - -It would also be nice if that entire process could be cancelled if a cell exits the visible table view area because bandwidth and processor time for blurring are expensive. - -It would also be nice if we didn't just immediately start to fetch image once the cell enters visible area because if user swipes really fast there could be a lot of requests fired and cancelled. - -It would be also nice if we could limit the number of concurrent image operations because blurring images is an expensive operation. - -This is how we can do it using Rx. - -```swift -// this is conceptual solution -let imageSubscription = imageURLs - .throttle(0.2, scheduler: MainScheduler.instance) - .flatMapLatest { imageURL in - API.fetchImage(imageURL) - } - .observeOn(operationScheduler) - .map { imageData in - return decodeAndBlurImage(imageData) - } - .observeOn(MainScheduler.instance) - .subscribeNext { blurredImage in - imageView.image = blurredImage - } - .addDisposableTo(reuseDisposeBag) -``` - -This code will do all that, and when `imageSubscription` is disposed it will cancel all dependent async operations and make sure no rogue image is bound to UI. - - -### Delegates - -Delegates can be used both as a hook for customizing behavior and as an observing mechanism. - -Each usage has it's drawbacks, but Rx can help remedy some of the problem with using delegates as an observing mechanism. - -Using delegates and optional methods to report changes can be problematic because there can be usually only one delegate registered, so there is no way to register multiple observers. - -Also, delegates usually don't fire initial value upon invoking delegate setter, so you'll also need to read that initial value in some other way. That is kind of tedious. - -RxCocoa not only provides wrappers for popular UIKit/Cocoa classes, but it also provides a generic mechanism called `DelegateProxy` that enables wrapping your own delegates and exposing them as observable sequences. - -This is real code taken from `UISearchBar` integration. - -It uses delegate as a notification mechanism to create an `Observable` that immediately returns current search text upon subscription, and then emits changed search values. - -```swift -extension UISearchBar { - - public var rx_delegate: DelegateProxy { - return proxyForObject(RxSearchBarDelegateProxy.self, self) - } - - public var rx_text: Observable { - return Observable.deferred { [weak self] in - let text = self?.text ?? "" - - return self?.rx_delegate.observe("searchBar:textDidChange:") ?? empty() - .map { a in - return a[1] as? String ?? "" - } - .startWith(text) - } - } -} -``` - -Definition of `RxSearchBarDelegateProxy` can be found [here](RxCocoa/iOS/Proxies/RxSearchBarDelegateProxy.swift) - -This is how that API can be now used - -```swift - -searchBar.rx_text - .subscribeNext { searchText in - print("Current search text '\(searchText)'") - } - -``` - -### Notifications - -Notifications enable registering multiple observers easily, but they are also untyped. Values need to be extracted from either `userInfo` or original target once they fire. - -They are just a notification mechanism, and initial value usually has to be acquired in some other way. - -That leads to this tedious pattern: - -```swift -let initialText = object.text - -doSomething(initialText) - -// .... - -func controlTextDidChange(notification: NSNotification) { - doSomething(object.text) -} - -``` - -You can use `rx_notification` to create an observable sequence with wanted properties in a similar fashion like `searchText` was constructed in delegate example, and thus reduce scattering of logic and duplication of code. - -### KVO - -KVO is a handy observing mechanism, but not without flaws. It's biggest flaw is confusing memory management. - -In case of observing a property on some object, the object has to outlive the KVO observer registration otherwise your system will crash with an exception. - -``` -`TickTock` was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. -``` - -There are some rules that you can follow when observing some object that is a direct descendant or ancestor in ownership chain, but if that relation is unknown, then it becomes tricky. - -It also has a really awkward callback method that needs to be implemented - -```objc --(void)observeValueForKeyPath:(NSString *)keyPath - ofObject:(id)object - change:(NSDictionary *)change - context:(void *)context -``` - -RxCocoa provides a really convenient observable sequence that solves those issues called [`rx_observe` and `rx_observeWeakly`](Documentation/GettingStarted.md#kvo) - -This is how they can be used: - -```swift -view.rx_observe(CGRect.self, "frame") - .subscribeNext { (frame: CGRect?) in - print("Got new frame \(frame)") - } -``` - -or - -```swift -someSuspiciousViewController.rx_observeWeakly(Bool.self, "behavingOk") - .subscribeNext { (behavingOk: Bool?) in - print("Cats can purr? \(behavingOk)") - } -``` - -### Benefits - -In short using Rx will make your code: - -* composable <- because Rx is composition's nick name -* reusable <- because it's composable -* declarative <- because definitions are immutable and only data changes -* understandable and concise <- raising level of abstraction and removing transient states -* stable <- because Rx code is thoroughly unit tested -* less stateful <- because you are modeling application as unidirectional data flows -* without leaks <- because resource management is easy - -### It's not all or nothing - -It is usually a good idea to model as much of your application as possible using Rx. - -But what if you don't know all of the operators and does there even exist some operator that models your particular case? - -Well, all of the Rx operators are based on math and should be intuitive. - -The good news is that about 10-15 operators cover most typical use cases. And that list already includes some of the familiar ones like `map`, `filter`, `zip`, `observeOn` ... - -There is a huge list of [all Rx operators](http://reactivex.io/documentation/operators.html) and list of all of the [currently supported RxSwift operators](Documentation/API.md). - -For each operator there is [marble diagram](http://reactivex.io/documentation/operators/retry.html) that helps to explain how does it work. - -But what if you need some operator that isn't on that list? Well, you can make your own operator. - -What if creating that kind of operator is really hard for some reason, or you have some legacy stateful piece of code that you need to work with? Well, you've got yourself in a mess, but you can [jump out of Rx monad](Documentation/GettingStarted.md#life-happens) easily, process the data, and return back into it. - -## Build / Install / Run - -Rx doesn't contain any external dependencies. - -These are currently supported options: - -### Manual - -Open Rx.xcworkspace, choose `RxExample` and hit run. This method will build everything and run sample app - -### [CocoaPods](https://guides.cocoapods.org/using/using-cocoapods.html) - -**:warning: IMPORTANT! For tvOS support CocoaPods `0.39` is required. :warning:** - -``` -# Podfile -use_frameworks! - -target 'YOUR_TARGET_NAME' do - pod 'RxSwift', '~> 2.0' - pod 'RxCocoa', '~> 2.0' - pod 'RxBlocking', '~> 2.0' - pod 'RxTests', '~> 2.0' -end -``` - -replace `YOUR_TARGET_NAME`, then type in the `Podfile` directory: - -``` -$ pod install -``` - -### [Carthage](https://github.com/Carthage/Carthage) - -**Xcode 7.1 required** - -Add this to `Cartfile` - -``` -github "ReactiveX/RxSwift" ~> 2.0 -``` - -``` -$ carthage update -``` - -### Manually using git submodules - -* Add RxSwift as a submodule - -``` -$ git submodule add git@github.com:ReactiveX/RxSwift.git -``` - -* Drag `Rx.xcodeproj` into Project Navigator -* Go to `Project > Targets > Build Phases > Link Binary With Libraries`, click `+` and select `RxSwift-[Platform]` and `RxCocoa-[Platform]` targets - -## Comparison with ReactiveCocoa - -RxSwift is somewhat similar to ReactiveCocoa since ReactiveCocoa borrows a large number of concepts from Rx. - -One of the main goals of this project was to create a significantly simpler interface that is more aligned with other Rx implementations, offers richer concurrency model, offers more optimization opportunities and is more aligned with built-in Swift error handling mechanisms. - -We've also decided to only rely on Swift/llvm compiler and not introduce any external dependencies. - -Probably the main difference between these projects is the difference of approach in building abstractions. - -The main goal of RxSwift project is to provide environment agnostic compositional computation glue abstracted in the form of observable sequences. -We then aim to improve the experience of using RxSwift on specific platforms. To do this RxCocoa uses those generic computations to build more practical abstractions and wrap Foundation/Cocoa/UKit frameworks. That means that other libraries give context and semantics to the generic computation engine RxSwift provides such as `Driver`, `ControlProperty`, `ControlEvent`s and more. - -One of the benefits to representing all these abstractions as a single concept; ​_observable sequences_​, is that all computation abstractions built on top of them are also composable in the same fundamental way. They all follow the same contract and implement the same interface. - It is also easy to create flexible subscription (resource) sharing strategies or use one of the built-in ones: `share`, `shareReplay`, `publish`, `multicast`, `shareReplayLatestWhileConnected`... - -This library also offers fine-tunable concurrency model. In case concurrent schedulers are used, observable sequence operators will preserve sequence properties. The same observable sequence operators will also know how to detect and optimally use known serial schedulers. ReactiveCocoa has a more limited concurrency model and only allows serial schedulers. - -Multithreaded programming is really hard and detecting non trivial loops is even harder. That's why all operators are built in a fault tolerant way. Even in case element generation occurs during element processing (recursion), operators will try to handle that situation and prevent deadlocks. That means that in worst possible case programming error will cause stack overflow, but users won't have to manually kill the app, and you will get crash report in error reporting systems so you can find and fix the problem. - -## Playgrounds - -To use playgrounds: - -* Open `Rx.xcworkspace` -* Build `RxSwift-OSX` scheme -* And then open `Rx` playground in `Rx.xcworkspace` tree view. -* Choose `View > Show Debug Area` - -## RxExamples - -To use playgrounds: - -* Open `Rx.xcworkspace` -* Choose one of example schemes and hit `Run`. - -## References +##### References * [http://reactivex.io/](http://reactivex.io/) * [Reactive Extensions GitHub (GitHub)](https://github.com/Reactive-Extensions) * [Erik Meijer (Wikipedia)](http://en.wikipedia.org/wiki/Erik_Meijer_%28computer_scientist%29) * [Expert to Expert: Brian Beckman and Erik Meijer - Inside the .NET Reactive Framework (Rx) (video)](https://youtu.be/looJcaeboBY) +* [Reactive Programming Overview (Jafar Husain from Netflix)](https://www.youtube.com/watch?v=dwP1TNXE6fc) * [Subject/Observer is Dual to Iterator (paper)](http://csl.stanford.edu/~christos/pldi2010.fit/meijer.duality.pdf) * [Rx standard sequence operators visualized (visualization tool)](http://rxmarbles.com/) * [Haskell](https://www.haskell.org/) diff --git a/Rx.playground/Pages/Connectable_Observable_Operators.xcplaygroundpage/Contents.swift b/Rx.playground/Pages/Connectable_Observable_Operators.xcplaygroundpage/Contents.swift index e2453a21..1bbdd93b 100644 --- a/Rx.playground/Pages/Connectable_Observable_Operators.xcplaygroundpage/Contents.swift +++ b/Rx.playground/Pages/Connectable_Observable_Operators.xcplaygroundpage/Contents.swift @@ -2,6 +2,14 @@ import RxSwift + +/*: + ## Below every example there is a commented method call that runs that example. To run the example just uncomment that part. + + E.g. `//sampleWithoutConnectableOperators()` +*/ + + /*: ## Connectable Observable Operators diff --git a/Rx.xcodeproj/project.pbxproj b/Rx.xcodeproj/project.pbxproj index 089159dc..54156dc4 100644 --- a/Rx.xcodeproj/project.pbxproj +++ b/Rx.xcodeproj/project.pbxproj @@ -35,6 +35,8 @@ 9BA1CBFD1C0F84A10044B50A /* UIActivityIndicatorView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BA1CBD11C0F7C0A0044B50A /* UIActivityIndicatorView+Rx.swift */; }; 9BA1CBFE1C0F84C40044B50A /* UIActivityIndicatorView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BA1CBD11C0F7C0A0044B50A /* UIActivityIndicatorView+Rx.swift */; }; 9D71C4D21BF08191006E8F59 /* UIButton+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C88254061B8A752B00B02D69 /* UIButton+Rx.swift */; }; + AAE623761C82475700FC7801 /* UIProgressView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE623751C82475700FC7801 /* UIProgressView+Rx.swift */; }; + AAE623771C82475700FC7801 /* UIProgressView+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE623751C82475700FC7801 /* UIProgressView+Rx.swift */; }; B1B7C3BD1BDD39DB0076934E /* TakeLast.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B7C3BC1BDD39DB0076934E /* TakeLast.swift */; }; B1B7C3BE1BDD39DB0076934E /* TakeLast.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B7C3BC1BDD39DB0076934E /* TakeLast.swift */; }; B1B7C3BF1BDD39DB0076934E /* TakeLast.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B7C3BC1BDD39DB0076934E /* TakeLast.swift */; }; @@ -507,6 +509,7 @@ C8350A231C38756B0027C24C /* VirtualSchedulerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C835091C1C38706D0027C24C /* VirtualSchedulerTest.swift */; }; C8350A2A1C3875B50027C24C /* RxMutableBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8093CB01B8A72BE0088E94D /* RxMutableBox.swift */; }; C8350A2B1C3875B60027C24C /* RxMutableBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8093CB01B8A72BE0088E94D /* RxMutableBox.swift */; }; + C839365F1C70E02200A9A09E /* UIApplication+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = C839365E1C70E02200A9A09E /* UIApplication+Rx.swift */; }; C83D73B81C1DBAEE003DC470 /* AnonymousInvocable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83D73B31C1DBAEE003DC470 /* AnonymousInvocable.swift */; }; C83D73B91C1DBAEE003DC470 /* AnonymousInvocable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83D73B31C1DBAEE003DC470 /* AnonymousInvocable.swift */; }; C83D73BA1C1DBAEE003DC470 /* AnonymousInvocable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C83D73B31C1DBAEE003DC470 /* AnonymousInvocable.swift */; }; @@ -912,6 +915,10 @@ C8FA89201C30424000CD3A17 /* VirtualTimeConverterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FA89121C30405400CD3A17 /* VirtualTimeConverterType.swift */; }; C8FA89211C30424000CD3A17 /* VirtualTimeConverterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FA89121C30405400CD3A17 /* VirtualTimeConverterType.swift */; }; C8FA89221C30424100CD3A17 /* VirtualTimeConverterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FA89121C30405400CD3A17 /* VirtualTimeConverterType.swift */; }; + C8FD21AE1C67E14C00863EC3 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */; }; + C8FD21AF1C67E14C00863EC3 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */; }; + C8FD21B01C67E14C00863EC3 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */; }; + C8FD21B11C67E14C00863EC3 /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */; }; CB255BD71BC46A9C00798A4C /* RetryWhen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB255BD61BC46A9C00798A4C /* RetryWhen.swift */; }; CB255BD81BC46A9C00798A4C /* RetryWhen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB255BD61BC46A9C00798A4C /* RetryWhen.swift */; }; CB255BD91BC46A9C00798A4C /* RetryWhen.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB255BD61BC46A9C00798A4C /* RetryWhen.swift */; }; @@ -1324,6 +1331,7 @@ 84C225A21C33F00B008724EC /* RxTextStorageDelegateProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxTextStorageDelegateProxy.swift; sourceTree = ""; }; 9BA1CBD11C0F7C0A0044B50A /* UIActivityIndicatorView+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIActivityIndicatorView+Rx.swift"; sourceTree = ""; }; A111CE961B91C97C00D0DCEE /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AAE623751C82475700FC7801 /* UIProgressView+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIProgressView+Rx.swift"; sourceTree = ""; }; B1B7C3BC1BDD39DB0076934E /* TakeLast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TakeLast.swift; sourceTree = ""; }; B1D8998E1BF653410027B05C /* Timeout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Timeout.swift; sourceTree = ""; }; C807F3611C2ACED300017910 /* TestSchedulerVirtualTimeConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestSchedulerVirtualTimeConverter.swift; sourceTree = ""; }; @@ -1541,6 +1549,7 @@ C83509231C38706E0027C24C /* XCTest+AllTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCTest+AllTests.swift"; sourceTree = ""; }; C83509841C38740E0027C24C /* AllTests-tvOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "AllTests-tvOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; C83509941C38742C0027C24C /* AllTests-OSX.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "AllTests-OSX.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + C839365E1C70E02200A9A09E /* UIApplication+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+Rx.swift"; sourceTree = ""; }; C83D73B31C1DBAEE003DC470 /* AnonymousInvocable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnonymousInvocable.swift; sourceTree = ""; }; C83D73B41C1DBAEE003DC470 /* InvocableScheduledItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvocableScheduledItem.swift; sourceTree = ""; }; C83D73B51C1DBAEE003DC470 /* InvocableType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InvocableType.swift; sourceTree = ""; }; @@ -1643,6 +1652,7 @@ C8FA89131C30405400CD3A17 /* VirtualTimeScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VirtualTimeScheduler.swift; sourceTree = ""; }; C8FA89161C30409900CD3A17 /* HistoricalScheduler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoricalScheduler.swift; sourceTree = ""; }; C8FA891B1C30412A00CD3A17 /* HistoricalSchedulerTimeConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoricalSchedulerTimeConverter.swift; sourceTree = ""; }; + C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIBindingObserver.swift; sourceTree = ""; }; CB255BD61BC46A9C00798A4C /* RetryWhen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RetryWhen.swift; sourceTree = ""; }; CB30D9E81BF0E3500084C1C0 /* SingleAsync.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleAsync.swift; sourceTree = ""; }; CB883B3A1BE24355000AC2EE /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; @@ -2125,6 +2135,7 @@ C80DDE8C1BCE69BA006A1832 /* Driver */, C80D33931B922FB00014629D /* ControlEvent.swift */, C80D33941B922FB00014629D /* ControlProperty.swift */, + C8FD21AD1C67E14C00863EC3 /* UIBindingObserver.swift */, ); path = CocoaUnits; sourceTree = ""; @@ -2312,6 +2323,7 @@ C88253F61B8A752B00B02D69 /* Protocols */, C88253F91B8A752B00B02D69 /* Proxies */, C88254051B8A752B00B02D69 /* UIBarButtonItem+Rx.swift */, + C839365E1C70E02200A9A09E /* UIApplication+Rx.swift */, C88254061B8A752B00B02D69 /* UIButton+Rx.swift */, C88254071B8A752B00B02D69 /* UICollectionView+Rx.swift */, C88254081B8A752B00B02D69 /* UIControl+Rx.swift */, @@ -2319,6 +2331,7 @@ C882540A1B8A752B00B02D69 /* UIGestureRecognizer+Rx.swift */, C882540B1B8A752B00B02D69 /* UIImageView+Rx.swift */, C882540C1B8A752B00B02D69 /* UILabel+Rx.swift */, + AAE623751C82475700FC7801 /* UIProgressView+Rx.swift */, 7F600F3D1C5D0C0100535B1D /* UIRefreshControl+Rx.swift */, C882540D1B8A752B00B02D69 /* UIScrollView+Rx.swift */, C882540E1B8A752B00B02D69 /* UISearchBar+Rx.swift */, @@ -3241,12 +3254,14 @@ C882542E1B8A752B00B02D69 /* UILabel+Rx.swift in Sources */, C88254211B8A752B00B02D69 /* RxSearchBarDelegateProxy.swift in Sources */, C80DDEA71BCE69BA006A1832 /* ObservableConvertibleType+Driver.swift in Sources */, + C839365F1C70E02200A9A09E /* UIApplication+Rx.swift in Sources */, C80DDE9F1BCE69BA006A1832 /* Driver+Subscription.swift in Sources */, C811C89D1C24D80100A2DDD4 /* DeallocObservable.swift in Sources */, C8BCD3ED1C14B5FB005F1280 /* UIView+Rx.swift in Sources */, C80D338F1B91EF9E0014629D /* Observable+Bind.swift in Sources */, C88254311B8A752B00B02D69 /* UISegmentedControl+Rx.swift in Sources */, C8093EED1B8A732E0088E94D /* KVOObservable.swift in Sources */, + AAE623761C82475700FC7801 /* UIProgressView+Rx.swift in Sources */, C8DB968D1BF7595D0084BD53 /* KVORepresentable+Swift.swift in Sources */, C80DDEB11BCE8CA3006A1832 /* Driver+Operators+arity.swift in Sources */, C88254281B8A752B00B02D69 /* UIButton+Rx.swift in Sources */, @@ -3279,6 +3294,7 @@ C88254301B8A752B00B02D69 /* UISearchBar+Rx.swift in Sources */, C88254181B8A752B00B02D69 /* ItemEvents.swift in Sources */, C8DB96881BF756F40084BD53 /* KVORepresentable+CoreGraphics.swift in Sources */, + C8FD21AE1C67E14C00863EC3 /* UIBindingObserver.swift in Sources */, C882541B1B8A752B00B02D69 /* RxTableViewDataSourceType.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3297,6 +3313,7 @@ C8DB967F1BF7496C0084BD53 /* KVORepresentable.swift in Sources */, C8093EFE1B8A732E0088E94D /* RxTarget.swift in Sources */, C8093ED21B8A732E0088E94D /* _RX.m in Sources */, + C8FD21AF1C67E14C00863EC3 /* UIBindingObserver.swift in Sources */, C8093EFC1B8A732E0088E94D /* RxCocoa.swift in Sources */, C8DB968E1BF7595D0084BD53 /* KVORepresentable+Swift.swift in Sources */, C80D33991B922FB00014629D /* ControlEvent.swift in Sources */, @@ -4171,6 +4188,7 @@ C8F0C03B1BBBFBB9001B112F /* UISearchBar+Rx.swift in Sources */, C8F0C03C1BBBFBB9001B112F /* ItemEvents.swift in Sources */, C8DB968B1BF756F40084BD53 /* KVORepresentable+CoreGraphics.swift in Sources */, + C8FD21B11C67E14C00863EC3 /* UIBindingObserver.swift in Sources */, C8F0C03D1BBBFBB9001B112F /* RxTableViewDataSourceType.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4244,6 +4262,7 @@ D203C50D1BB9C53E00D02D00 /* UISegmentedControl+Rx.swift in Sources */, C8C4B4C41C17727000828BD5 /* MessageSentObserver.swift in Sources */, D2138C861BB9BEBE00339B5C /* Observable+Bind.swift in Sources */, + AAE623771C82475700FC7801 /* UIProgressView+Rx.swift in Sources */, D203C50A1BB9C53E00D02D00 /* UILabel+Rx.swift in Sources */, D203C4F51BB9C52900D02D00 /* ItemEvents.swift in Sources */, C8BCD3F61C14B6D1005F1280 /* NSLayoutConstraint+Rx.swift in Sources */, @@ -4262,6 +4281,7 @@ D203C4FD1BB9C53700D02D00 /* RxSearchBarDelegateProxy.swift in Sources */, D2138C8A1BB9BEBE00339B5C /* Logging.swift in Sources */, C8DB968A1BF756F40084BD53 /* KVORepresentable+CoreGraphics.swift in Sources */, + C8FD21B01C67E14C00863EC3 /* UIBindingObserver.swift in Sources */, D203C50F1BB9C53E00D02D00 /* UIStepper+Rx.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Rx.xcworkspace/contents.xcworkspacedata b/Rx.xcworkspace/contents.xcworkspacedata index dc76df6f..286c69b5 100644 --- a/Rx.xcworkspace/contents.xcworkspacedata +++ b/Rx.xcworkspace/contents.xcworkspacedata @@ -103,6 +103,12 @@ + + + + @@ -121,9 +127,6 @@ - - diff --git a/RxBlocking.podspec b/RxBlocking.podspec index f540715f..f207ff9e 100644 --- a/RxBlocking.podspec +++ b/RxBlocking.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "RxBlocking" - s.version = "2.1.0" + s.version = "2.2.0" s.summary = "RxSwift Blocking operatos" s.description = <<-DESC Set of blocking operators for RxSwift. These operators are mostly intended for unit/integration tests @@ -24,5 +24,5 @@ Waiting for observable sequence to complete before exiting command line applicat s.source_files = 'RxBlocking/**/*.swift' - s.dependency 'RxSwift', '~> 2.0' + s.dependency 'RxSwift', '~> 2.2' end diff --git a/RxBlocking/RunLoopLock.swift b/RxBlocking/RunLoopLock.swift index 2f32c630..3f44ff01 100644 --- a/RxBlocking/RunLoopLock.swift +++ b/RxBlocking/RunLoopLock.swift @@ -11,11 +11,28 @@ import Foundation import RxSwift #endif +typealias AtomicInt = Int32 + +#if os(Linux) + func AtomicIncrement(increment: UnsafeMutablePointer) -> AtomicInt { + increment.memory = increment.memory + 1 + return increment.memory + } + + func AtomicDecrement(increment: UnsafeMutablePointer) -> AtomicInt { + increment.memory = increment.memory - 1 + return increment.memory + } +#else + let AtomicIncrement = OSAtomicIncrement32 + let AtomicDecrement = OSAtomicDecrement32 +#endif + class RunLoopLock { let currentRunLoop: CFRunLoopRef - var calledRun: Int32 = 0 - var calledStop: Int32 = 0 + var calledRun: AtomicInt = 0 + var calledStop: AtomicInt = 0 init() { currentRunLoop = CFRunLoopGetCurrent() @@ -37,7 +54,7 @@ class RunLoopLock { } func stop() { - if OSAtomicIncrement32(&calledStop) != 1 { + if AtomicIncrement(&calledStop) != 1 { return } CFRunLoopPerformBlock(currentRunLoop, kCFRunLoopDefaultMode) { @@ -47,7 +64,7 @@ class RunLoopLock { } func run() { - if OSAtomicIncrement32(&calledRun) != 1 { + if AtomicIncrement(&calledRun) != 1 { fatalError("Run can be only called once") } CFRunLoopRun() diff --git a/RxCocoa.podspec b/RxCocoa.podspec index 401ff46f..25ef5d87 100644 --- a/RxCocoa.podspec +++ b/RxCocoa.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "RxCocoa" - s.version = "2.1.0" + s.version = "2.2.0" 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' + s.dependency 'RxSwift', '~> 2.2' end diff --git a/RxCocoa/Common/CLLocationManager+Rx.swift b/RxCocoa/Common/CLLocationManager+Rx.swift index 39e6a2fe..82a1a277 100644 --- a/RxCocoa/Common/CLLocationManager+Rx.swift +++ b/RxCocoa/Common/CLLocationManager+Rx.swift @@ -49,10 +49,10 @@ extension CLLocationManager { /** Reactive wrapper for `delegate` message. */ - public var rx_didFinishDeferredUpdatesWithError: Observable { + public var rx_didFinishDeferredUpdatesWithError: Observable { return rx_delegate.observe("locationManager:didFinishDeferredUpdatesWithError:") .map { a in - return try castOrThrow(NSError.self, a[1]) + return try castOptionalOrThrow(NSError.self, a[1]) } } #endif diff --git a/RxCocoa/Common/CocoaUnits/UIBindingObserver.swift b/RxCocoa/Common/CocoaUnits/UIBindingObserver.swift new file mode 100644 index 00000000..04b46037 --- /dev/null +++ b/RxCocoa/Common/CocoaUnits/UIBindingObserver.swift @@ -0,0 +1,63 @@ +// +// UIBindingObserver.swift +// Rx +// +// Created by Krunoslav Zaher on 2/7/16. +// Copyright © 2016 Krunoslav Zaher. All rights reserved. +// + +import Foundation +#if !RX_NO_MODULE + import RxSwift +#endif + +/** +Observer that enforces interface binding rules: + * can't bind errors (in debug builds binding of errors causes `fatalError` in release builds errors are being logged) + * ensures binding is performed on main thread + +`InterfaceBindingObserver` doesn't retain target interface and in case owned interface element is released, element isn't bound. +*/ +public class UIBindingObserver : ObserverType { + public typealias E = Value + + weak var UIElement: UIElementType? + + let binding: (UIElementType, Value) -> Void + + /** + Initializes `ViewBindingObserver` using + */ + public init(UIElement: UIElementType, binding: (UIElementType, Value) -> Void) { + self.UIElement = UIElement + self.binding = binding + } + + /** + Binds next element to owner view as described in `binding`. + */ + public func on(event: Event) { + MainScheduler.ensureExecutingOnScheduler() + + switch event { + case .Next(let element): + if let view = self.UIElement { + binding(view, element) + } + case .Error(let error): + bindingErrorToInterface(error) + break + case .Completed: + break + } + } + + /** + Erases type of observer. + + - returns: type erased observer. + */ + public func asObserver() -> AnyObserver { + return AnyObserver(eventHandler: on) + } +} \ No newline at end of file diff --git a/RxCocoa/Common/DelegateProxyType.swift b/RxCocoa/Common/DelegateProxyType.swift index 57c92ca6..8b0992e9 100644 --- a/RxCocoa/Common/DelegateProxyType.swift +++ b/RxCocoa/Common/DelegateProxyType.swift @@ -196,7 +196,7 @@ public func proxyForObject(type: P.Type, _ object: AnyObje func installDelegate(proxy: P, delegate: AnyObject, retainDelegate: Bool, onProxyForObject object: AnyObject) -> Disposable { weak var weakDelegate: AnyObject? = delegate - assert(proxy.forwardToDelegate() === nil, "There is already a set delegate \(proxy.forwardToDelegate())") + assert(proxy.forwardToDelegate() === nil, "There is already a delegate set -> `\(proxy.forwardToDelegate())` for object -> `\(object)`.\nMaybe delegate was already set in `xib` or `storyboard` and now it's being overwritten in code.") proxy.setForwardToDelegate(delegate, retainDelegate: retainDelegate) @@ -223,7 +223,7 @@ extension ObservableType { -> Disposable { let proxy = proxyForObject(P.self, object) let disposable = installDelegate(proxy, delegate: dataSource, retainDelegate: retainDataSource, onProxyForObject: object) - + let subscription = self.asObservable() // source can't ever end, otherwise it will release the subscriber .concat(Observable.never()) diff --git a/RxCocoa/Common/NSLayoutConstraint+Rx.swift b/RxCocoa/Common/NSLayoutConstraint+Rx.swift index ec100a3b..36e97598 100644 --- a/RxCocoa/Common/NSLayoutConstraint+Rx.swift +++ b/RxCocoa/Common/NSLayoutConstraint+Rx.swift @@ -24,19 +24,9 @@ extension NSLayoutConstraint { Bindable sink for `constant` property. */ public var rx_constant: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.constant = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { constraint, constant in + constraint.constant = constant + }.asObserver() } /** @@ -44,19 +34,9 @@ extension NSLayoutConstraint { */ @available(iOS 8, OSX 10.10, *) public var rx_active: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.active = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { constraint, value in + constraint.active = value + }.asObserver() } } diff --git a/RxCocoa/Common/Observables/NSNotificationCenter+Rx.swift b/RxCocoa/Common/Observables/NSNotificationCenter+Rx.swift index 0f912e36..a9ad4517 100644 --- a/RxCocoa/Common/Observables/NSNotificationCenter+Rx.swift +++ b/RxCocoa/Common/Observables/NSNotificationCenter+Rx.swift @@ -20,7 +20,7 @@ extension NSNotificationCenter { - 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 { + public func rx_notification(name: String?, object: AnyObject? = nil) -> Observable { return Observable.create { [weak object] observer in let nsObserver = self.addObserverForName(name, object: object, queue: nil) { notification in observer.on(.Next(notification)) diff --git a/RxCocoa/Common/Observables/NSURLSession+Rx.swift b/RxCocoa/Common/Observables/NSURLSession+Rx.swift index 9317e820..5fef6e6d 100644 --- a/RxCocoa/Common/Observables/NSURLSession+Rx.swift +++ b/RxCocoa/Common/Observables/NSURLSession+Rx.swift @@ -59,7 +59,7 @@ func escapeTerminalString(value: String) -> String { func convertURLRequestToCurlCommand(request: NSURLRequest) -> String { let method = request.HTTPMethod ?? "GET" - var returnValue = "curl -i -v -X \(method) " + var returnValue = "curl -X \(method) " if request.HTTPMethod == "POST" && request.HTTPBody != nil { let maybeBody = NSString(data: request.HTTPBody!, encoding: NSUTF8StringEncoding) as? String @@ -71,12 +71,14 @@ func convertURLRequestToCurlCommand(request: NSURLRequest) -> String { for (key, value) in request.allHTTPHeaderFields ?? [:] { let escapedKey = escapeTerminalString((key as String) ?? "") let escapedValue = escapeTerminalString((value as String) ?? "") - returnValue += "-H \"\(escapedKey): \(escapedValue)\" " + returnValue += "\n -H \"\(escapedKey): \(escapedValue)\" " } let URLString = request.URL?.absoluteString ?? "" - returnValue += "\"\(escapeTerminalString(URLString))\"" + returnValue += "\n\"\(escapeTerminalString(URLString))\"" + + returnValue += " -i -v" return returnValue } diff --git a/RxCocoa/Common/RxCocoa.swift b/RxCocoa/Common/RxCocoa.swift index a7243890..9c58d6c1 100644 --- a/RxCocoa/Common/RxCocoa.swift +++ b/RxCocoa/Common/RxCocoa.swift @@ -278,7 +278,7 @@ func castOrFatalError(value: AnyObject!, message: String) -> T { return result } -func castOrFatalError(value: AnyObject!) -> T { +func castOrFatalError(value: Any!) -> T { let maybeResult: T? = value as? T guard let result = maybeResult else { rxFatalError("Failure converting from \(value) to \(T.self)") diff --git a/RxCocoa/Common/_RXDelegateProxy.m b/RxCocoa/Common/_RXDelegateProxy.m index 03595af6..581fbabb 100644 --- a/RxCocoa/Common/_RXDelegateProxy.m +++ b/RxCocoa/Common/_RXDelegateProxy.m @@ -41,6 +41,8 @@ static NSMutableDictionary *forwardableSelectorsPerClass = nil; for (unsigned int i = 0; i < numberOfBaseProtocols; ++i) { [selectors unionSet:[self collectSelectorsForProtocol:pSubprotocols[i]]]; } + + free(pSubprotocols); return selectors; } diff --git a/RxCocoa/OSX/NSButton+Rx.swift b/RxCocoa/OSX/NSButton+Rx.swift index 0c6e5f6c..c18492a6 100644 --- a/RxCocoa/OSX/NSButton+Rx.swift +++ b/RxCocoa/OSX/NSButton+Rx.swift @@ -25,10 +25,13 @@ extension NSButton { Reactive wrapper for `state` property`. */ public var rx_state: ControlProperty { - return rx_value(getter: { [weak self] in - return self?.state ?? 0 - }, setter: { [weak self] state in - self?.state = state - }) + return NSButton.rx_value( + self, + getter: { control in + return control.state + }, setter: { control, state in + control.state = state + } + ) } } \ No newline at end of file diff --git a/RxCocoa/OSX/NSControl+Rx.swift b/RxCocoa/OSX/NSControl+Rx.swift index 7380a2e2..a2f6065d 100644 --- a/RxCocoa/OSX/NSControl+Rx.swift +++ b/RxCocoa/OSX/NSControl+Rx.swift @@ -43,57 +43,48 @@ extension NSControl { return ControlEvent(events: source) } - func rx_value(getter getter: () -> T, setter: T -> Void) -> ControlProperty { + /** + You might be wondering why the ugly `as!` casts etc, well, for some reason if + Swift compiler knows C is UIControl type and optimizations are turned on, it will crash. + + Can somebody offer poor Swift compiler writers some other better job maybe, this is becoming + ridiculous. So much time wasted ... + */ + static func rx_value(control: C, getter: (C) -> T, setter: (C, T) -> Void) -> ControlProperty { MainScheduler.ensureExecutingOnScheduler() - let source = rx_lazyInstanceObservable(&rx_value_key) { () -> Observable in - return Observable.create { [weak self] observer in - guard let control = self else { + let source = (control as! NSObject).rx_lazyInstanceObservable(&rx_value_key) { () -> Observable in + return Observable.create { [weak weakControl = control] (observer: AnyObserver) in + guard let control = weakControl else { observer.on(.Completed) return NopDisposable.instance } - observer.on(.Next(getter())) + observer.on(.Next(getter(control))) - let observer = ControlTarget(control: control) { control in - observer.on(.Next(getter())) + let observer = ControlTarget(control: control as! NSControl) { _ in + if let control = weakControl { + observer.on(.Next(getter(control))) + } } return observer } - .distinctUntilChanged() - .takeUntil(self.rx_deallocated) + .distinctUntilChanged() + .takeUntil((control as! NSObject).rx_deallocated) } + let bindingObserver = UIBindingObserver(UIElement: control, binding: setter) - return ControlProperty(values: source, valueSink: AnyObserver { event in - switch event { - case .Next(let value): - setter(value) - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break - } - }) + return ControlProperty(values: source, valueSink: bindingObserver) } /** Bindable sink for `enabled` property. */ public var rx_enabled: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.enabled = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { (owner, value) in + owner.enabled = value + }.asObserver() } } \ No newline at end of file diff --git a/RxCocoa/OSX/NSImageView+Rx.swift b/RxCocoa/OSX/NSImageView+Rx.swift index 0ff16138..a494152c 100644 --- a/RxCocoa/OSX/NSImageView+Rx.swift +++ b/RxCocoa/OSX/NSImageView+Rx.swift @@ -27,31 +27,21 @@ extension NSImageView { - parameter transitionType: Optional transition type while setting the image (kCATransitionFade, kCATransitionMoveIn, ...) */ public func rx_imageAnimated(transitionType: String?) -> AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - if let transitionType = transitionType { - if value != nil { - let transition = CATransition() - transition.duration = 0.25 - transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - transition.type = transitionType - self?.layer?.addAnimation(transition, forKey: kCATransition) - } + return UIBindingObserver(UIElement: self) { control, value in + if let transitionType = transitionType { + if value != nil { + let transition = CATransition() + transition.duration = 0.25 + transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + transition.type = transitionType + control.layer?.addAnimation(transition, forKey: kCATransition) } - else { - self?.layer?.removeAllAnimations() - } - self?.image = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break } - } + else { + control.layer?.removeAllAnimations() + } + control.image = value + }.asObserver() } } diff --git a/RxCocoa/OSX/NSSlider+Rx.swift b/RxCocoa/OSX/NSSlider+Rx.swift index bbe8f33f..b1d9edd9 100644 --- a/RxCocoa/OSX/NSSlider+Rx.swift +++ b/RxCocoa/OSX/NSSlider+Rx.swift @@ -18,11 +18,15 @@ extension NSSlider { Reactive wrapper for `value` property. */ public var rx_value: ControlProperty { - return rx_value(getter: { [weak self] in - return self?.doubleValue ?? 0 - }, setter: { [weak self] value in - self?.doubleValue = value - }) + return NSControl.rx_value( + self, + getter: { control in + return control.doubleValue + }, + setter: { control, value in + control.doubleValue = value + } + ) } } \ No newline at end of file diff --git a/RxCocoa/OSX/NSTextField+Rx.swift b/RxCocoa/OSX/NSTextField+Rx.swift index d3d6f831..568cf4de 100644 --- a/RxCocoa/OSX/NSTextField+Rx.swift +++ b/RxCocoa/OSX/NSTextField+Rx.swift @@ -105,20 +105,12 @@ extension NSTextField { let source = Observable.deferred { [weak self] in delegate.textSubject.startWith(self?.stringValue ?? "") }.takeUntil(rx_deallocated) - - return ControlProperty(values: source, valueSink: AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.stringValue = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - }) + + let observer = UIBindingObserver(UIElement: self) { control, value in + control.stringValue = value + } + + return ControlProperty(values: source, valueSink: observer.asObserver()) } } diff --git a/RxCocoa/OSX/NSView+Rx.swift b/RxCocoa/OSX/NSView+Rx.swift index 04bcc221..57a1e005 100644 --- a/RxCocoa/OSX/NSView+Rx.swift +++ b/RxCocoa/OSX/NSView+Rx.swift @@ -17,37 +17,17 @@ extension NSView { Bindable sink for `hidden` property. */ public var rx_hidden: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.hidden = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { view, value in + view.hidden = value + }.asObserver() } /** Bindable sink for `alphaValue` property. */ public var rx_alpha: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.alphaValue = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { view, value in + view.alphaValue = value + }.asObserver() } } \ No newline at end of file diff --git a/RxCocoa/iOS/DataSources/RxCollectionViewReactiveArrayDataSource.swift b/RxCocoa/iOS/DataSources/RxCollectionViewReactiveArrayDataSource.swift index 2c1aa325..2ff9e097 100644 --- a/RxCocoa/iOS/DataSources/RxCollectionViewReactiveArrayDataSource.swift +++ b/RxCocoa/iOS/DataSources/RxCollectionViewReactiveArrayDataSource.swift @@ -50,15 +50,10 @@ class RxCollectionViewReactiveArrayDataSourceSequenceWrapper } func collectionView(collectionView: UICollectionView, observedEvent: Event) { - switch observedEvent { - case .Next(let value): - super.collectionView(collectionView, observedElements: Array(value)) - self.itemModels = Array(value) - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break - } + UIBindingObserver(UIElement: self) { collectionViewDataSource, sectionModels in + let sections = Array(sectionModels) + collectionViewDataSource.collectionView(collectionView, observedElements: sections) + }.on(observedEvent) } } diff --git a/RxCocoa/iOS/DataSources/RxTableViewReactiveArrayDataSource.swift b/RxCocoa/iOS/DataSources/RxTableViewReactiveArrayDataSource.swift index 09312322..e3e80dec 100644 --- a/RxCocoa/iOS/DataSources/RxTableViewReactiveArrayDataSource.swift +++ b/RxCocoa/iOS/DataSources/RxTableViewReactiveArrayDataSource.swift @@ -51,14 +51,10 @@ class RxTableViewReactiveArrayDataSourceSequenceWrapper } func tableView(tableView: UITableView, observedEvent: Event) { - switch observedEvent { - case .Next(let value): - super.tableView(tableView, observedElements: Array(value)) - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break - } + UIBindingObserver(UIElement: self) { tableViewDataSource, sectionModels in + let sections = Array(sectionModels) + tableViewDataSource.tableView(tableView, observedElements: sections) + }.on(observedEvent) } } diff --git a/RxCocoa/iOS/UIActivityIndicatorView+Rx.swift b/RxCocoa/iOS/UIActivityIndicatorView+Rx.swift index 14a10277..f1a76967 100644 --- a/RxCocoa/iOS/UIActivityIndicatorView+Rx.swift +++ b/RxCocoa/iOS/UIActivityIndicatorView+Rx.swift @@ -19,22 +19,13 @@ extension UIActivityIndicatorView { Bindable sink for `startAnimating()`, `stopAnimating()` methods. */ public var rx_animating: AnyObserver { - return AnyObserver {event in - MainScheduler.ensureExecutingOnScheduler() - - switch (event) { - case .Next(let value): - if value { - self.startAnimating() - } else { - self.stopAnimating() - } - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break + return UIBindingObserver(UIElement: self) { activityIndicator, active in + if active { + self.startAnimating() + } else { + self.stopAnimating() } - } + }.asObserver() } } diff --git a/RxCocoa/iOS/UIApplication+Rx.swift b/RxCocoa/iOS/UIApplication+Rx.swift index a4072294..1bf270da 100644 --- a/RxCocoa/iOS/UIApplication+Rx.swift +++ b/RxCocoa/iOS/UIApplication+Rx.swift @@ -8,30 +8,23 @@ import Foundation -#if os(iOS) || os(tvOS) +#if os(iOS) import UIKit - + #if !RX_NO_MODULE import RxSwift #endif - + extension UIApplication { /** Bindable sink for `networkActivityIndicatorVisible`. */ public var rx_networkActivityIndicatorVisible: AnyObserver { - return AnyObserver { event in - MainScheduler.ensureExecutingOnScheduler() - switch event { - case .Next(let value): - self.networkActivityIndicatorVisible = value - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { application, active in + application.networkActivityIndicatorVisible = active + }.asObserver() } } -#endif \ No newline at end of file +#endif + diff --git a/RxCocoa/iOS/UIBarButtonItem+Rx.swift b/RxCocoa/iOS/UIBarButtonItem+Rx.swift index 0f048016..ef45e73d 100644 --- a/RxCocoa/iOS/UIBarButtonItem+Rx.swift +++ b/RxCocoa/iOS/UIBarButtonItem+Rx.swift @@ -21,19 +21,9 @@ extension UIBarButtonItem { Bindable sink for `enabled` property. */ public var rx_enabled: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.enabled = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { UIElement, value in + UIElement.enabled = value + }.asObserver() } /** diff --git a/RxCocoa/iOS/UICollectionView+Rx.swift b/RxCocoa/iOS/UICollectionView+Rx.swift index a734105c..8147c6ac 100644 --- a/RxCocoa/iOS/UICollectionView+Rx.swift +++ b/RxCocoa/iOS/UICollectionView+Rx.swift @@ -27,10 +27,13 @@ extension UICollectionView { */ public func rx_itemsWithCellFactory (source: O) - (cellFactory: (UICollectionView, Int, S.Generator.Element) -> UICollectionViewCell) + -> (cellFactory: (UICollectionView, Int, S.Generator.Element) -> UICollectionViewCell) -> Disposable { - let dataSource = RxCollectionViewReactiveArrayDataSourceSequenceWrapper(cellFactory: cellFactory) - return self.rx_itemsWithDataSource(dataSource)(source: source) + return { cellFactory in + let dataSource = RxCollectionViewReactiveArrayDataSourceSequenceWrapper(cellFactory: cellFactory) + return self.rx_itemsWithDataSource(dataSource)(source: source) + } + } /** @@ -44,17 +47,21 @@ extension UICollectionView { */ public func rx_itemsWithCellIdentifier (cellIdentifier: String, cellType: Cell.Type = Cell.self) - (source: O) - (configureCell: (Int, S.Generator.Element, Cell) -> Void) + -> (source: O) + -> (configureCell: (Int, S.Generator.Element, Cell) -> Void) -> Disposable { - let dataSource = RxCollectionViewReactiveArrayDataSourceSequenceWrapper { (cv, i, item) in - let indexPath = NSIndexPath(forItem: i, inSection: 0) - let cell = cv.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) as! Cell - configureCell(i, item, cell) - return cell + return { source in + return { configureCell in + let dataSource = RxCollectionViewReactiveArrayDataSourceSequenceWrapper { (cv, i, item) in + let indexPath = NSIndexPath(forItem: i, inSection: 0) + let cell = cv.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) as! Cell + configureCell(i, item, cell) + return cell + } + + return self.rx_itemsWithDataSource(dataSource)(source: source) + } } - - return self.rx_itemsWithDataSource(dataSource)(source: source) } /** @@ -66,13 +73,15 @@ extension UICollectionView { */ public func rx_itemsWithDataSource, S: SequenceType, O: ObservableType where DataSource.Element == S, O.E == S> (dataSource: DataSource) - (source: O) + -> (source: O) -> Disposable { - return source.subscribeProxyDataSourceForObject(self, dataSource: dataSource, retainDataSource: false) { [weak self] (_: RxCollectionViewDataSourceProxy, event) -> Void in - guard let collectionView = self else { - return + return { source in + return source.subscribeProxyDataSourceForObject(self, dataSource: dataSource, retainDataSource: false) { [weak self] (_: RxCollectionViewDataSourceProxy, event) -> Void in + guard let collectionView = self else { + return + } + dataSource.collectionView(collectionView, observedEvent: event) } - dataSource.collectionView(collectionView, observedEvent: event) } } } diff --git a/RxCocoa/iOS/UIControl+Rx.swift b/RxCocoa/iOS/UIControl+Rx.swift index 3e7f356f..92299e30 100644 --- a/RxCocoa/iOS/UIControl+Rx.swift +++ b/RxCocoa/iOS/UIControl+Rx.swift @@ -20,19 +20,18 @@ extension UIControl { Bindable sink for `enabled` property. */ public var rx_enabled: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.enabled = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { control, value in + control.enabled = value + }.asObserver() + } + + /** + Bindable sink for `selected` property. + */ + public var rx_selected: AnyObserver { + return UIBindingObserver(UIElement: self) { control, selected in + control.selected = selected + }.asObserver() } /** @@ -62,39 +61,38 @@ extension UIControl { return ControlEvent(events: source) } - func rx_value(getter getter: () -> T, setter: T -> Void) -> ControlProperty { - let source: Observable = Observable.create { [weak self] observer in - guard let control = self else { - observer.on(.Completed) - return NopDisposable.instance - } + /** + You might be wondering why the ugly `as!` casts etc, well, for some reason if + Swift compiler knows C is UIControl type and optimizations are turned on, it will crash. - observer.on(.Next(getter())) + Can somebody offer poor Swift compiler writers some other better job maybe, this is becoming + ridiculous. So much time wasted ... + */ + static func rx_value(control: C, getter: (C) -> T, setter: (C, T) -> Void) -> ControlProperty { + let source: Observable = Observable.create { [weak weakControl = control] observer in + guard let control = weakControl else { + observer.on(.Completed) + return NopDisposable.instance + } - let controlTarget = ControlTarget(control: control, controlEvents: [.AllEditingEvents, .ValueChanged]) { control in - observer.on(.Next(getter())) + observer.on(.Next(getter(control))) + + let controlTarget = ControlTarget(control: control as! UIControl, controlEvents: [.AllEditingEvents, .ValueChanged]) { _ in + if let control = weakControl { + observer.on(.Next(getter(control))) + } + } + + return AnonymousDisposable { + controlTarget.dispose() + } } - - return AnonymousDisposable { - controlTarget.dispose() - } - } .distinctUntilChanged() - .takeUntil(rx_deallocated) - - return ControlProperty(values: source, valueSink: AnyObserver { event in - MainScheduler.ensureExecutingOnScheduler() + .takeUntil((control as! NSObject).rx_deallocated) - switch event { - case .Next(let value): - setter(value) - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - }) + let bindingObserver = UIBindingObserver(UIElement: control, binding: setter) + + return ControlProperty(values: source, valueSink: bindingObserver) } } diff --git a/RxCocoa/iOS/UIDatePicker+Rx.swift b/RxCocoa/iOS/UIDatePicker+Rx.swift index 36f594c1..a09c9c6f 100644 --- a/RxCocoa/iOS/UIDatePicker+Rx.swift +++ b/RxCocoa/iOS/UIDatePicker+Rx.swift @@ -20,11 +20,14 @@ extension UIDatePicker { Reactive wrapper for `date` property. */ public var rx_date: ControlProperty { - return rx_value(getter: { [weak self] in - self?.date ?? NSDate() - }, setter: { [weak self] value in - self?.date = value - }) + return UIControl.rx_value( + self, + getter: { datePicker in + datePicker.date + }, setter: { datePicker, value in + datePicker.date = value + } + ) } } diff --git a/RxCocoa/iOS/UIImageView+Rx.swift b/RxCocoa/iOS/UIImageView+Rx.swift index 5b84643e..6d0caf04 100644 --- a/RxCocoa/iOS/UIImageView+Rx.swift +++ b/RxCocoa/iOS/UIImageView+Rx.swift @@ -29,31 +29,21 @@ extension UIImageView { - parameter transitionType: Optional transition type while setting the image (kCATransitionFade, kCATransitionMoveIn, ...) */ public func rx_imageAnimated(transitionType: String?) -> AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - if let transitionType = transitionType { - if value != nil { - let transition = CATransition() - transition.duration = 0.25 - transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - transition.type = transitionType - self?.layer.addAnimation(transition, forKey: kCATransition) - } + return UIBindingObserver(UIElement: self) { imageView, image in + if let transitionType = transitionType { + if image != nil { + let transition = CATransition() + transition.duration = 0.25 + transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + transition.type = transitionType + imageView.layer.addAnimation(transition, forKey: kCATransition) } - else { - self?.layer.removeAllAnimations() - } - self?.image = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break } - } + else { + imageView.layer.removeAllAnimations() + } + imageView.image = image + }.asObserver() } } diff --git a/RxCocoa/iOS/UILabel+Rx.swift b/RxCocoa/iOS/UILabel+Rx.swift index 69aa9729..7e5822fb 100644 --- a/RxCocoa/iOS/UILabel+Rx.swift +++ b/RxCocoa/iOS/UILabel+Rx.swift @@ -20,38 +20,18 @@ extension UILabel { Bindable sink for `text` property. */ public var rx_text: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.text = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { label, text in + label.text = text + }.asObserver() } /** Bindable sink for `attributedText` property. */ public var rx_attributedText: AnyObserver { - 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 - } - } + return UIBindingObserver(UIElement: self) { label, text in + label.attributedText = text + }.asObserver() } } diff --git a/RxCocoa/iOS/UIProgressView+Rx.swift b/RxCocoa/iOS/UIProgressView+Rx.swift new file mode 100644 index 00000000..3b59dd71 --- /dev/null +++ b/RxCocoa/iOS/UIProgressView+Rx.swift @@ -0,0 +1,30 @@ +// +// UIProgressView+Rx.swift +// Rx +// +// Created by Samuel Bae on 2/27/16. +// Copyright © 2016 Krunoslav Zaher. All rights reserved. +// + +#if os(iOS) || os(tvOS) + +import Foundation +#if !RX_NO_MODULE +import RxSwift +#endif +import UIKit + +extension UIProgressView { + + /** + Bindable sink for `progress` property + */ + public var rx_progress: AnyObserver { + return UIBindingObserver(UIElement: self) { progressView, progress in + progressView.progress = progress + }.asObserver() + } + +} + +#endif \ No newline at end of file diff --git a/RxCocoa/iOS/UIRefreshControl+Rx.swift b/RxCocoa/iOS/UIRefreshControl+Rx.swift index 8107252b..4c106d25 100644 --- a/RxCocoa/iOS/UIRefreshControl+Rx.swift +++ b/RxCocoa/iOS/UIRefreshControl+Rx.swift @@ -19,23 +19,13 @@ extension UIRefreshControl { Bindable sink for `beginRefreshing()`, `endRefreshing()` methods. */ public var rx_refreshing: AnyObserver { - return AnyObserver {event in - MainScheduler.ensureExecutingOnScheduler() - - switch (event) { - case .Next(let value): - if value { - self.beginRefreshing() - } else { - self.endRefreshing() - } - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break + return UIBindingObserver(UIElement: self) { refreshControl, refresh in + if refresh { + refreshControl.beginRefreshing() + } else { + refreshControl.endRefreshing() } - } + }.asObserver() } } diff --git a/RxCocoa/iOS/UIScrollView+Rx.swift b/RxCocoa/iOS/UIScrollView+Rx.swift index 004dbec6..2aaf8e4d 100644 --- a/RxCocoa/iOS/UIScrollView+Rx.swift +++ b/RxCocoa/iOS/UIScrollView+Rx.swift @@ -39,17 +39,12 @@ extension UIScrollView { */ public var rx_contentOffset: ControlProperty { let proxy = proxyForObject(RxScrollViewDelegateProxy.self, self) - - return ControlProperty(values: proxy.contentOffsetSubject, valueSink: AnyObserver { [weak self] event in - switch event { - case .Next(let value): - self?.contentOffset = value - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break - } - }) + + let bindingObserver = UIBindingObserver(UIElement: self) { scrollView, contentOffset in + scrollView.contentOffset = contentOffset + } + + return ControlProperty(values: proxy.contentOffsetSubject, valueSink: bindingObserver) } /** diff --git a/RxCocoa/iOS/UISearchBar+Rx.swift b/RxCocoa/iOS/UISearchBar+Rx.swift index 5c8c07ba..13bd2d32 100644 --- a/RxCocoa/iOS/UISearchBar+Rx.swift +++ b/RxCocoa/iOS/UISearchBar+Rx.swift @@ -40,17 +40,12 @@ extension UISearchBar { } .startWith(text) } + + let bindingObserver = UIBindingObserver(UIElement: self) { (searchBar, text: String) in + searchBar.text = text + } - return ControlProperty(values: source, valueSink: AnyObserver { [weak self] event in - switch event { - case .Next(let value): - self?.text = value - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break - } - }) + return ControlProperty(values: source, valueSink: bindingObserver) } } diff --git a/RxCocoa/iOS/UISegmentedControl+Rx.swift b/RxCocoa/iOS/UISegmentedControl+Rx.swift index bc0f2c3f..be46f4a7 100644 --- a/RxCocoa/iOS/UISegmentedControl+Rx.swift +++ b/RxCocoa/iOS/UISegmentedControl+Rx.swift @@ -20,11 +20,14 @@ extension UISegmentedControl { Reactive wrapper for `selectedSegmentIndex` property. */ public var rx_value: ControlProperty { - return rx_value(getter: { [weak self] in - self?.selectedSegmentIndex ?? 0 - }, setter: { [weak self] value in - self?.selectedSegmentIndex = value - }) + return UIControl.rx_value( + self, + getter: { segmentedControl in + segmentedControl.selectedSegmentIndex + }, setter: { segmentedControl, value in + segmentedControl.selectedSegmentIndex = value + } + ) } } diff --git a/RxCocoa/iOS/UISlider+Rx.swift b/RxCocoa/iOS/UISlider+Rx.swift index 0dabe9aa..2c02bd08 100644 --- a/RxCocoa/iOS/UISlider+Rx.swift +++ b/RxCocoa/iOS/UISlider+Rx.swift @@ -20,11 +20,14 @@ extension UISlider { Reactive wrapper for `value` property. */ public var rx_value: ControlProperty { - return rx_value(getter: { [weak self] in - self?.value ?? 0.0 - }, setter: { [weak self] value in - self?.value = value - }) + return UIControl.rx_value( + self, + getter: { slider in + slider.value + }, setter: { slider, value in + slider.value = value + } + ) } } diff --git a/RxCocoa/iOS/UIStepper+Rx.swift b/RxCocoa/iOS/UIStepper+Rx.swift index 7f29d934..9dc40c5c 100644 --- a/RxCocoa/iOS/UIStepper+Rx.swift +++ b/RxCocoa/iOS/UIStepper+Rx.swift @@ -20,11 +20,14 @@ extension UIStepper { Reactive wrapper for `value` property. */ public var rx_value: ControlProperty { - return rx_value(getter: { [weak self] in - self?.value ?? 0 - }, setter: { [weak self] value in - self?.value = value - }) + return UIControl.rx_value( + self, + getter: { stepper in + stepper.value + }, setter: { stepper, value in + stepper.value = value + } + ) } } diff --git a/RxCocoa/iOS/UISwitch+Rx.swift b/RxCocoa/iOS/UISwitch+Rx.swift index 50b0285c..bfc5b11f 100644 --- a/RxCocoa/iOS/UISwitch+Rx.swift +++ b/RxCocoa/iOS/UISwitch+Rx.swift @@ -20,11 +20,14 @@ extension UISwitch { Reactive wrapper for `on` property. */ public var rx_value: ControlProperty { - return rx_value(getter: { [weak self] in - self?.on ?? false - }, setter: { [weak self] value in - self?.on = value - }) + return UIControl.rx_value( + self, + getter: { uiSwitch in + uiSwitch.on + }, setter: { uiSwitch, value in + uiSwitch.on = value + } + ) } } diff --git a/RxCocoa/iOS/UITableView+Rx.swift b/RxCocoa/iOS/UITableView+Rx.swift index 678bda59..28bc25ac 100644 --- a/RxCocoa/iOS/UITableView+Rx.swift +++ b/RxCocoa/iOS/UITableView+Rx.swift @@ -27,11 +27,13 @@ extension UITableView { */ public func rx_itemsWithCellFactory (source: O) - (cellFactory: (UITableView, Int, S.Generator.Element) -> UITableViewCell) + -> (cellFactory: (UITableView, Int, S.Generator.Element) -> UITableViewCell) -> Disposable { - let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper(cellFactory: cellFactory) - - return self.rx_itemsWithDataSource(dataSource)(source: source) + return { cellFactory in + let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper(cellFactory: cellFactory) + + return self.rx_itemsWithDataSource(dataSource)(source: source) + } } /** @@ -45,17 +47,20 @@ extension UITableView { */ public func rx_itemsWithCellIdentifier (cellIdentifier: String, cellType: Cell.Type = Cell.self) - (source: O) - (configureCell: (Int, S.Generator.Element, Cell) -> Void) + -> (source: O) + -> (configureCell: (Int, S.Generator.Element, Cell) -> Void) -> Disposable { - let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper { (tv, i, item) in - let indexPath = NSIndexPath(forItem: i, inSection: 0) - let cell = tv.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! Cell - configureCell(i, item, cell) - return cell + return { source in + return { configureCell in + let dataSource = RxTableViewReactiveArrayDataSourceSequenceWrapper { (tv, i, item) in + let indexPath = NSIndexPath(forItem: i, inSection: 0) + let cell = tv.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! Cell + configureCell(i, item, cell) + return cell + } + return self.rx_itemsWithDataSource(dataSource)(source: source) + } } - - return self.rx_itemsWithDataSource(dataSource)(source: source) } /** @@ -67,13 +72,15 @@ extension UITableView { */ public func rx_itemsWithDataSource, S: SequenceType, O: ObservableType where DataSource.Element == S, O.E == S> (dataSource: DataSource) - (source: O) + -> (source: O) -> Disposable { - return source.subscribeProxyDataSourceForObject(self, dataSource: dataSource, retainDataSource: false) { [weak self] (_: RxTableViewDataSourceProxy, event) -> Void in - guard let tableView = self else { - return + return { source in + return source.subscribeProxyDataSourceForObject(self, dataSource: dataSource, retainDataSource: false) { [weak self] (_: RxTableViewDataSourceProxy, event) -> Void in + guard let tableView = self else { + return + } + dataSource.tableView(tableView, observedEvent: event) } - dataSource.tableView(tableView, observedEvent: event) } } } @@ -148,6 +155,18 @@ extension UITableView { return ControlEvent(events: source) } + /** + Reactive wrapper for `delegate` message `tableView:accessoryButtonTappedForRowWithIndexPath:`. + */ + public var rx_itemAccessoryButtonTapped: ControlEvent { + let source: Observable = rx_delegate.observe("tableView:accessoryButtonTappedForRowWithIndexPath:") + .map { a in + return a[1] as! NSIndexPath + } + + return ControlEvent(events: source) + } + /** Reactive wrapper for `delegate` message `tableView:commitEditingStyle:forRowAtIndexPath:`. */ @@ -244,7 +263,7 @@ extension UITableView { let element = try dataSource.modelAtIndexPath(indexPath) - return element as! T + return castOrFatalError(element) } } diff --git a/RxCocoa/iOS/UITextField+Rx.swift b/RxCocoa/iOS/UITextField+Rx.swift index acd00865..35f888fc 100644 --- a/RxCocoa/iOS/UITextField+Rx.swift +++ b/RxCocoa/iOS/UITextField+Rx.swift @@ -20,11 +20,14 @@ extension UITextField { Reactive wrapper for `text` property. */ public var rx_text: ControlProperty { - return rx_value(getter: { [weak self] in - self?.text ?? "" - }, setter: { [weak self] value in - self?.text = value - }) + return UIControl.rx_value( + self, + getter: { textField in + textField.text ?? "" + }, setter: { textField, value in + textField.text = value + } + ) } } diff --git a/RxCocoa/iOS/UITextView+Rx.swift b/RxCocoa/iOS/UITextView+Rx.swift index 6961e766..f21d31a9 100644 --- a/RxCocoa/iOS/UITextView+Rx.swift +++ b/RxCocoa/iOS/UITextView+Rx.swift @@ -45,17 +45,12 @@ extension UITextView { .startWith(text) .distinctUntilChanged() } + + let bindingObserver = UIBindingObserver(UIElement: self) { (textView, text: String) in + textView.text = text + } - return ControlProperty(values: source, valueSink: AnyObserver { [weak self] event in - switch event { - case .Next(let value): - self?.text = value - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break - } - }) + return ControlProperty(values: source, valueSink: bindingObserver) } } diff --git a/RxCocoa/iOS/UIView+Rx.swift b/RxCocoa/iOS/UIView+Rx.swift index 105496f4..ea0ee82b 100644 --- a/RxCocoa/iOS/UIView+Rx.swift +++ b/RxCocoa/iOS/UIView+Rx.swift @@ -19,38 +19,18 @@ extension UIView { Bindable sink for `hidden` property. */ public var rx_hidden: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.hidden = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { view, hidden in + view.hidden = hidden + }.asObserver() } /** Bindable sink for `alpha` property. */ public var rx_alpha: AnyObserver { - return AnyObserver { [weak self] event in - MainScheduler.ensureExecutingOnScheduler() - - switch event { - case .Next(let value): - self?.alpha = value - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { view, alpha in + view.alpha = alpha + }.asObserver() } } diff --git a/RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedAnimatedDataSource.swift b/RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedAnimatedDataSource.swift index 260ba127..c65caed8 100644 --- a/RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedAnimatedDataSource.swift +++ b/RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedAnimatedDataSource.swift @@ -22,24 +22,17 @@ class RxCollectionViewSectionedAnimatedDataSource : RxColle var set = false func collectionView(collectionView: UICollectionView, observedEvent: Event) { - switch observedEvent { - case .Next(let element): + UIBindingObserver(UIElement: self) { ds, element in for c in element { - //print("Animating ==============================\n\(c)\n===============================\n") - - if !set { - setSections(c.finalSections) + if !ds.set { + ds.setSections(c.finalSections) collectionView.reloadData() - set = true + ds.set = true return } - setSections(c.finalSections) + ds.setSections(c.finalSections) collectionView.performBatchUpdates(c) } - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break - } + }.on(observedEvent) } } \ No newline at end of file diff --git a/RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedReloadDataSource.swift b/RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedReloadDataSource.swift index cd47edbb..92542cc3 100644 --- a/RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedReloadDataSource.swift +++ b/RxExample/RxDataSourceStarterKit/DataSources/RxCollectionViewSectionedReloadDataSource.swift @@ -18,14 +18,9 @@ class RxCollectionViewSectionedReloadDataSource : RxCollect typealias Element = [S] func collectionView(collectionView: UICollectionView, observedEvent: Event) { - switch observedEvent { - case .Next(let element): - setSections(element) + UIBindingObserver(UIElement: self) { dataSource, element in + dataSource.setSections(element) collectionView.reloadData() - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break - } + }.on(observedEvent) } } \ No newline at end of file diff --git a/RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedAnimatedDataSource.swift b/RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedAnimatedDataSource.swift index 2ccd5aa2..25e18d02 100644 --- a/RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedAnimatedDataSource.swift +++ b/RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedAnimatedDataSource.swift @@ -21,11 +21,10 @@ class RxTableViewSectionedAnimatedDataSource : RxTableViewS typealias Element = [Changeset] func tableView(tableView: UITableView, observedEvent: Event) { - switch observedEvent { - case .Next(let element): + UIBindingObserver(UIElement: self) { dataSource, element in for c in element { //print("Animating ==============================\n\(c)\n===============================\n") - setSections(c.finalSections) + dataSource.setSections(c.finalSections) if c.reloadData { tableView.reloadData() } @@ -33,10 +32,6 @@ class RxTableViewSectionedAnimatedDataSource : RxTableViewS tableView.performBatchUpdates(c) } } - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break - } + }.on(observedEvent) } } \ No newline at end of file diff --git a/RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedReloadDataSource.swift b/RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedReloadDataSource.swift index 0a2d7b40..5d9095db 100644 --- a/RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedReloadDataSource.swift +++ b/RxExample/RxDataSourceStarterKit/DataSources/RxTableViewSectionedReloadDataSource.swift @@ -21,14 +21,9 @@ class RxTableViewSectionedReloadDataSource : RxTableViewSec typealias Element = [S] func tableView(tableView: UITableView, observedEvent: Event) { - switch observedEvent { - case .Next(let element): - setSections(element) + UIBindingObserver(UIElement: self) { dataSource, element in + dataSource.setSections(element) tableView.reloadData() - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break - } + }.on(observedEvent) } } \ No newline at end of file diff --git a/RxExample/RxDataSourceStarterKit/RxDataSourceStarterKit.swift b/RxExample/RxDataSourceStarterKit/RxDataSourceStarterKit.swift index 8241be5e..e55048d1 100644 --- a/RxExample/RxDataSourceStarterKit/RxDataSourceStarterKit.swift +++ b/RxExample/RxDataSourceStarterKit/RxDataSourceStarterKit.swift @@ -8,14 +8,3 @@ import Foundation -#if !RX_NO_MODULE -func bindingErrorToInterface(error: ErrorType) { - let error = "Binding error to UI: \(error)" - #if DEBUG - fatalError(error) - #else - print(error) - #endif -} - -#endif diff --git a/RxExample/RxExample.xcodeproj/project.pbxproj b/RxExample/RxExample.xcodeproj/project.pbxproj index 54e6f0aa..e7e217be 100644 --- a/RxExample/RxExample.xcodeproj/project.pbxproj +++ b/RxExample/RxExample.xcodeproj/project.pbxproj @@ -402,6 +402,8 @@ C8DF92EA1B0B38C0009BCF9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C8DF92E91B0B38C0009BCF9A /* Images.xcassets */; }; C8DF92EB1B0B38C0009BCF9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C8DF92E91B0B38C0009BCF9A /* Images.xcassets */; }; C8E9D2AF1BD3FD960079D0DB /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C80397391BD3E17D009D8B26 /* ActivityIndicator.swift */; }; + C8F3FFF11C6FD2FA00E60EEC /* UIApplication+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3EE18D11C4D68F900834224 /* UIApplication+Rx.swift */; }; + C8F3FFF51C6FD62E00E60EEC /* UIBindingObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8F3FFF41C6FD62E00E60EEC /* UIBindingObserver.swift */; }; C8F6A12B1BEF9DA3007DF367 /* ConcurrentDispatchQueueScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C894648E1BC6C2B00055219D /* ConcurrentDispatchQueueScheduler.swift */; }; C8F6A12C1BEF9DA3007DF367 /* ConcurrentMainScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8B145051BD2E45200267DCE /* ConcurrentMainScheduler.swift */; }; C8F6A12D1BEF9DA3007DF367 /* CurrentThreadScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C894648F1BC6C2B00055219D /* CurrentThreadScheduler.swift */; }; @@ -923,6 +925,7 @@ C8DF92E91B0B38C0009BCF9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; C8DF92F01B0B3E67009BCF9A /* Info-OSX.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-OSX.plist"; sourceTree = ""; }; C8DF92F21B0B3E71009BCF9A /* Info-iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; + C8F3FFF41C6FD62E00E60EEC /* UIBindingObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIBindingObserver.swift; sourceTree = ""; }; C8F6A1361BEF9DD4007DF367 /* RetryWhen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RetryWhen.swift; sourceTree = ""; }; C8F8C4891C277F460047640B /* CalculatorState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalculatorState.swift; sourceTree = ""; }; C8F8C49C1C277F4F0047640B /* CalculatorAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalculatorAction.swift; sourceTree = ""; }; @@ -1550,6 +1553,7 @@ C89465171BC6C2BC0055219D /* CocoaUnits */ = { isa = PBXGroup; children = ( + C8F3FFF41C6FD62E00E60EEC /* UIBindingObserver.swift */, C80DDEC11BCE9041006A1832 /* Driver */, C89465191BC6C2BC0055219D /* ControlEvent.swift */, C894651B1BC6C2BC0055219D /* ControlProperty.swift */, @@ -2246,6 +2250,7 @@ C8F6A1311BEF9DA3007DF367 /* OperationQueueScheduler.swift in Sources */, CBEE77541BD8C7B700AD584C /* ToArray.swift in Sources */, C89465771BC6C2BC0055219D /* NSNotificationCenter+Rx.swift in Sources */, + C8F3FFF51C6FD62E00E60EEC /* UIBindingObserver.swift in Sources */, C89465091BC6C2B00055219D /* ReplaySubject.swift in Sources */, C8BCD3E01C1480E9005F1280 /* Operators.swift in Sources */, C84CC58E1BDD486300E06A64 /* SynchronizedSubscribeType.swift in Sources */, @@ -2286,6 +2291,7 @@ C89465901BC6C2BC0055219D /* UIButton+Rx.swift in Sources */, C89464DD1BC6C2B00055219D /* Sink.swift in Sources */, C89464BE1BC6C2B00055219D /* Catch.swift in Sources */, + C8F3FFF11C6FD2FA00E60EEC /* UIApplication+Rx.swift in Sources */, C89CDB721BCC45EE002063D9 /* SkipUntil.swift in Sources */, B1604CCB1BE5BC45002E1279 /* UIImageView+DownloadableImage.swift in Sources */, C8297E471B6CF905000589EA /* ViewController.swift in Sources */, @@ -2429,7 +2435,6 @@ C8984C491C36A579001E4272 /* SectionModel.swift in Sources */, C83367251AD029AE00C668A7 /* ImageService.swift in Sources */, C86E2F471AE5A0CA00C31024 /* WikipediaSearchResult.swift in Sources */, - E3EE18D21C4D68F900834224 /* UIApplication+Rx.swift in Sources */, C8984CD11C36BC3E001E4272 /* NumberCell.swift in Sources */, C8A2A2C81B4049E300F11F09 /* PseudoRandomGenerator.swift in Sources */, C8D132151C42B54B00B59FFF /* UIImagePickerController+RxCreate.swift in Sources */, diff --git a/RxExample/RxExample/Examples/GeolocationExample/GeolocationViewController.swift b/RxExample/RxExample/Examples/GeolocationExample/GeolocationViewController.swift index ab24d06e..e94c6c1b 100644 --- a/RxExample/RxExample/Examples/GeolocationExample/GeolocationViewController.swift +++ b/RxExample/RxExample/Examples/GeolocationExample/GeolocationViewController.swift @@ -15,36 +15,24 @@ import CoreLocation private extension UILabel { var rx_driveCoordinates: AnyObserver { - return AnyObserver { [weak self] event in - guard let _self = self else { return } - switch event { - case let .Next(location): - _self.text = "Lat: \(location.latitude)\nLon: \(location.longitude)" - default: - break - } - } + return UIBindingObserver(UIElement: self) { label, location in + label.text = "Lat: \(location.latitude)\nLon: \(location.longitude)" + }.asObserver() } } private extension UIView { var rx_driveAuthorization: AnyObserver { - return AnyObserver { [weak self] event in - guard let _self = self else { return } - switch event { - case let .Next(autorized): - if autorized { - _self.hidden = true - _self.superview?.sendSubviewToBack(_self) - } - else { - _self.hidden = false - _self.superview?.bringSubviewToFront(_self) - } - default: - break + return UIBindingObserver(UIElement: self) { label, authorized in + if authorized { + label.hidden = true + label.superview?.sendSubviewToBack(label) } - } + else { + label.hidden = false + label.superview?.bringSubviewToFront(label) + } + }.asObserver() } } @@ -59,15 +47,16 @@ class GeolocationViewController: ViewController { super.viewDidLoad() let geolocationService = GeolocationService.instance - + + geolocationService.autorized .drive(noGeolocationView.rx_driveAuthorization) .addDisposableTo(disposeBag) - + /* geolocationService.location .drive(label.rx_driveCoordinates) .addDisposableTo(disposeBag) - + button.rx_tap .bindNext { [weak self] in self?.openAppPreferences() @@ -79,7 +68,7 @@ class GeolocationViewController: ViewController { self?.openAppPreferences() } .addDisposableTo(disposeBag) - + */ } private func openAppPreferences() { diff --git a/RxExample/RxExample/Examples/GitHubSearchRepositories/GitHubSearchRepositoriesViewController.swift b/RxExample/RxExample/Examples/GitHubSearchRepositories/GitHubSearchRepositoriesViewController.swift index 006020ec..52c2e610 100644 --- a/RxExample/RxExample/Examples/GitHubSearchRepositories/GitHubSearchRepositoriesViewController.swift +++ b/RxExample/RxExample/Examples/GitHubSearchRepositories/GitHubSearchRepositoriesViewController.swift @@ -95,9 +95,7 @@ class GitHubSearchRepositoriesViewController: ViewController, UITableViewDelegat // activity indicator in status bar // { GitHubSearchRepositoriesAPI.sharedAPI.activityIndicator - .driveNext { active in - UIApplication.sharedApplication().networkActivityIndicatorVisible = active - } + .drive(UIApplication.sharedApplication().rx_networkActivityIndicatorVisible) .addDisposableTo(disposeBag) // } } diff --git a/RxExample/RxExample/Examples/GitHubSearchRepositories/UINavigationController+Extensions.swift b/RxExample/RxExample/Examples/GitHubSearchRepositories/UINavigationController+Extensions.swift index 02b290e3..5ecb41e2 100644 --- a/RxExample/RxExample/Examples/GitHubSearchRepositories/UINavigationController+Extensions.swift +++ b/RxExample/RxExample/Examples/GitHubSearchRepositories/UINavigationController+Extensions.swift @@ -20,22 +20,15 @@ struct Colors { extension UINavigationController { var rx_serviceState: AnyObserver { - 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 + return UIBindingObserver(UIElement: self) { navigationController, maybeServiceState in + // if nil is being bound, then don't change color, it's not perfect, but :) + if let serviceState = maybeServiceState { + let isOffline = serviceState ?? .Online == .Offline - self.navigationBar.backgroundColor = isOffline - ? Colors.OfflineColor - : Colors.OnlineColor - } - case .Error(let error): - bindingErrorToInterface(error) - case .Completed: - break + self.navigationBar.backgroundColor = isOffline + ? Colors.OfflineColor + : Colors.OnlineColor } - } + }.asObserver() } } diff --git a/RxExample/RxExample/Examples/GitHubSignup/BindingExtensions.swift b/RxExample/RxExample/Examples/GitHubSignup/BindingExtensions.swift index 9f24c9bb..b5b61283 100644 --- a/RxExample/RxExample/Examples/GitHubSignup/BindingExtensions.swift +++ b/RxExample/RxExample/Examples/GitHubSignup/BindingExtensions.swift @@ -50,16 +50,9 @@ extension ValidationResult { extension UILabel { var ex_validationResult: AnyObserver { - return AnyObserver { [weak self] event in - switch event { - case let .Next(result): - self?.textColor = result.textColor - self?.text = result.description - case let .Error(error): - bindingErrorToInterface(error) - case .Completed: - break - } - } + return UIBindingObserver(UIElement: self) { label, result in + label.textColor = result.textColor + label.text = result.description + }.asObserver() } } \ No newline at end of file diff --git a/RxExample/RxExample/Examples/GitHubSignup/UsingDriver/GitHubSignupViewController2.swift b/RxExample/RxExample/Examples/GitHubSignup/UsingDriver/GitHubSignupViewController2.swift index f1907d87..5921b6e4 100644 --- a/RxExample/RxExample/Examples/GitHubSignup/UsingDriver/GitHubSignupViewController2.swift +++ b/RxExample/RxExample/Examples/GitHubSignup/UsingDriver/GitHubSignupViewController2.swift @@ -74,7 +74,12 @@ class GitHubSignupViewController2 : ViewController { .addDisposableTo(disposeBag) //} - let tapBackground = UITapGestureRecognizer(target: self, action: Selector("dismissKeyboard:")) + let tapBackground = UITapGestureRecognizer() + tapBackground.rx_event + .subscribeNext { [weak self] _ in + self?.view.endEditing(true) + } + .addDisposableTo(disposeBag) view.addGestureRecognizer(tapBackground) } @@ -95,8 +100,4 @@ class GitHubSignupViewController2 : ViewController { } } - func dismissKeyboard(gr: UITapGestureRecognizer) { - view.endEditing(true) - } - } \ No newline at end of file diff --git a/RxExample/RxExample/Examples/GitHubSignup/UsingVanillaObservables/GitHubSignupViewController1.swift b/RxExample/RxExample/Examples/GitHubSignup/UsingVanillaObservables/GitHubSignupViewController1.swift index 919e3262..aa1b63b9 100644 --- a/RxExample/RxExample/Examples/GitHubSignup/UsingVanillaObservables/GitHubSignupViewController1.swift +++ b/RxExample/RxExample/Examples/GitHubSignup/UsingVanillaObservables/GitHubSignupViewController1.swift @@ -74,7 +74,12 @@ class GitHubSignupViewController1 : ViewController { .addDisposableTo(disposeBag) //} - let tapBackground = UITapGestureRecognizer(target: self, action: Selector("dismissKeyboard:")) + let tapBackground = UITapGestureRecognizer() + tapBackground.rx_event + .subscribeNext { [weak self] _ in + self?.view.endEditing(true) + } + .addDisposableTo(disposeBag) view.addGestureRecognizer(tapBackground) } @@ -95,8 +100,4 @@ class GitHubSignupViewController1 : ViewController { } } - func dismissKeyboard(gr: UITapGestureRecognizer) { - view.endEditing(true) - } - } \ No newline at end of file diff --git a/RxExample/RxExample/Examples/SimpleTableViewExample/SimpleTableViewExampleViewController.swift b/RxExample/RxExample/Examples/SimpleTableViewExample/SimpleTableViewExampleViewController.swift index 4b1b093f..33206ed0 100644 --- a/RxExample/RxExample/Examples/SimpleTableViewExample/SimpleTableViewExampleViewController.swift +++ b/RxExample/RxExample/Examples/SimpleTableViewExample/SimpleTableViewExampleViewController.swift @@ -38,5 +38,13 @@ class SimpleTableViewExampleViewController : ViewController { DefaultWireframe.presentAlert("Tapped `\(value)`") } .addDisposableTo(disposeBag) + + tableView + .rx_itemAccessoryButtonTapped + .subscribeNext { indexPath in + DefaultWireframe.presentAlert("Tapped Detail @ \(indexPath.section),\(indexPath.row)") + } + .addDisposableTo(disposeBag) + } } \ No newline at end of file diff --git a/RxExample/RxExample/Examples/SimpleValidation/SimpleValidationViewController.swift b/RxExample/RxExample/Examples/SimpleValidation/SimpleValidationViewController.swift index d4931404..cb4b3a9f 100644 --- a/RxExample/RxExample/Examples/SimpleValidation/SimpleValidationViewController.swift +++ b/RxExample/RxExample/Examples/SimpleValidation/SimpleValidationViewController.swift @@ -30,7 +30,7 @@ class SimpleValidationViewController : ViewController { super.viewDidLoad() usernameValidOutlet.text = "Username has to be at least \(minimalUsernameLength) characters" - passwordValidOutlet.text = "Username has to be at least \(minimalPasswordLength) characters" + passwordValidOutlet.text = "Password has to be at least \(minimalPasswordLength) characters" let usernameValid = usernameOutlet.rx_text .map { $0.characters.count >= minimalUsernameLength } diff --git a/RxExample/RxExample/Examples/WikipediaImageSearch/Views/CollectionViewImageCell.swift b/RxExample/RxExample/Examples/WikipediaImageSearch/Views/CollectionViewImageCell.swift index 0e37fa3a..ae5f1e9b 100644 --- a/RxExample/RxExample/Examples/WikipediaImageSearch/Views/CollectionViewImageCell.swift +++ b/RxExample/RxExample/Examples/WikipediaImageSearch/Views/CollectionViewImageCell.swift @@ -16,7 +16,7 @@ import RxCocoa public class CollectionViewImageCell: UICollectionViewCell { @IBOutlet var imageOutlet: UIImageView! - var disposeBag: DisposeBag! + var disposeBag: DisposeBag? var downloadableImage: Observable?{ didSet{ diff --git a/RxExample/RxExample/Examples/WikipediaImageSearch/Views/WikipediaSearchCell.swift b/RxExample/RxExample/Examples/WikipediaImageSearch/Views/WikipediaSearchCell.swift index b36ef9c5..b1a832cb 100644 --- a/RxExample/RxExample/Examples/WikipediaImageSearch/Views/WikipediaSearchCell.swift +++ b/RxExample/RxExample/Examples/WikipediaImageSearch/Views/WikipediaSearchCell.swift @@ -19,7 +19,7 @@ public class WikipediaSearchCell: UITableViewCell { @IBOutlet var URLOutlet: UILabel! @IBOutlet var imagesOutlet: UICollectionView! - var disposeBag: DisposeBag! + var disposeBag: DisposeBag? let imageService = DefaultImageService.sharedImageService diff --git a/RxExample/RxExample/Examples/WikipediaImageSearch/Views/WikipediaSearchViewController.swift b/RxExample/RxExample/Examples/WikipediaImageSearch/Views/WikipediaSearchViewController.swift index e3a1d8cb..a32d8b55 100644 --- a/RxExample/RxExample/Examples/WikipediaImageSearch/Views/WikipediaSearchViewController.swift +++ b/RxExample/RxExample/Examples/WikipediaImageSearch/Views/WikipediaSearchViewController.swift @@ -94,9 +94,7 @@ class WikipediaSearchViewController: ViewController { DefaultImageService.sharedImageService.loadingImage ) { $0 || $1 } .distinctUntilChanged() - .driveNext { active in - UIApplication.sharedApplication().networkActivityIndicatorVisible = active - } + .drive(UIApplication.sharedApplication().rx_networkActivityIndicatorVisible) .addDisposableTo(disposeBag) } } diff --git a/RxExample/RxExample/Services/UIImageView+DownloadableImage.swift b/RxExample/RxExample/Services/UIImageView+DownloadableImage.swift index 7927120f..0d3a6919 100644 --- a/RxExample/RxExample/Services/UIImageView+DownloadableImage.swift +++ b/RxExample/RxExample/Services/UIImageView+DownloadableImage.swift @@ -21,34 +21,21 @@ extension UIImageView{ } func rxex_downloadableImageAnimated(transitionType:String?) -> AnyObserver { - - return AnyObserver { [weak self] event in - - guard let strongSelf = self else { return } - MainScheduler.ensureExecutingOnScheduler() - - switch event{ - case .Next(let value): - for subview in strongSelf.subviews { - subview.removeFromSuperview() - } - switch value{ - case .Content(let image): - strongSelf.rx_image.onNext(image) - case .OfflinePlaceholder: - let label = UILabel(frame: strongSelf.bounds) - label.textAlignment = .Center - label.font = UIFont.systemFontOfSize(35) - label.text = "⚠️" - strongSelf.addSubview(label) - } - case .Error(let error): - bindingErrorToInterface(error) - break - case .Completed: - break + return UIBindingObserver(UIElement: self) { imageView, image in + for subview in imageView.subviews { + subview.removeFromSuperview() } - } + switch image { + case .Content(let image): + imageView.rx_image.onNext(image) + case .OfflinePlaceholder: + let label = UILabel(frame: imageView.bounds) + label.textAlignment = .Center + label.font = UIFont.systemFontOfSize(35) + label.text = "⚠️" + imageView.addSubview(label) + } + }.asObserver() } } #endif diff --git a/RxExample/RxExample/iOS/Main.storyboard b/RxExample/RxExample/iOS/Main.storyboard index ed5602c5..dee95f0b 100644 --- a/RxExample/RxExample/iOS/Main.storyboard +++ b/RxExample/RxExample/iOS/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -41,7 +41,7 @@ - + @@ -52,13 +52,13 @@ - + @@ -502,7 +502,7 @@ To do this automatically, check out the corresponding `Driver` example. - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - +