├── .circleci └── config.yml ├── .gitignore ├── .gitmodules ├── .swiftlint.yml ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── CHANGELOG.md ├── CONDUCT.md ├── CONTRIBUTING.md ├── Cartfile ├── Cartfile.resolved ├── ISSUE_TEMPLATE.md ├── LICENSE ├── Package.swift ├── Playground └── RxSwiftExtPlayground.playground │ ├── Contents.o │ ├── Pages │ ├── Index.xcplaygroundpage │ │ └── Contents.swift │ ├── UIScrollView.reachedBottom.xcplaygroundpage │ │ └── Contents.swift │ ├── UIViewPropertyAnimator.animate.xcplaygroundpage │ │ ├── Contents.swift │ │ └── timeline.xctimeline │ ├── UIViewPropertyAnimator.fractionComplete.xcplaygroundpage │ │ └── Contents.swift │ ├── and.xcplaygroundpage │ │ └── Contents.swift │ ├── apply.xcplaygroundpage │ │ └── Contents.swift │ ├── bufferWithTrigger.xcplaygroundpage │ │ └── Contents.swift │ ├── cascade.xcplaygroundpage │ │ ├── Contents.swift │ │ └── timeline.xctimeline │ ├── catchErrorJustComplete.xcplaygroundpage │ │ └── Contents.swift │ ├── count.xcplaygroundpage │ │ └── Contents.swift │ ├── distinct.xcplaygroundpage │ │ └── Contents.swift │ ├── filterMap.xcplaygroundpage │ │ └── Contents.swift │ ├── fromAsync.xcplaygroundpage │ │ └── Contents.swift │ ├── ignore.xcplaygroundpage │ │ └── Contents.swift │ ├── mapAt.xcplaygroundpage │ │ └── Contents.swift │ ├── mapMany.xcplaygroundpage │ │ └── Contents.swift │ ├── mapTo.xcplaygroundpage │ │ └── Contents.swift │ ├── mergeWith.xcplaygroundpage │ │ └── Contents.swift │ ├── not.xcplaygroundpage │ │ └── Contents.swift │ ├── nwise.xcplaygroundpage │ │ └── Contents.swift │ ├── ofType.xcplaygroundpage │ │ └── Contents.swift │ ├── once.xcplaygroundpage │ │ └── Contents.swift │ ├── partition.xcplaygroundpage │ │ └── Contents.swift │ ├── pausable.xcplaygroundpage │ │ └── Contents.swift │ ├── pausableBuffered.xcplaygroundpage │ │ └── Contents.swift │ ├── repeatWithBehavior.xcplaygroundpage │ │ └── Contents.swift │ ├── retryWithBehavior.xcplaygroundpage │ │ └── Contents.swift │ ├── toSortedArray.xcplaygroundpage │ │ └── Contents.swift │ ├── unwrap.xcplaygroundpage │ │ └── Contents.swift │ ├── withUnretained.xcplaygroundpage │ │ └── Contents.swift │ └── zipWith.xcplaygroundpage │ │ └── Contents.swift │ ├── Sources │ └── SupportCode.swift │ ├── SupportCode.o │ ├── SupportCode.remap │ ├── contents.xcplayground │ └── playground.xcworkspace │ └── contents.xcworkspacedata ├── Readme.md ├── RxSwiftExt.podspec ├── RxSwiftExt.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── RxSwiftExt-iOS.xcscheme │ ├── RxSwiftExt-macOS.xcscheme │ ├── RxSwiftExt-tvOS.xcscheme │ ├── RxSwiftExtPlayground.xcscheme │ ├── RxSwiftExtTests-iOS.xcscheme │ ├── RxSwiftExtTests-macOS.xcscheme │ └── RxSwiftExtTests-tvOS.xcscheme ├── RxSwiftExt.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── Source ├── Info.plist ├── RxCocoa │ ├── UIScrollView+reachedBottom.swift │ ├── UIViewPropertyAnimator+Rx.swift │ ├── distinct+RxCocoa.swift │ ├── mapTo+RxCocoa.swift │ ├── not+RxCocoa.swift │ ├── partition+RxCocoa.swift │ └── unwrap+SharedSequence.swift ├── RxSwift │ ├── ObservableType+Weak.swift │ ├── and.swift │ ├── apply.swift │ ├── bufferWithTrigger.swift │ ├── cascade.swift │ ├── catchErrorJustComplete.swift │ ├── count.swift │ ├── distinct.swift │ ├── filterMap.swift │ ├── fromAsync.swift │ ├── ignore.swift │ ├── ignoreErrors.swift │ ├── ignoreWhen.swift │ ├── mapAt.swift │ ├── mapMany.swift │ ├── mapTo.swift │ ├── materialized+elements.swift │ ├── mergeWith.swift │ ├── not.swift │ ├── nwise.swift │ ├── ofType.swift │ ├── once.swift │ ├── partition.swift │ ├── pausable.swift │ ├── pausableBuffered.swift │ ├── repeatWithBehavior.swift │ ├── retryWithBehavior.swift │ ├── toSortedArray.swift │ ├── unwrap.swift │ └── zipWith.swift └── Tools │ ├── Observable+Alias.swift │ └── curry.swift ├── Tests ├── Info.plist ├── RxCocoa │ ├── DistinctTests+RxCocoa.swift │ ├── MapToTests+RxCocoa.swift │ ├── NotTests+RxCocoa.swift │ ├── PartitionTests+RxCocoa.swift │ ├── UIScrollView+reachedBottomTests.swift │ ├── UIViewPropertyAnimatorTests+Rx.swift │ └── unrwapTests+SharedSequence.swift ├── RxSwift │ ├── BufferWithTriggerTests.swift │ ├── CountTests.swift │ ├── DistinctTests.swift │ ├── IgnoreTests.swift │ ├── MapAtTests.swift │ ├── MapManyTests.swift │ ├── MergeWithTests.swift │ ├── Observable+OfTypeTests.swift │ ├── OnceTests.swift │ ├── PartitionTests.swift │ ├── ToSortedArrayTests.swift │ ├── UnwrapTests.swift │ ├── WeakTarget.swift │ ├── WeakTests.swift │ ├── ZipWithTest.swift │ ├── andTests.swift │ ├── applyTests.swift │ ├── cascadeTests.swift │ ├── catchErrorJustCompleteTests.swift │ ├── filterMapTests.swift │ ├── fromAsyncTests.swift │ ├── ignoreErrorsTests.swift │ ├── ignoreWhenTests.swift │ ├── mapToTests.swift │ ├── materialized+elementsTests.swift │ ├── notTests.swift │ ├── nwiseTests.swift │ ├── pausableBufferedTests.swift │ ├── pausableTests.swift │ ├── repeatWithBehaviorTests.swift │ └── retryWithBehaviorTests.swift └── TestErrors.swift └── scripts ├── bootstrap-if-needed.sh └── bootstrap.sh /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | "RxSwiftExt Tests": 4 | working_directory: ~/RxSwiftCommunity/RxSwiftExt 5 | parallelism: 1 6 | shell: /bin/bash --login 7 | environment: 8 | XCODE_TEST_REPORTS: /tmp/xcode-test-results 9 | LANG: en_US.UTF-8 10 | macos: 11 | xcode: '12.3.0' 12 | steps: 13 | - checkout 14 | - run: mkdir -p $CIRCLE_ARTIFACTS $CIRCLE_TEST_REPORTS $XCODE_TEST_REPORTS 15 | - restore_cache: 16 | keys: 17 | - v1-dep-{{ .Branch }}- 18 | - v1-dep-master- 19 | - v1-dep- 20 | - run: 21 | name: Bootstrap Carthage 22 | command: scripts/bootstrap-if-needed.sh 23 | - save_cache: 24 | key: v1-dep-{{ .Branch }}-{{ epoch }} 25 | paths: 26 | - Carthage 27 | - run: 28 | name: Test macOS 29 | command: set -o pipefail && xcodebuild test -scheme RxSwiftExt-macOS -workspace RxSwiftExt.xcworkspace -sdk macosx -destination "arch=x86_64" | tee "${XCODE_TEST_REPORTS}/xcode_macOS.log" | xcpretty -c -r html --output $XCODE_TEST_REPORTS/macOS.html 30 | - run: 31 | name: Test iOS 32 | command: set -o pipefail && xcodebuild test -scheme RxSwiftExt-iOS -workspace RxSwiftExt.xcworkspace -sdk iphonesimulator -destination "name=iPhone 12" | tee "${XCODE_TEST_REPORTS}/xcode_iOS.log" | xcpretty -c -r html --output $XCODE_TEST_REPORTS/iOS.html 33 | - run: 34 | name: Test tvOS 35 | command: set -o pipefail && xcodebuild test -scheme RxSwiftExt-tvOS -workspace RxSwiftExt.xcworkspace -sdk appletvsimulator -destination "name=Apple TV 4K (at 1080p)" | tee "${XCODE_TEST_REPORTS}/xcode_tvOS.log" | xcpretty -c -r html --output $XCODE_TEST_REPORTS/tvOS.html 36 | - run: 37 | name: Test SPM 38 | command: swift test 39 | - store_artifacts: 40 | path: /tmp/xcode-test-results 41 | "RxSwiftExt Release": 42 | working_directory: ~/RxSwiftCommunity/RxSwiftExt 43 | parallelism: 1 44 | shell: /bin/bash --login 45 | environment: 46 | LANG: en_US.UTF-8 47 | macos: 48 | xcode: '12.3.0' 49 | steps: 50 | - checkout 51 | - run: 52 | name: Setup CocoaPods 53 | command: pod setup 54 | - run: 55 | name: Override Circle CI Config 56 | command: rm ~/.cocoapods/config.yaml # This hack is needed since CircleCI forces --verbose 57 | - run: 58 | name: Push Podspec to Trunk 59 | command: pod trunk push --skip-tests --allow-warnings 60 | workflows: 61 | version: 2 62 | build: 63 | jobs: 64 | - "RxSwiftExt Tests": 65 | filters: 66 | tags: 67 | ignore: /[0-9]+(\.[0-9]+)*/ 68 | release: 69 | jobs: 70 | - "RxSwiftExt Release": 71 | filters: 72 | branches: 73 | ignore: /.*/ 74 | tags: 75 | only: /[0-9]+(\.[0-9]+)*/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | *.o 22 | 23 | # Bundler 24 | .bundle 25 | 26 | # Carthage 27 | Carthage 28 | !Carthage/** 29 | 30 | # AppCode 31 | .idea 32 | 33 | # SPM 34 | .build 35 | 36 | # Cocoapods 37 | # We recommend against adding the Pods directory to your .gitignore. However 38 | # you should judge for yourself, the pros and cons are mentioned at: 39 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 40 | # 41 | # Note: if you ignore the Pods directory, make sure to uncomment 42 | # `pod install` in .travis.yml 43 | # 44 | Pods/ 45 | Podfile.lock 46 | Demo/RxSwiftExtDemo/RxSwiftExtPlayground.playground/Pages/not.xcplaygroundpage/timeline.xctimeline 47 | Package.resolved 48 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Carthage/Checkouts/RxSwift"] 2 | path = Carthage/Checkouts/RxSwift 3 | url = https://github.com/ReactiveX/RxSwift.git 4 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - Source 3 | - Tests 4 | disabled_rules: 5 | - line_length 6 | - trailing_comma 7 | identifier_name: 8 | min_length: 1 9 | opt_in_rules: 10 | - file_header 11 | type_name: 12 | excluded: T 13 | function_body_length: 100 14 | type_body_length: 500 15 | cyclomatic_complexity: 16 | warning: 15 17 | error: 25 18 | file_header: 19 | required_pattern: | 20 | \/\/ 21 | \/\/ .*?\.swift 22 | \/\/ RxSwiftExt 23 | \/\/ 24 | \/\/ Created by .*? on \d{1,2}\/\d{1,2}\/(\d{2}|\d{4})\. 25 | \/\/ Copyright © \d{4} RxSwift Community\. All rights reserved\. 26 | \/\/ -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | - added `reachedBottom(offset:)` for `UIScrollView` 5 | - `once` now uses a `NSRecursiveLock` instead of the deprecated `OSAtomicOr32OrigBarrier` 6 | - Simplify `filterMap(_:)` implementation and make callback throwing 7 | - `once` now uses a `NSRecursiveLock` instead of the deprecated `OSAtomicOr32OrigBarrier` 8 | - added `merge(with:)` for `Observable` 9 | - removed `flatMapSync` operator 10 | - added `apply` for `Completable` and `Maybe` 11 | - added `mapTo` for `Single` and `Maybe` 12 | - added SPM support 13 | - adjusted repeat with behaviour doc 14 | 15 | 5.0.0 16 | ----- 17 | - Update to RxSwift 5.0. 18 | - Requires the Swift 5 compiler (Xcode 10.2 and up).i 19 | - added `partition(_:)` operator 20 | - added `bufferWithTrigger` operator 21 | - added `fromAsync` operator for `Single` 22 | 23 | 4.0.0 24 | ------ 25 | Version 4.x has been skipped to align with RxSwift versioning. 26 | 27 | RxSwiftExt 5.x supports Swift 5.x 28 | RxSwiftExt 3.x supports Swift 4.x 29 | 30 | 3.4.0 31 | ----- 32 | - Fix `withUnretained` so it allows proper destructuring 33 | - added `mapMany` operator 34 | - added `toSortedArray` operator 35 | - rolled `map(to:)` back to `mapTo(_:)` for `SharedSequenceConvertibleType` 36 | - added `unwrap()` operator for SharedSequence 37 | - added `apply(_:)` for `Single` 38 | - added `count` operator 39 | 40 | 3.3.0 41 | ----- 42 | - added UIViewPropertyAnimator `fractionComplete` reactive binder 43 | - added `withUnretained(_:)` operator 44 | - added UIViewPropertyAnimator Reactive Extensions (`animate()` operator) 45 | 46 | 3.2.0 47 | ----- 48 | - added `mapAt(keyPath:)` operator 49 | - added `zip(with:)` operator 50 | - added `ofType(_:)` operator 51 | 52 | 3.1.0 53 | ----- 54 | - added `pairwise()` and `nwise(_:)` operators 55 | - added `and()` operators 56 | - added support for compiling in an iOS App Extension 57 | 58 | 3.0.0 59 | ----- 60 | - added support for Swift 4, RxSwift 4.0 61 | 62 | 2.5.1 63 | ----- 64 | - added support for macOS 65 | 66 | 2.5.0 67 | ----- 68 | - new operator: `filterMap` 69 | - new operator: `flatMapSync` 70 | - new operator: `pausableBuffered` 71 | - fixed issues with the demo Playground 72 | 73 | 2.4.0 74 | ----- 75 | - re-added `errors()` and `elements()` operators for materialized sequences 76 | - fixed Carthage and CI issues 77 | 78 | 2.3.0 79 | ----- 80 | - removed `materialize` and `dematerialize` operators as they now are part of RxSwift 3.4.0 and later 81 | 82 | 2.2.1 83 | ----- 84 | - fixed compilation warning with Swift 3.1 85 | 86 | 2.2.0 87 | ----- 88 | - new operator: `apply` 89 | - added `not`, `mapTo` and `distinct` support for RxCocoa units (`Driver` et al.) 90 | 91 | 2.1.0 92 | ----- 93 | - new operators: `materialize` / `dematerialize` 94 | - extract Playground to use Carthage instead of CocoaPods 95 | 96 | 2.0.0 97 | ----- 98 | - Support Swift 3.0 / RxSwift 3.0 99 | 100 | 1.2 101 | ----- 102 | - new operator: `pausable` 103 | - Tweaked `Podfile` to fix an issue with running the demo playground 104 | 105 | 1.1 106 | ----- 107 | - new operator: `retry` with four different behaviors 108 | - new operator: `catchErrorJustComplete` 109 | - new operator: `ignoreErrors` 110 | 111 | 1.0.1 112 | ----- 113 | - new operator: `distinct` with predicate 114 | - updated to CocoaPods 1.0 115 | 116 | 1.0 117 | ----- 118 | - Initial release. 119 | -------------------------------------------------------------------------------- /CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at fpillet@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Preparing for development 2 | 3 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed. We use it to pull the RxSwiftExt dependency for development. 4 | 2. Check out this repository. 5 | 3. Do a `carthage update --platform ios --no-use-binaries` 6 | 4. Open `RxSwiftExt.xcworkspace` and start hacking! 7 | 8 | ## About contributions 9 | 10 | There are multiple ways you can contribute to this project. 11 | 12 | The easiest way to contribute is to report possible bugs, request features, [discuss ideas](https://github.com/RxSwiftCommunity/RxSwiftExt/issues) and share excitement about this project. We use [issues](https://github.com/RxSwiftCommunity/RxSwiftExt/issues) to discuss new operators for inclusion in the project. 13 | 14 | You can also make pull requests. Other than bug fixes, please make sure you discuss your contribution in Issues first. 15 | 16 | ## Submitting a Pull Request 17 | 18 | When submitting new code, please make sure that it is backed by tests. When submitting code for a new operator, please make sure that: 19 | 20 | - Your code documents what the operator does (see other operators for examples). 21 | - You provide tests for your new operator. 22 | - You provide a demo playground page, and add a reference to this page in the playground index. 23 | - You add documentation of the operator in this repo's `README.md` file. 24 | 25 | **Note**: Base your custom operators only on RxSwift Core operators. Do not use existing RxSwiftExt operators to implement your own operator, unless it's an absolute necessity. 26 | 27 | ## Developer's Certificate of Origin 1.1 28 | 29 | By making a contribution to this project, I certify that: 30 | 31 | - (a) The contribution was created in whole or in part by me and I 32 | have the right to submit it under the open source license 33 | indicated in the file; or 34 | 35 | - (b) The contribution is based upon previous work that, to the best 36 | of my knowledge, is covered under an appropriate open source 37 | license and I have the right under that license to submit that 38 | work with modifications, whether created in whole or in part 39 | by me, under the same open source license (unless I am 40 | permitted to submit under a different license), as indicated 41 | in the file; or 42 | 43 | - (c) The contribution was provided directly to me by some other 44 | person who certified (a), (b) or (c) and I have not modified 45 | it. 46 | 47 | - (d) I understand and agree that this project and the contribution 48 | are public and that a record of the contribution (including all 49 | personal information I submit with it, including my sign-off) is 50 | maintained indefinitely and may be redistributed consistent with 51 | this project or the open source license(s) involved. 52 | 53 | *Wording of statement copied from [elinux.org](http://elinux.org/Developer_Certificate_Of_Origin)* 54 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "ReactiveX/RxSwift" ~> 6.0 2 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "ReactiveX/RxSwift" "6.0.0" 2 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Name and description 2 | 3 | ### Motivation for inclusion 4 | 5 | ### Example of use 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-latest RxSwiftCommunity https://github.com/RxSwiftCommunity 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "RxSwiftExt", 7 | platforms: [ 8 | .iOS(.v9), .tvOS(.v9), .macOS(.v10_11), .watchOS(.v3) 9 | ], 10 | products: [ 11 | .library(name: "RxSwiftExt", targets: ["RxSwiftExt"]), 12 | ], 13 | dependencies: [ 14 | .package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "6.0.0")), 15 | ], 16 | targets: [ 17 | .target(name: "RxSwiftExt", dependencies: ["RxSwift", "RxCocoa"], path: "Source"), 18 | .testTarget(name: "RxSwiftExtTests", dependencies: ["RxSwiftExt", "RxTest"], path: "Tests"), 19 | ], 20 | swiftLanguageVersions: [.v5] 21 | ) 22 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Contents.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxSwiftCommunity/RxSwiftExt/eb4adf9f00a21b3efc3869a5218a6d7517e95222/Playground/RxSwiftExtPlayground.playground/Contents.o -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/UIScrollView.reachedBottom.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxCocoa 14 | import RxSwiftExt 15 | import PlaygroundSupport 16 | import UIKit 17 | 18 | /*: 19 | ## reachedBottom 20 | 21 | `reachedBottom` provides a sequence that emits every time the `UIScrollView` is scrolled to the bottom, with an optional offset. 22 | 23 | Please open the Assistant Editor (⌘⌥⏎) to see the Interactive Live View example. 24 | */ 25 | 26 | final class ReachedBottomViewController: UITableViewController { 27 | private let dataSource = Array(stride(from: 0, to: 28, by: 1)) 28 | private let identifier = "identifier" 29 | private let disposeBag = DisposeBag() 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: identifier) 34 | tableView.rx.reachedBottom(offset: 40) 35 | .subscribe { print("Reached bottom") } 36 | .disposed(by: disposeBag) 37 | } 38 | 39 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 40 | return dataSource.count 41 | } 42 | 43 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 44 | let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) 45 | cell.textLabel?.text = "\(dataSource[indexPath.row])" 46 | return cell 47 | } 48 | } 49 | 50 | // Present the view controller in the Live View window 51 | PlaygroundPage.current.liveView = ReachedBottomViewController() 52 | //: [Next](@next) 53 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/UIViewPropertyAnimator.animate.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxCocoa 14 | import RxSwiftExt 15 | import PlaygroundSupport 16 | import UIKit 17 | 18 | /*: 19 | ## animate 20 | 21 | The `animate` operator provides a Completable that triggers the animation upon subscription and completes when the animation ends. 22 | 23 | Please open the Assistant Editor (⌘⌥⏎) to see the Interactive Live View example. 24 | */ 25 | 26 | class AnimateViewController: UIViewController { 27 | 28 | let disposeBag = DisposeBag() 29 | 30 | lazy var box1: UIView = { 31 | let view = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100)) 32 | view.backgroundColor = .red 33 | return view 34 | }() 35 | 36 | lazy var box2: UIView = { 37 | let view = UIView(frame: CGRect(x: 100, y: 220, width: 100, height: 100)) 38 | view.backgroundColor = .green 39 | return view 40 | }() 41 | 42 | lazy var box3: UIView = { 43 | let view = UIView(frame: CGRect(x: 100, y: 340, width: 100, height: 100)) 44 | view.backgroundColor = .blue 45 | return view 46 | }() 47 | 48 | lazy var button: UIButton = { 49 | let button = UIButton(frame: CGRect(x: 100, y: 500, width: 200, height: 50)) 50 | button.setTitle("Play animation", for: .normal) 51 | button.setTitleColor(.blue, for: .normal) 52 | button.backgroundColor = .white 53 | button.layer.borderColor = UIColor.black.cgColor 54 | button.layer.borderWidth = 2 55 | 56 | return button 57 | }() 58 | 59 | var animator1: UIViewPropertyAnimator! 60 | var animator2: UIViewPropertyAnimator! 61 | var animator3: UIViewPropertyAnimator! 62 | 63 | private func makeAnimators() { 64 | animator1 = UIViewPropertyAnimator(duration: 0.3, curve: .easeInOut) { [unowned self] in 65 | self.box1.transform = self.box1.transform != .identity ? .identity 66 | : self.box1.transform.translatedBy(x: 0, y: -100) 67 | } 68 | 69 | animator2 = UIViewPropertyAnimator(duration: 0.25, curve: .easeInOut) { [unowned self] in 70 | self.box2.transform = self.box2.transform != .identity ? .identity 71 | : self.box2.transform 72 | .translatedBy(x: 0, y: -100) 73 | .scaledBy(x: 1.2, y: 1.2) 74 | } 75 | 76 | animator3 = UIViewPropertyAnimator(duration: 0.15, curve: .easeInOut) { [unowned self] in 77 | self.box3.transform = self.box3.transform != .identity ? .identity 78 | : self.box3.transform 79 | .translatedBy(x: 0, y: -100) 80 | .rotated(by: .pi) 81 | } 82 | } 83 | 84 | override func viewDidLoad() { 85 | super.viewDidLoad() 86 | 87 | // construct the main view 88 | let views = [box1, box2, box3, button] 89 | view.backgroundColor = .white 90 | 91 | views.forEach { 92 | view.addSubview($0) 93 | } 94 | 95 | makeAnimators() 96 | 97 | // Trigger chained animations after a button tap 98 | button.rx.tap 99 | .flatMap { [unowned self] in 100 | self.animator1.rx.animate() 101 | .andThen(self.animator2.rx.animate(afterDelay: 0.15)) 102 | .andThen(self.animator3.rx.animate(afterDelay: 0.1)) 103 | .do(onCompleted: { 104 | self.makeAnimators() 105 | }) 106 | .debug("animation sequence") 107 | } 108 | .subscribe() 109 | .disposed(by: disposeBag) 110 | } 111 | } 112 | 113 | // Present the view controller in the Live View window 114 | PlaygroundPage.current.liveView = AnimateViewController() 115 | 116 | //: [Next](@next) 117 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/UIViewPropertyAnimator.animate.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/UIViewPropertyAnimator.fractionComplete.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxCocoa 14 | import RxSwiftExt 15 | import PlaygroundSupport 16 | import UIKit 17 | 18 | /*: 19 | ## fractionComplete 20 | 21 | The `fractionComplete` binder provides a reactive way to bind to `UIViewPropertyAnimator.fractionComplete`. 22 | 23 | Please open the Assistant Editor (⌘⌥⏎) to see the Interactive Live View example. 24 | */ 25 | 26 | class FractionCompleteViewController: UIViewController { 27 | 28 | let disposeBag = DisposeBag() 29 | 30 | lazy var box: UIView = { 31 | let view = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100)) 32 | view.backgroundColor = .purple 33 | return view 34 | }() 35 | 36 | lazy var slider: UISlider = { 37 | let slider = UISlider(frame: .zero) 38 | slider.translatesAutoresizingMaskIntoConstraints = false 39 | return slider 40 | }() 41 | 42 | lazy var animator: UIViewPropertyAnimator = { 43 | UIViewPropertyAnimator(duration: 1, curve: .linear, animations: { 44 | let transform = CGAffineTransform(translationX: 100, y: 0) 45 | .concatenating(CGAffineTransform(rotationAngle: 360)) 46 | self.box.transform = transform 47 | }) 48 | }() 49 | 50 | lazy var fractionCompleteLabel: UILabel = { 51 | let label = UILabel() 52 | label.translatesAutoresizingMaskIntoConstraints = false 53 | return label 54 | }() 55 | 56 | override func viewDidLoad() { 57 | super.viewDidLoad() 58 | 59 | // construct the main view 60 | view.backgroundColor = .white 61 | setupViewHierarchy() 62 | setupConstraints() 63 | 64 | slider.rx.value.map(CGFloat.init) 65 | .bind(to: animator.rx.fractionComplete) 66 | .disposed(by: disposeBag) 67 | 68 | slider.rx.value 69 | .map { value in 70 | String(format: "fractionComplete: %.2lf", value) 71 | } 72 | .bind(to: fractionCompleteLabel.rx.text) 73 | .disposed(by: disposeBag) 74 | } 75 | 76 | private func setupViewHierarchy() { 77 | [box, slider, fractionCompleteLabel] 78 | .forEach(view.addSubview) 79 | } 80 | 81 | private func setupConstraints() { 82 | 83 | slider.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 84 | slider.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true 85 | slider.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8).isActive = true 86 | 87 | fractionCompleteLabel.topAnchor.constraint(equalTo: slider.bottomAnchor).isActive = true 88 | fractionCompleteLabel.centerXAnchor.constraint(equalTo: slider.centerXAnchor).isActive = true 89 | } 90 | } 91 | 92 | // Present the view controller in the Live View window 93 | PlaygroundPage.current.liveView = FractionCompleteViewController() 94 | 95 | //: [Next](@next) 96 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/and.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | import RxSwift 12 | import RxSwiftExt 13 | 14 | example("Ensure that only `true` values are emitted") { 15 | let allTrue = Observable.of(true, true ,true) 16 | let allTrue2 = Observable.just(true) 17 | let someFalse = Observable.of(true, false, true) 18 | let empty = Observable.empty() 19 | 20 | allTrue.and().subscribe { result in 21 | print("- when all values are true, we get a `true` Maybe: \(result)") 22 | } 23 | 24 | someFalse.and().subscribe { result in 25 | print("- when some values are false, we get a `false` Maybe: \(result)") 26 | } 27 | 28 | empty.and().subscribe { result in 29 | print("- when no value is emitted, we get a Maybe with no result: \(result)") 30 | } 31 | 32 | Observable.and(allTrue, empty).subscribe { result in 33 | print("- mixing an empty sequence and a sequence of true values, we get a `true` Maybe: \(result)") 34 | } 35 | 36 | Observable.and(allTrue, someFalse, empty).subscribe { result in 37 | print("- mixing an empty sequence and sequences of true and false values, we get a `false` Maybe: \(result)") 38 | } 39 | 40 | Observable.and(allTrue, allTrue2).subscribe { result in 41 | print("- mixing sequences of true values, we get a `true` Maybe: \(result)") 42 | } 43 | } 44 | 45 | //: [Next](@next) 46 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/apply.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import Foundation 13 | import RxSwift 14 | import RxSwiftExt 15 | 16 | /*: 17 | ## apply 18 | 19 | The `apply` operator takes a transformation function `(Observable) -> Observable` and applies it to the stream. The purpose of this operator is to provide syntactic sugar for applying multiple operators to the stream, while preserving the chaining operator structure of Rx. 20 | 21 | */ 22 | 23 | func addOne(input: Observable) -> Observable { 24 | return input 25 | .map { $0 + 1 } 26 | .map { "The next number is \($0)" } 27 | } 28 | 29 | func addOne(input: Single) -> Single { 30 | return input 31 | .map { $0 + 1 } 32 | .map { "The next number is \($0)" } 33 | } 34 | 35 | example("apply a transformation") { 36 | let numbers1 = Observable.from([1, 2, 3]) 37 | let numbers2 = Observable.from([100, 101, 102]) 38 | let number3 = Single.just(1) 39 | let number4 = Single.just(100) 40 | 41 | print("apply() calls the transform function on the Observable sequence: ") 42 | 43 | let transformed1 = numbers1.apply(addOne) 44 | let transformed2 = numbers2.apply(addOne) 45 | let transformed3 = number3.apply(addOne) 46 | let transformed4 = number4.apply(addOne) 47 | 48 | transformed1.subscribe(onNext: { result in 49 | print(result) 50 | }) 51 | 52 | transformed2.subscribe(onNext: { result in 53 | print(result) 54 | }) 55 | 56 | transformed3.subscribe(onSuccess: { result in 57 | print(result) 58 | }) 59 | 60 | transformed4.subscribe(onSuccess: { result in 61 | print(result) 62 | }) 63 | } 64 | 65 | //: [Next](@next) 66 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/bufferWithTrigger.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | import RxSwift 12 | import RxSwiftExt 13 | /*: 14 | ## bufferWithTrigger 15 | 16 | Collects the elements of the source observable, and emits them as an array when the trigger emits. 17 | */ 18 | 19 | example("bufferWithTrigger") { 20 | let observable = Observable.interval(1, scheduler: MainScheduler.instance) 21 | 22 | let signalAtThreeSeconds = Observable.timer(3, scheduler: MainScheduler.instance).map { _ in () } 23 | let signalAtFiveSeconds = Observable.timer(5, scheduler: MainScheduler.instance).map { _ in () } 24 | let trigger = Observable.of(signalAtThreeSeconds, signalAtFiveSeconds).merge() 25 | 26 | observable.bufferWithTrigger(trigger).debug("buffer").subscribe() 27 | 28 | playgroundShouldContinueIndefinitely() 29 | } 30 | //: [Next](@next) 31 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/cascade.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## cascade 17 | 18 | The `cascade` operator takes a ordinary sequence (i.e. Array) of observables and cascades through them: 19 | - it first subscribes to all observables in the sequence 20 | - every time an observable emits an element, all previous observables in the sequence are unsubscribed from, and elements from this observables are sent through 21 | - when any of the currently subscribed-to observables errors, the resulting observable errors too 22 | */ 23 | 24 | example("cascade") { 25 | 26 | // produce an infinite sequence of numbers starting at 0 27 | let a = Observable.interval(1, scheduler: MainScheduler.instance) 28 | .map { "a emitted \($0)" } 29 | 30 | // produce an infinite sequence of numbers after a 3 second delay 31 | let b = Observable.interval(1, scheduler: MainScheduler.instance) 32 | .map { "b emitted \($0)" } 33 | .delaySubscription(3, scheduler: MainScheduler.instance) 34 | 35 | // produce an infinite sequence of numbers after a 6 second delay 36 | let c = Observable.interval(1, scheduler: MainScheduler.instance) 37 | .map { "c emitted \($0)" } 38 | .delaySubscription(6, scheduler: MainScheduler.instance) 39 | 40 | // cascade subscribes to all three observables, but switches to the latest one in the 41 | // observables list, unsubscribing from previous ones. The resulting sequence will 42 | // first output values from `a' (as they are being emitted immediately) then switch to 43 | // `b' as soon as it starts emitting values (after 3 seconds) then switch to `c' as soon 44 | // as it starts emitting values (after 6 seconds), effectively cascading through the 45 | // given observables with no possible return to previous ones. 46 | Observable.cascade([a,b,c]) 47 | .subscribe(onNext: { 48 | print("Cascade next: \($0)") 49 | }) 50 | 51 | // watch the resulting sequence in the playground debug area! 52 | playgroundShouldContinueIndefinitely() 53 | } 54 | 55 | //: [Next](@next) 56 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/cascade.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/catchErrorJustComplete.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## catchErrorJustComplete 17 | 18 | Dismiss errors and complete the sequence instead 19 | 20 | - returns: An observable sequence that never errors and completes when an error occurs in the underlying sequence 21 | */ 22 | private enum SampleErrors : Error { 23 | case fatalError 24 | } 25 | 26 | let sampleObservable = Observable.create { observer in 27 | observer.onNext("First") 28 | observer.onNext("Second") 29 | observer.onError(SampleErrors.fatalError) 30 | observer.onCompleted() 31 | return Disposables.create() 32 | } 33 | 34 | example("catchErrorJustComplete") { 35 | 36 | let _ = sampleObservable 37 | .do(onError: { print("Source observable emitted error \($0)") }) 38 | .catchErrorJustComplete() 39 | .subscribe { 40 | print ("\($0)") 41 | } 42 | 43 | } 44 | 45 | //: [Next](@next) 46 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/count.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## count 17 | 18 | Count the number of items emitted by an Observable 19 | - seealso: [count operator on reactivex.io](http://reactivex.io/documentation/operators/count.html) 20 | - parameter predicate: predicate determines what elements to be counted. 21 | 22 | - returns: An Observable sequence containing a value that represents how many elements in the specified observable sequence satisfy a condition if provided, else the count of items. 23 | 24 | */ 25 | 26 | example("count") { 27 | 28 | // count even number in the sequence 29 | let _ = Observable.from([1...10]) 30 | .count { $0 % 2 == 0 } 31 | .subscribe(onNext: { 32 | print ($0) 33 | }) 34 | 35 | } 36 | 37 | //: [Next](@next) 38 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/distinct.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## distinct 17 | 18 | Suppress duplicate items emitted by an Observable 19 | - seealso: [distinct operator on reactivex.io](http://reactivex.io/documentation/operators/distinct.html) 20 | - returns: An observable sequence only containing the distinct contiguous elements, based on equality operator, from the source sequence. 21 | 22 | */ 23 | 24 | example("distinct") { 25 | 26 | // suppress duplicate strings from the sequence 27 | let _ = Observable.of("a","b","a","c","b","a","d") 28 | .distinct() 29 | .subscribe(onNext: { 30 | print ("\($0)") 31 | }) 32 | 33 | } 34 | 35 | //: [Next](@next) 36 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/filterMap.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExt (playground)` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## filterMap() 17 | 18 | A common pattern in Rx is to filter out some values, then map the remaining ones to something else. The `filterMap` operator does this in one step: 19 | */ 20 | 21 | example("filterMap") { 22 | // keep only odd numbers and double them 23 | Observable.of(1,2,3,4,5,6) 24 | .filterMap { number in 25 | return (number % 2 == 0) ? .ignore : .map(number * 2) 26 | } 27 | .subscribe { print($0) } 28 | } 29 | 30 | //: [Next](@next) 31 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/fromAsync.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExt (playground)` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | import Foundation 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## Observable.fromAsync() 17 | 18 | This function takes as argument a function that takes up to 9 arbitrary arguments and a completionHandler 19 | and returns a function with the same signature, minus the completionHandler, and returns an Observable with 20 | the same Element type as the completionHandler 21 | */ 22 | example("Turn a completion handler into an observable sequence") { 23 | func someAsynchronousTask(arg1: String, arg2: Int, completionHandler: @escaping (String) -> Void) { 24 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { 25 | completionHandler("completion handler result") 26 | } 27 | } 28 | 29 | let observableService = Observable.fromAsync(someAsynchronousTask) 30 | 31 | print("Waiting for completion handler to be called...") 32 | 33 | _ = observableService("Foo", 0) 34 | .subscribe(onNext: { (result) in 35 | print("Asynchronous callback called with: \(result)") 36 | }) 37 | 38 | playgroundShouldContinueIndefinitely() 39 | } 40 | //: [Next](@next) 41 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/ignore.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## ignore 17 | 18 | The `ignore` operator filters out any of the elements passed in parameters. An alternate implementation of `ignore` is provided, which takes a `SequenceType` with any number of elements to ignore. 19 | */ 20 | 21 | example("ignore a single value") { 22 | 23 | let values = ["Hello", "Swift", "world"] 24 | Observable.from(values) 25 | .ignore("Swift") 26 | .toArray() 27 | .subscribe(onNext: { result in 28 | // look values on the right panel ===> 29 | values 30 | result 31 | print("ignore() transformed \(values) to \(result)") 32 | }) 33 | 34 | } 35 | 36 | example("ignore multiple values") { 37 | 38 | let values = "Hello Swift world we really like Swift and RxSwift".components(separatedBy: " ") 39 | Observable.from(values) 40 | .ignore("Swift", "and") 41 | .toArray() 42 | .subscribe(onNext: { result in 43 | // look values on the right panel ===> 44 | values 45 | result 46 | print("ignore() transformed \(values) to \(result)") 47 | }) 48 | 49 | } 50 | 51 | example("ignore a collection of values") { 52 | 53 | let values = "Hello Swift world we really like Swift and RxSwift".components(separatedBy: " ") 54 | let ignoreSet = Set(["and", "Swift"]) 55 | 56 | Observable.from(values) 57 | .ignore(ignoreSet) 58 | .toArray() 59 | .subscribe(onNext: { result in 60 | // look values on the right panel ===> 61 | values 62 | result 63 | print("ignore() transformed \(values) to \(result)") 64 | }) 65 | 66 | } 67 | 68 | /*: 69 | ## ignoreWhen 70 | 71 | The `ignoreWhen` operator works like `filter` but ignores the elements for which the predicate returns `true` instead of keeping them. 72 | */ 73 | 74 | example("ignore some elements") { 75 | 76 | let values = [1, 5, 40, 12, 60, 3, 9, 18] 77 | 78 | Observable.from(values) 79 | .ignoreWhen { value in 80 | return value > 10 81 | } 82 | .toArray() 83 | .subscribe(onNext: { result in 84 | // look values on the right panel ===> 85 | values 86 | result 87 | print("ignoreWhen() transformed \(values) to \(result)") 88 | }) 89 | } 90 | 91 | /*: 92 | ## ignoreErrors 93 | 94 | The `ignoreErrors` operator is a synonym for the `retry` operator: it unconditionally ignores any error emitted by the sequence, 95 | creating an sequence that never fails 96 | */ 97 | enum ExampleError : Error { 98 | case SeriousError 99 | case MinorError 100 | } 101 | 102 | example("ignore all errors") { 103 | 104 | let subject = PublishSubject>() 105 | 106 | let _ = subject 107 | .asObservable() 108 | .flatMap { $0 } 109 | .ignoreErrors() 110 | .subscribe { print($0) } 111 | 112 | subject.onNext(Observable.just(1)) 113 | subject.onNext(Observable.just(2)) 114 | subject.onNext(Observable.just(3)) 115 | subject.onNext(Observable.error(ExampleError.SeriousError)) 116 | subject.onNext(Observable.just(4)) 117 | subject.onNext(Observable.just(5)) 118 | 119 | } 120 | 121 | example("ignore only minor errors") { 122 | 123 | let subject = PublishSubject>() 124 | 125 | let _ = subject 126 | .asObservable() 127 | .flatMap { $0 } 128 | .ignoreErrors { 129 | if case ExampleError.SeriousError = $0 { 130 | return false 131 | } 132 | return true 133 | } 134 | .subscribe { print($0) } 135 | 136 | subject.onNext(Observable.just(1)) 137 | subject.onNext(Observable.just(2)) 138 | subject.onNext(Observable.just(3)) 139 | subject.onNext(Observable.error(ExampleError.SeriousError)) 140 | subject.onNext(Observable.just(4)) 141 | subject.onNext(Observable.just(5)) 142 | 143 | } 144 | 145 | //: [Next](@next) 146 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/mapAt.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## mapAt(KeyPath) 17 | 18 | The `mapAt` operator transforms a sequence of elements where each element is mapped to its value at the provided key path 19 | */ 20 | example("map input to the value at provided key path") { 21 | struct Person { 22 | let name: String 23 | } 24 | 25 | let people: [Person] = [ 26 | Person(name: "Bart"), 27 | Person(name: "Lisa"), 28 | Person(name: "Maggie") 29 | ] 30 | 31 | Observable.from(people) 32 | .mapAt(\.name) 33 | .toArray() 34 | .subscribe(onNext: {result in 35 | // look types on the right panel ===> 36 | people 37 | result 38 | print("mapAt() transformed \(people) to \(result)") 39 | }) 40 | } 41 | 42 | 43 | //: [Next](@next) 44 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/mapMany.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | import RxSwift 12 | import RxSwiftExt 13 | 14 | example("mapMany") { 15 | let numbers = Observable.of(1...10) 16 | let strings = Observable.of(["RxSwift", "is" ,"awesome", "along", "with", "RxSwiftCommunity"]) 17 | 18 | // Map many using a model initializer 19 | numbers.mapMany(SomeModel.init) 20 | .subscribe(onNext: { result in 21 | print(result) 22 | }) 23 | 24 | // Map many with a transformation closure 25 | numbers.mapMany { $0 * $0 } 26 | .subscribe(onNext: { result in 27 | print(result) 28 | }) 29 | 30 | strings.mapMany { $0.lowercased() } 31 | .subscribe(onNext: { result in 32 | print(result) 33 | }) 34 | 35 | struct SomeModel: CustomStringConvertible { 36 | let number: Int 37 | var description: String { return "#\(number)" } 38 | 39 | init(_ number: Int) { 40 | self.number = number 41 | } 42 | } 43 | } 44 | 45 | //: [Next](@next) 46 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/mapTo.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## mapTo( Any) 17 | 18 | The `mapTo` operator takes a sequence of elements and returns a sequence of the same constant provided as a parameter. In effect it ignores its input and replaces it with a constant 19 | */ 20 | example("replace any input with a value") { 21 | 22 | let numbers = Array([1, 2, 3]) 23 | Observable.from(numbers) 24 | .mapTo("candy") 25 | .toArray() 26 | .subscribe(onNext: {result in 27 | // look types on the right panel ===> 28 | numbers 29 | result 30 | print("mapTo() transformed \(numbers) to \(result)") 31 | }) 32 | } 33 | 34 | 35 | //: [Next](@next) 36 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/mergeWith.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## merge(with:) 17 | Merges elements from observable sequences into a single observable sequence. 18 | 19 | */ 20 | example("merge(with:)") { 21 | let oddNumbers = [1, 3, 5, 7, 9] 22 | let evenNumbers = [2, 4, 6, 8, 10] 23 | let otherNumbers = [1, 5 ,6, 2] 24 | 25 | let disposeBag = DisposeBag() 26 | let oddStream = Observable.from(oddNumbers) 27 | let evenStream = Observable.from(evenNumbers) 28 | let otherStream = Observable.from(otherNumbers) 29 | 30 | oddStream 31 | .merge(with: [evenStream, otherStream]) 32 | .subscribe(onNext: { result in 33 | print(result) 34 | }) 35 | .disposed(by: disposeBag) 36 | } 37 | 38 | //: [Next](@next) 39 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/not.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## not 17 | 18 | The `not` operator applies a the boolean not (!) to a `Bool` 19 | */ 20 | example("boolean not - example 1") { 21 | 22 | _ = Observable.just(false) 23 | .not() 24 | .subscribe(onNext: { result in 25 | assert(result) 26 | print("Success! result = \(result)") 27 | }) 28 | } 29 | 30 | //: [Next](@next) 31 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/nwise.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | import RxSwift 12 | import RxSwiftExt 13 | 14 | /*: 15 | ## nwise 16 | 17 | The `nwise` operator groups elements emitted by an Observable into arrays, where each array consists of N consecutive items; similar to a sliding window. 18 | */ 19 | 20 | example("Grouping numbers into triples") { 21 | let input = [1, 2, 3, 4, 5, 6] 22 | 23 | print("Input:", input) 24 | print("Output:") 25 | 26 | Observable.from(input) 27 | .nwise(3) 28 | .subscribe(onNext: { result in 29 | print(result) 30 | }) 31 | } 32 | 33 | /*: 34 | ## pairwise 35 | 36 | The `pairwise` operator is a special case of `nwise` with the size of 2, which emits the previous and current items in tuples instead of arrays. 37 | */ 38 | 39 | example("Grouping numbers into pairs") { 40 | let input = [1, 2, 3, 4, 5, 6] 41 | 42 | print("Input:", input) 43 | print("Output:") 44 | 45 | Observable.from(input) 46 | .pairwise() 47 | .subscribe(onNext: { result in 48 | print(result) 49 | }) 50 | } 51 | 52 | //: [Next](@next) 53 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/ofType.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## ofType() 17 | 18 | The `ofType` operator filters the elements of an observable sequence, if that is an instance of the supplied type. 19 | 20 | */ 21 | 22 | example("ofType") { 23 | Observable.of(NSNumber(value: 1), 24 | NSDecimalNumber(string: "2"), 25 | NSNumber(value: 3), 26 | NSNumber(value: 4), 27 | NSDecimalNumber(string: "5"), 28 | NSNumber(value: 6)) 29 | .ofType(NSDecimalNumber.self) 30 | .subscribe { print($0) } 31 | } 32 | 33 | //: [Next](@next) 34 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/once.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## Observable.once 17 | 18 | The `Observable.once` function creates a sequence that delivers an element *once* to the first subscriber then completes. The same sequence will complete immediately without delivering any element to all further subscribers. 19 | 20 | It lets you guarantee a single-time delivery of a value. Most of the time you will want to use the `Observable.just` operator (creates a sequence which delivers a single element to any observer then complete). In some cases, `Observable.once` can be useful. 21 | */ 22 | 23 | let justOnce = Observable.once("Hello, world") 24 | 25 | // let's subscribe a first time 26 | justOnce.subscribe { event in 27 | switch event { 28 | case .next(let value): 29 | print("First subscriber received value \"\(value)\"") 30 | case .completed: 31 | print("First subscriber completed") 32 | default: 33 | break 34 | } 35 | } 36 | 37 | // let's subscribe a second time to the SAME sequence 38 | justOnce.subscribe { event in 39 | switch event { 40 | case .next(let value): 41 | // this will never be reached 42 | print("Second subscriber received value \"\(value)\"") 43 | case .completed: 44 | print("Second subscriber completed") 45 | default: 46 | break 47 | } 48 | } 49 | 50 | //: [Next](@next) 51 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/partition.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | example("partition") { 16 | let numbers = Observable 17 | .of(1, 2, 3, 5, 8, 13, 18, 21, 23) 18 | 19 | let (evens, odds) = numbers.partition { $0 % 2 == 0 } 20 | 21 | _ = evens.debug("even").subscribe() 22 | _ = odds.debug("odds").subscribe() 23 | } 24 | //: [Next](@next) 25 | 26 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/pausable.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## pausable 17 | 18 | The `pausable` operator pauses the elements of the source observable sequence based on the latest element from the second observable sequence. 19 | - elements from the underlying observable sequence are emitted if and only if the second sequence has most recently emitted `true`. 20 | */ 21 | 22 | example("pausable") { 23 | 24 | let observable = Observable.interval(1, scheduler: MainScheduler.instance) 25 | 26 | let trueAtThreeSeconds = Observable.timer(3, scheduler: MainScheduler.instance).map { _ in true } 27 | let falseAtFiveSeconds = Observable.timer(5, scheduler: MainScheduler.instance).map { _ in false } 28 | let pauser = Observable.of(trueAtThreeSeconds, falseAtFiveSeconds).merge() 29 | 30 | let pausedObservable = observable.pausable(pauser) 31 | 32 | pausedObservable 33 | .subscribe { print($0) } 34 | 35 | playgroundShouldContinueIndefinitely() 36 | } 37 | 38 | //: [Next](@next) 39 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/pausableBuffered.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## pausableBuffered 17 | 18 | Pauses the elements of the source observable sequence based on the latest element from the second observable sequence. 19 | 20 | While paused, elements from the source are buffered, limited to a maximum number of element. 21 | 22 | When resumed, all buffered elements are flushed as single events in a contiguous stream. 23 | */ 24 | 25 | example("pausableBuffered") { 26 | 27 | let observable = Observable.interval(1, scheduler: MainScheduler.instance) 28 | 29 | let trueAtThreeSeconds = Observable.timer(3, scheduler: MainScheduler.instance).map { _ in true } 30 | let falseAtFiveSeconds = Observable.timer(5, scheduler: MainScheduler.instance).map { _ in false } 31 | let pauser = Observable.of(trueAtThreeSeconds, falseAtFiveSeconds).merge() 32 | 33 | // unlimited buffering of values received while paused 34 | let pausedObservable = observable.pausableBuffered(pauser, limit: nil) 35 | 36 | pausedObservable 37 | .subscribe { print($0) } 38 | 39 | playgroundShouldContinueIndefinitely() 40 | 41 | } 42 | //: [Next](@next) 43 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/repeatWithBehavior.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import Foundation 13 | import RxSwift 14 | import RxSwiftExt 15 | 16 | private enum SampleErrors : Error { 17 | case fatalError 18 | } 19 | 20 | let completingObservable = Observable.create { observer in 21 | observer.onNext("First") 22 | observer.onNext("Second") 23 | observer.onCompleted() 24 | return Disposables.create() 25 | } 26 | 27 | let erroringObservable = Observable.create { observer in 28 | observer.onNext("First") 29 | observer.onNext("Second") 30 | observer.onError(SampleErrors.fatalError) 31 | return Disposables.create() 32 | } 33 | 34 | let delayScheduler = SerialDispatchQueueScheduler(qos: .utility) 35 | 36 | example("Immediate repeat") { 37 | // repeat immediately after completion 38 | _ = completingObservable.repeatWithBehavior(.immediate(maxCount: 2)) 39 | .subscribe(onNext: { event in 40 | print("Receive event: \(event)") 41 | }) 42 | } 43 | 44 | 45 | 46 | example("Immediate repeat with custom predicate") { 47 | // here we provide a custom predicate that will determines whether we should resubscribe when the sequence is complete 48 | _ = completingObservable.repeatWithBehavior(.immediate(maxCount: 2), scheduler: delayScheduler) { 49 | return true 50 | } 51 | .subscribe(onNext: { event in 52 | print("Receive event: \(event)") 53 | }) 54 | } 55 | 56 | example("Delayed repeat") { 57 | // once complete, the observable will be resubscribed to after 1.0 second delay 58 | _ = completingObservable.repeatWithBehavior(.delayed(maxCount: 2, time: 1.0), scheduler: delayScheduler) 59 | .subscribe(onNext: { event in 60 | print("Receive event: \(event)") 61 | }) 62 | } 63 | 64 | // sleep in order to wait until previous example finishes 65 | Thread.sleep(forTimeInterval: 2.5) 66 | 67 | example("Exponential delay") { 68 | // when the sequence completes initial delay will be 1 second, 69 | // every next delay will be doubled 70 | // delay formula is: initial * pow(1 + multiplier, Double(currentAttempt - 1)), so multiplier 1.0 means, delay will doubled 71 | _ = completingObservable.repeatWithBehavior(.exponentialDelayed(maxCount: 3, initial: 1.0, multiplier: 1.2), scheduler: delayScheduler) 72 | .subscribe(onNext: { event in 73 | print("Receive event: \(event)") 74 | }) 75 | } 76 | 77 | // sleep in order to wait until previous example finishes 78 | Thread.sleep(forTimeInterval: 4.0) 79 | 80 | example("Delay with calculator") { 81 | // custom delay calculator 82 | // will be invoked to calculate delay for particular repeat 83 | // will be invoked in the beginning of repeat 84 | let customCalculator: (UInt) -> Double = { attempt in 85 | switch attempt { 86 | case 1: return 0.5 87 | case 2: return 1.5 88 | default: return 2.0 89 | } 90 | } 91 | _ = completingObservable.repeatWithBehavior(.customTimerDelayed(maxCount: 3, delayCalculator: customCalculator), scheduler: delayScheduler) 92 | .subscribe(onNext: { event in 93 | print("Receive event: \(event)") 94 | }) 95 | } 96 | 97 | // sleep in order to wait until previous example finishes 98 | Thread.sleep(forTimeInterval: 4.0) 99 | 100 | example("Observable with error") { 101 | _ = erroringObservable.repeatWithBehavior(.immediate(maxCount: 2)) 102 | .subscribe(onNext: { event in 103 | print("Receive event: \(event)") 104 | }, onError: { error in 105 | print("Repetition interrupted with error: \(error)") 106 | }) 107 | } 108 | 109 | playgroundShouldContinueIndefinitely() 110 | 111 | //: [Next](@next) 112 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/retryWithBehavior.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import Foundation 13 | import RxSwift 14 | import RxSwiftExt 15 | 16 | private enum SampleErrors : Error { 17 | case fatalError 18 | } 19 | 20 | let sampleObservable = Observable.create { observer in 21 | observer.onNext("First") 22 | observer.onNext("Second") 23 | observer.onError(SampleErrors.fatalError) 24 | observer.onCompleted() 25 | return Disposables.create() 26 | } 27 | 28 | let delayScheduler = SerialDispatchQueueScheduler(qos: .utility) 29 | 30 | example("Immediate retry") { 31 | // after receiving error will immediate retry 32 | _ = sampleObservable.retry(.immediate(maxCount: 3)) 33 | .subscribe(onNext: { event in 34 | print("Receive event: \(event)") 35 | }, onError: { error in 36 | print("Receive error \(error)") 37 | }) 38 | } 39 | 40 | example("Immediate retry with custom predicate") { 41 | // in this case we provide custom predicate, that will evaluate error and decide, should we retry or not 42 | _ = sampleObservable.retry(.immediate(maxCount: 3), scheduler: delayScheduler) { error in 43 | // error checks here 44 | // in this example we simply say, that retry not allowed 45 | return false 46 | } 47 | .subscribe(onNext: { event in 48 | print("Receive event: \(event)") 49 | }, onError: { error in 50 | print("Receive error \(error)") 51 | }) 52 | } 53 | 54 | example("Delayed retry") { 55 | // after error, observable will be retried after 1.0 second delay 56 | _ = sampleObservable.retry(.delayed(maxCount: 3, time: 1.0), scheduler: delayScheduler) 57 | .subscribe(onNext: { event in 58 | print("Receive event: \(event)") 59 | }, onError: { error in 60 | print("Receive error: \(error)") 61 | }) 62 | } 63 | 64 | // sleep in order to wait until previous example finishes 65 | Thread.sleep(forTimeInterval: 2.5) 66 | 67 | example("Exponential delay") { 68 | // in case of an error initial delay will be 1 second, 69 | // every next delay will be doubled 70 | // delay formula is: initial * pow(1 + multiplier, Double(currentRepetition - 1)), so multiplier 1.0 means, delay will doubled 71 | _ = sampleObservable.retry(.exponentialDelayed(maxCount: 3, initial: 1.0, multiplier: 1.0), scheduler: delayScheduler) 72 | .subscribe(onNext: { event in 73 | print("Receive event: \(event)") 74 | }, onError: { error in 75 | print("Receive error: \(error)") 76 | }) 77 | } 78 | 79 | // sleep in order to wait until previous example finishes 80 | Thread.sleep(forTimeInterval: 4.0) 81 | 82 | example("Delay with calculator") { 83 | // custom delay calculator 84 | // will be invoked to calculate delay for particular repetition 85 | // will be invoked in the beginning of repetition, not when error occurred 86 | let customCalculator: (UInt) -> Double = { attempt in 87 | switch attempt { 88 | case 1: return 0.5 89 | case 2: return 1.5 90 | default: return 2.0 91 | } 92 | } 93 | _ = sampleObservable.retry(.customTimerDelayed(maxCount: 3, delayCalculator: customCalculator), scheduler: delayScheduler) 94 | .subscribe(onNext: { event in 95 | print("Receive event: \(event)") 96 | }, onError: { error in 97 | print("Receive error: \(error)") 98 | }) 99 | } 100 | 101 | playgroundShouldContinueIndefinitely() 102 | 103 | //: [Next](@next) 104 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/toSortedArray.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | import RxSwift 12 | import RxSwiftExt 13 | 14 | /*: 15 | ## toSortedArray 16 | 17 | The `toSortedArray` operator transforms an observable of comparables into an observable of ordered arrays. 18 | */ 19 | 20 | example("Ensure that only a sorted array is emitted") { 21 | let sequenceStream = Observable.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) 22 | let array = Observable.of(10, 9, 12, 8, 4, 1, 1, 8, 14) 23 | 24 | // Ascending order 25 | array.toSortedArray() 26 | .subscribe(onNext: { result in 27 | print(result) 28 | }) 29 | 30 | array.toSortedArray(ascending: true) 31 | .subscribe(onNext: { result in 32 | print(result) 33 | }) 34 | 35 | // Descending order 36 | sequenceStream.toSortedArray(by: >) 37 | .subscribe(onNext: { result in 38 | print(result) 39 | }) 40 | 41 | array.toSortedArray(ascending: false) 42 | .subscribe(onNext: { result in 43 | print(result) 44 | }) 45 | } 46 | 47 | //: [Next](@next) 48 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/unwrap.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## unwrap() 17 | 18 | The `unwrap` operator takes a sequence of optional elements 19 | and returns a sequence of non-optional elements, filtering out any `nil` values. 20 | */ 21 | example("unwrap optional values") { 22 | 23 | let numbers = Array([1, 2, 3]) 24 | Observable.from(numbers) 25 | .unwrap() 26 | .toArray() 27 | .subscribe(onNext: { result in 28 | // look types on the right panel ===> 29 | numbers 30 | result 31 | print("unwrap() transformed \(numbers) to \(result)") 32 | }) 33 | } 34 | 35 | Observable.of(1,2,nil,Int?(4)) 36 | .unwrap() 37 | .subscribe { print($0) } 38 | 39 | example("unwrap and filter out nil values") { 40 | 41 | let numbers = [1, 2, nil, Int?(4)] 42 | Observable.from(numbers) 43 | .unwrap() 44 | .toArray() 45 | .subscribe(onNext: { result in 46 | // look types on the right panel ===> 47 | numbers 48 | result 49 | print("unwrap() transformed \(numbers) to \(result)") 50 | }) 51 | } 52 | 53 | example("unwrap and filter out nil values for a Driver") { 54 | 55 | let numbers = [1, 2, nil, Int?(4)] 56 | let numbersSubject = BehaviorSubject(value: nil) 57 | 58 | numbersSubject 59 | .asDriver(onErrorJustReturn: nil) 60 | .unwrap() 61 | .asObservable() 62 | .toArray() 63 | .subscribe(onNext: { result in 64 | // look types on the right panel ===> 65 | numbers 66 | result 67 | print("unwrap() transformed \(numbers) to \(result)") 68 | }) 69 | 70 | Observable.from(numbers) 71 | .bind(to: numbersSubject) 72 | } 73 | 74 | //: [Next](@next) 75 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/withUnretained.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | class TestClass: CustomStringConvertible { 16 | var description: String { return "Test Class" } 17 | } 18 | 19 | example("withUnretained") { 20 | var testClass: TestClass! = TestClass() 21 | 22 | _ = Observable 23 | .of(1, 2, 3, 5, 8, 13, 18, 21, 23) 24 | .withUnretained(testClass) 25 | .debug("Combined Object with Emitted Events") 26 | .do(onNext: { _, value in 27 | if value == 13 { 28 | // When testClass becomes nil, the next emission of the original 29 | // sequence will try to retain it and fail. As soon as it fails, 30 | // the sequence will complete. 31 | testClass = nil 32 | } 33 | }) 34 | .subscribe() 35 | } 36 | //: [Next](@next) 37 | 38 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Pages/zipWith.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: 3 | 4 | 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 5 | 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 6 | 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 7 | 1. Choose `View > Show Debug Area` 8 | */ 9 | 10 | //: [Previous](@previous) 11 | 12 | import RxSwift 13 | import RxSwiftExt 14 | 15 | /*: 16 | ## zip(with:) 17 | Merges the specified observable sequences into one observable sequence by using the selector function whenever all 18 | of the observable sequences have produced an element at a corresponding index. 19 | 20 | */ 21 | example("zip(with:)") { 22 | let numbers = [1,2,3] 23 | let strings = ["a", "b", "c"] 24 | 25 | let first = Observable.from(numbers) 26 | let second = Observable.from(strings) 27 | 28 | first.zip(with: second) { i, s in 29 | s + String(i) 30 | } 31 | .toArray() 32 | .subscribe(onNext: { result in 33 | print("zip(with:) merged \(numbers) with \(strings) to \(result)") 34 | }) 35 | } 36 | 37 | //: [Next](@next) 38 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/Sources/SupportCode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | Encloses each code example in its own scope. Prints a `description` header and then executes the `action` closure. 5 | - parameter description: example description 6 | - parameter action: `Void` closure 7 | */ 8 | public func example(_ description: String, action: () -> ()) { 9 | printExampleHeader(description) 10 | action() 11 | } 12 | 13 | public func printExampleHeader(_ description: String) { 14 | print("\n--- \(description) example ---") 15 | } 16 | 17 | /** 18 | Executes `closure` on main thread after `delay` seconds. 19 | - parameter delay: time in seconds to wait before executing `closure` 20 | - parameter closure: `Void` closure 21 | */ 22 | public func delay(_ delay: Double, closure: @escaping () -> ()) { 23 | 24 | let delayTime = DispatchTime.now() + DispatchTimeInterval.seconds(Int(delay)) 25 | DispatchQueue.main.asyncAfter(deadline: delayTime) { 26 | closure() 27 | } 28 | } 29 | 30 | #if NOT_IN_PLAYGROUND 31 | 32 | public func playgroundShouldContinueIndefinitely() { } 33 | 34 | #else 35 | 36 | import PlaygroundSupport 37 | 38 | public func playgroundShouldContinueIndefinitely() { 39 | PlaygroundPage.current.needsIndefiniteExecution = true 40 | } 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/SupportCode.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RxSwiftCommunity/RxSwiftExt/eb4adf9f00a21b3efc3869a5218a6d7517e95222/Playground/RxSwiftExtPlayground.playground/SupportCode.o -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/SupportCode.remap: -------------------------------------------------------------------------------- 1 | [ 2 | ] -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Playground/RxSwiftExtPlayground.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RxSwiftExt.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "RxSwiftExt" 3 | s.version = "6.2.1" 4 | s.summary = "RxSwift operators not found in the core distribtion" 5 | s.description = <<-DESC 6 | A collection of operators for RxSwift adding commonly requested operations not found in the core distribution 7 | of RxSwift. 8 | DESC 9 | s.homepage = "https://github.com/RxSwiftCommunity/RxSwiftExt" 10 | s.license = { :type => "MIT", :file => "LICENSE" } 11 | s.authors = { "RxSwiftCommunity" => "https://github.com/RxSwiftCommunity" } 12 | 13 | s.ios.deployment_target = '12.0' 14 | s.osx.deployment_target = '10.13' 15 | s.watchos.deployment_target = '4.0' 16 | s.tvos.deployment_target = '9.0' 17 | 18 | s.source = { :git => "https://github.com/RxSwiftCommunity/RxSwiftExt.git", :tag => s.version } 19 | 20 | s.subspec "Core" do |cs| 21 | cs.source_files = "Source/RxSwift", "Source/Tools" 22 | cs.frameworks = "Foundation" 23 | cs.dependency "RxSwift", '~> 6.0' 24 | end 25 | 26 | s.subspec "RxCocoa" do |co| 27 | co.source_files = "Source/RxCocoa" 28 | co.frameworks = "Foundation" 29 | co.dependency "RxCocoa", '~> 6.0' 30 | co.dependency "RxSwiftExt/Core" 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /RxSwiftExt.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RxSwiftExt.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RxSwiftExt.xcodeproj/xcshareddata/xcschemes/RxSwiftExt-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 51 | 52 | 53 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 77 | 83 | 84 | 85 | 86 | 92 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /RxSwiftExt.xcodeproj/xcshareddata/xcschemes/RxSwiftExt-macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /RxSwiftExt.xcodeproj/xcshareddata/xcschemes/RxSwiftExt-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /RxSwiftExt.xcodeproj/xcshareddata/xcschemes/RxSwiftExtPlayground.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /RxSwiftExt.xcodeproj/xcshareddata/xcschemes/RxSwiftExtTests-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 62 | 63 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /RxSwiftExt.xcodeproj/xcshareddata/xcschemes/RxSwiftExtTests-macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 62 | 63 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /RxSwiftExt.xcodeproj/xcshareddata/xcschemes/RxSwiftExtTests-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /RxSwiftExt.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /RxSwiftExt.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Source/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 2.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Source/RxCocoa/UIScrollView+reachedBottom.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+reachedBottom.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Anton Nazarov on 09/05/2019. 6 | // Copyright © 2019 RxSwift Community. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | import RxSwift 12 | import RxCocoa 13 | 14 | public extension Reactive where Base: UIScrollView { 15 | /** 16 | Shows if the bottom of the UIScrollView is reached. 17 | - parameter offset: A threshhold indicating the bottom of the UIScrollView. 18 | - returns: ControlEvent that emits when the bottom of the base UIScrollView is reached. 19 | */ 20 | func reachedBottom(offset: CGFloat = 0.0) -> ControlEvent { 21 | let source = contentOffset.map { contentOffset in 22 | let visibleHeight = self.base.frame.height - self.base.contentInset.top - self.base.contentInset.bottom 23 | let y = contentOffset.y + self.base.contentInset.top 24 | let threshold = max(offset, self.base.contentSize.height - visibleHeight) 25 | return y >= threshold 26 | } 27 | .distinctUntilChanged() 28 | .filter { $0 } 29 | .map { _ in () } 30 | return ControlEvent(events: source) 31 | } 32 | } 33 | #endif 34 | -------------------------------------------------------------------------------- /Source/RxCocoa/UIViewPropertyAnimator+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewPropertyAnimator+Rx.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Wittemberg, Thibault on 29/03/18. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import Foundation 11 | import UIKit 12 | import RxSwift 13 | import RxCocoa 14 | 15 | @available(iOS 10.0, *) 16 | public extension Reactive where Base: UIViewPropertyAnimator { 17 | /** 18 | Bindable extension for `fractionComplete` property. 19 | */ 20 | var fractionComplete: Binder { 21 | return Binder(base) { propertyAnimator, fractionComplete in 22 | propertyAnimator.fractionComplete = fractionComplete 23 | } 24 | } 25 | 26 | /// Provides a Completable that triggers the UIViewPropertyAnimator upon subscription 27 | /// and completes once the animation ends. 28 | /// 29 | /// - Parameter afterDelay: the delay to apply to the animation start 30 | /// 31 | /// - Returns: Completable 32 | func animate(afterDelay delay: TimeInterval = 0) -> Completable { 33 | return Completable.create { [base] completable in 34 | base.addCompletion { position in 35 | guard position == .end else { return } 36 | completable(.completed) 37 | } 38 | 39 | if delay != 0 { 40 | base.startAnimation(afterDelay: delay) 41 | } else { 42 | base.startAnimation() 43 | } 44 | 45 | return Disposables.create { 46 | base.stopAnimation(true) 47 | } 48 | } 49 | } 50 | } 51 | #endif 52 | -------------------------------------------------------------------------------- /Source/RxCocoa/distinct+RxCocoa.swift: -------------------------------------------------------------------------------- 1 | // 2 | // distinct+RxCocoa.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Rafael Ferreira on 3/8/17. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import RxCocoa 10 | 11 | extension SharedSequence { 12 | /** 13 | Suppress duplicate items emitted by an SharedSequence 14 | - seealso: [distinct operator on reactivex.io](http://reactivex.io/documentation/operators/distinct.html) 15 | - parameter predicate: predicate determines whether element distinct 16 | 17 | - returns: An shared sequence only containing the distinct contiguous elements, based on predicate, from the source sequence. 18 | */ 19 | public func distinct(_ predicate: @escaping (Element) -> Bool) -> SharedSequence { 20 | var cache = [Element]() 21 | 22 | return flatMap { element -> SharedSequence in 23 | if cache.contains(where: predicate) { 24 | return SharedSequence.empty() 25 | } else { 26 | cache.append(element) 27 | 28 | return SharedSequence.just(element) 29 | } 30 | } 31 | } 32 | } 33 | 34 | extension SharedSequence where Element: Equatable { 35 | /** 36 | Suppress duplicate items emitted by an SharedSequence 37 | - seealso: [distinct operator on reactivex.io](http://reactivex.io/documentation/operators/distinct.html) 38 | - returns: An shared sequence only containing the distinct contiguous elements, based on equality operator, from the source sequence. 39 | */ 40 | public func distinct() -> SharedSequence { 41 | var cache = [Element]() 42 | 43 | return flatMap { element -> SharedSequence in 44 | if cache.contains(element) { 45 | return SharedSequence.empty() 46 | } else { 47 | cache.append(element) 48 | 49 | return SharedSequence.just(element) 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Source/RxCocoa/mapTo+RxCocoa.swift: -------------------------------------------------------------------------------- 1 | // 2 | // mapTo+RxCocoa.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Rafael Ferreira on 3/7/17. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import RxCocoa 10 | 11 | extension SharedSequenceConvertibleType { 12 | /** 13 | Returns an Unit containing as many elements as its input but all of them are the constant provided as a parameter 14 | 15 | - parameter value: A constant that each element of the input sequence is being replaced with 16 | - returns: An unit containing the values `value` provided as a parameter 17 | */ 18 | public func mapTo(_ value: Result) -> SharedSequence { 19 | return map { _ in value } 20 | } 21 | 22 | @available(*, deprecated, renamed: "mapTo(_:)") 23 | public func map(to value: Result) -> SharedSequence { 24 | return map { _ in value } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Source/RxCocoa/not+RxCocoa.swift: -------------------------------------------------------------------------------- 1 | // 2 | // not+RxCocoa.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Rafael Ferreira on 3/7/17. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import RxCocoa 10 | import RxSwift 11 | 12 | extension SharedSequenceConvertibleType where Element == Bool { 13 | /// Boolean not operator. 14 | public func not() -> SharedSequence { 15 | return map(!) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Source/RxCocoa/partition+RxCocoa.swift: -------------------------------------------------------------------------------- 1 | // 2 | // partition+RxCocoa.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Shai Mishali on 24/11/2018. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | import RxCocoa 11 | 12 | public extension SharedSequence { 13 | /** 14 | Partition a stream into two separate streams of elements that match, and don't match, the provided predicate. 15 | 16 | - parameter predicate: A predicate used to filter matching and non-matching elements. 17 | 18 | - returns: A tuple of two streams of elements that match, and don't match, the provided predicate. 19 | */ 20 | func partition(_ predicate: @escaping (Element) -> Bool) -> (matches: SharedSequence, 21 | nonMatches: SharedSequence) { 22 | let stream = self.map { ($0, predicate($0)) } 23 | 24 | let hits = stream.filter { $0.1 }.map { $0.0 } 25 | let misses = stream.filter { !$0.1 }.map { $0.0 } 26 | 27 | return (hits, misses) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Source/RxCocoa/unwrap+SharedSequence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // unwrap+SharedSequence.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Hugo Saynac on 05/10/2018. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import RxCocoa 10 | 11 | extension SharedSequence { 12 | 13 | /** 14 | Takes a SharedSequence of optional elements and returns a SharedSequence of non-optional elements, filtering out any nil values. 15 | 16 | - returns: A SharedSequence of non-optional elements 17 | */ 18 | 19 | public func unwrap() -> SharedSequence where Element == Result? { 20 | return self.filter { $0 != nil }.map { $0! } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Source/RxSwift/and.swift: -------------------------------------------------------------------------------- 1 | // 2 | // and.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Florent Pillet on 26/11/17. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension ObservableType where Element == Bool { 13 | /** 14 | Emits a single Bool value indicating whether or not a Bool sequence emits only `true` values. 15 | 16 | If a `false` value is emitted, the resulting sequence immediately completes with a `false` result. 17 | If only `true` values are emitted, the resulting sequence completes with a `true` result once the 18 | source sequence completes. 19 | If no value is emitted, the resulting sequence completes with no value once the source sequence completes. 20 | 21 | Use `asSingle()` or `asObservable()` to convert to your requirements. 22 | */ 23 | public func and() -> Maybe { 24 | return Maybe.create { observer in 25 | var gotValue = false 26 | return self.subscribe { event in 27 | switch event { 28 | case .next(let value): 29 | if !value { 30 | // first `false` value emits false & completes 31 | observer(.success(false)) 32 | } else { 33 | gotValue = true 34 | } 35 | case .error(let error): 36 | observer(.error(error)) 37 | case .completed: 38 | observer(gotValue ? .success(true) : .completed) 39 | } 40 | } 41 | } 42 | } 43 | 44 | /** 45 | Emits a single Bool value indicating whether or not a each Bool sequence in the collection emits only `true` values. 46 | 47 | Each sequence of the collection is expected to emit at least one `true` value. 48 | If any sequence does not emit anything, the produced `Maybe` will just complete. 49 | If any sequence emits a `false` value, the produiced `Maybe` will emit a `false` result. 50 | If all sequences emit at least one `true` value, the produced `Maybe` will emit a `true` result. 51 | 52 | Use `asSingle()` or `asObservable()` to convert to your requirements. 53 | */ 54 | public static func and(_ collection: Collection) 55 | -> Maybe where Collection.Element: ObservableType, Collection.Element.Element == Element { 56 | return Maybe.create { observer in 57 | var emitted = [Bool](repeating: false, count: Int(collection.count)) 58 | var completed = 0 59 | let lock = NSRecursiveLock() 60 | lock.lock() 61 | defer { lock.unlock() } 62 | let subscriptions = collection.enumerated().map { item in 63 | item.element.subscribe { event in 64 | lock.lock() 65 | defer { lock.unlock() } 66 | switch event { 67 | case .next(let value): 68 | if !value { 69 | // first `false` value emits false & completes 70 | observer(.success(false)) 71 | } else { 72 | emitted[item.offset] = true 73 | } 74 | case .error(let error): 75 | observer(.error(error)) 76 | case .completed: 77 | completed += 1 78 | guard completed == collection.count else { return } 79 | // if all emitted at least one `true`, emit true otherwise just complete 80 | if emitted.allSatisfy({ $0 }) { 81 | observer(.success(true)) 82 | } else { 83 | observer(.completed) 84 | } 85 | } 86 | 87 | } 88 | } 89 | return CompositeDisposable(disposables: subscriptions) 90 | } 91 | } 92 | 93 | /** 94 | Emits a single Bool value indicating whether or not a each Bool sequence in the collection emits only `true` values. 95 | 96 | Each sequence of the collection is expected to emit at least one `true` value. 97 | If any sequence does not emit anything, the produced `Maybe` will just complete. 98 | If any sequence emits a `false` value, the produiced `Maybe` will emit a `false` result. 99 | If all sequences emit at least one `true` value, the produced `Maybe` will emit a `true` result. 100 | 101 | Use `asSingle()` or `asObservable()` to convert to your requirements. 102 | */ 103 | public static func and(_ sources: Observable ...) -> Maybe where Observable.Element == Element { 104 | return and(sources) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Source/RxSwift/apply.swift: -------------------------------------------------------------------------------- 1 | // 2 | // apply.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Andy Chou on 2/22/17. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension ObservableType { 13 | /// Apply a transformation function to the Observable. 14 | public func apply(_ transform: (Observable) -> Observable) -> Observable { 15 | return transform(self.asObservable()) 16 | } 17 | } 18 | 19 | extension PrimitiveSequenceType { 20 | /// Apply a transformation function to the primitive sequence. 21 | public func apply(_ transform: (PrimitiveSequence) -> PrimitiveSequence) 22 | -> PrimitiveSequence { 23 | return transform(self.primitiveSequence) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Source/RxSwift/bufferWithTrigger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // bufferWithTrigger.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Patrick Maltagliati on 11/12/18. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension ObservableType { 13 | /** 14 | Collects the elements of the source observable, and emits them as an array when the trigger emits. 15 | 16 | - parameter trigger: The observable sequence used to signal the emission of the buffered items. 17 | - returns: The buffered observable from elements of the source sequence. 18 | */ 19 | public func bufferWithTrigger(_ trigger: Trigger) -> Observable<[Element]> { 20 | return Observable.create { observer in 21 | var buffer: [Element] = [] 22 | let lock = NSRecursiveLock() 23 | let triggerDisposable = trigger.subscribe { event in 24 | lock.lock(); defer { lock.unlock() } 25 | switch event { 26 | case .next: 27 | observer.onNext(buffer) 28 | buffer = [] 29 | default: 30 | break 31 | } 32 | } 33 | let disposable = self.subscribe { event in 34 | lock.lock(); defer { lock.unlock() } 35 | switch event { 36 | case .next(let element): 37 | buffer.append(element) 38 | case .completed: 39 | observer.onNext(buffer) 40 | observer.onCompleted() 41 | case .error(let error): 42 | observer.onError(error) 43 | buffer = [] 44 | } 45 | } 46 | return Disposables.create([disposable, triggerDisposable]) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Source/RxSwift/catchErrorJustComplete.swift: -------------------------------------------------------------------------------- 1 | // 2 | // catchErrorJustComplete.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Florent Pillet on 21/05/16. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | extension ObservableType { 12 | /** 13 | Dismiss errors and complete the sequence instead 14 | 15 | - returns: An observable sequence that never errors and completes when an error occurs in the underlying sequence 16 | */ 17 | public func catchErrorJustComplete() -> Observable { 18 | return `catch` { _ in 19 | return Observable.empty() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Source/RxSwift/count.swift: -------------------------------------------------------------------------------- 1 | // 2 | // count.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Fred on 06/11/2018. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension Observable { 13 | /** 14 | Count the number of items emitted by an Observable 15 | - seealso: [count operator on reactivex.io](http://reactivex.io/documentation/operators/count.html) 16 | - returns: An Observable sequence containing a value that represents how many elements in the specified observable sequence satisfy a condition if provided, else the count of items. 17 | */ 18 | public func count() -> Observable { 19 | return reduce(0) { count, _ in count + 1 } 20 | } 21 | /** 22 | Count the number of items emitted by an Observable 23 | - seealso: [count operator on reactivex.io](http://reactivex.io/documentation/operators/count.html) 24 | - parameter predicate: predicate determines what elements to be counted. 25 | 26 | - returns: An Observable sequence containing a value that represents how many elements in the specified observable sequence satisfy a condition if provided, else the count of items. 27 | */ 28 | public func count(_ predicate: @escaping (Element) throws -> Bool) -> Observable { 29 | return filter(predicate).count() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Source/RxSwift/distinct.swift: -------------------------------------------------------------------------------- 1 | // 2 | // distinct.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Segii Shulga on 5/4/16. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension Observable { 13 | /** 14 | Suppress duplicate items emitted by an Observable 15 | - seealso: [distinct operator on reactivex.io](http://reactivex.io/documentation/operators/distinct.html) 16 | - parameter predicate: predicate determines whether element distinct 17 | 18 | - returns: An observable sequence only containing the distinct contiguous elements, based on predicate, from the source sequence. 19 | */ 20 | public func distinct(_ predicate: @escaping (Element) throws -> Bool) -> Observable { 21 | var cache = [Element]() 22 | return flatMap { element -> Observable in 23 | if try cache.contains(where: predicate) { 24 | return Observable.empty() 25 | } else { 26 | cache.append(element) 27 | return Observable.just(element) 28 | } 29 | } 30 | } 31 | } 32 | 33 | extension Observable where Element: Hashable { 34 | /** 35 | Suppress duplicate items emitted by an Observable 36 | - seealso: [distinct operator on reactivex.io](http://reactivex.io/documentation/operators/distinct.html) 37 | - returns: An observable sequence only containing the distinct contiguous elements, based on equality operator, from the source sequence. 38 | */ 39 | public func distinct() -> Observable { 40 | var cache = Set() 41 | return flatMap { element -> Observable in 42 | if cache.contains(element) { 43 | return Observable.empty() 44 | } else { 45 | cache.insert(element) 46 | return Observable.just(element) 47 | } 48 | } 49 | } 50 | } 51 | 52 | extension Observable where Element: Equatable { 53 | /** 54 | Suppress duplicate items emitted by an Observable 55 | - seealso: [distinct operator on reactivex.io](http://reactivex.io/documentation/operators/distinct.html) 56 | - returns: An observable sequence only containing the distinct contiguous elements, based on equality operator, from the source sequence. 57 | */ 58 | public func distinct() -> Observable { 59 | var cache = [Element]() 60 | return flatMap { element -> Observable in 61 | if cache.contains(element) { 62 | return Observable.empty() 63 | } else { 64 | cache.append(element) 65 | return Observable.just(element) 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Source/RxSwift/filterMap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // filterMap.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Jeremie Girault on 31/05/2017. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | public enum FilterMap { 12 | case ignore 13 | case map(Result) 14 | } 15 | 16 | extension ObservableType { 17 | 18 | /** 19 | Filters or Maps values from the source. 20 | - The returned Observable will complete with the source. It will error with the source or with error thrown by transform callback. 21 | - `next` values will be output according to the `transform` callback result: 22 | - returning `.ignore` will filter the value out of the returned Observable 23 | - returning `.map(newValue)` will propagate newValue through the returned Observable. 24 | */ 25 | public func filterMap(_ transform: @escaping (Element) throws -> FilterMap) -> Observable { 26 | return compactMap { element in 27 | switch try transform(element) { 28 | case .ignore: 29 | return nil 30 | case let .map(result): 31 | return result 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Source/RxSwift/ignore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ignore.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Florent Pillet on 10/04/16. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension ObservableType where Element: Equatable { 13 | public func ignore(_ valuesToIgnore: Element...) -> Observable { 14 | return self.asObservable().filter { !valuesToIgnore.contains($0) } 15 | } 16 | 17 | public func ignore(_ valuesToIgnore: Sequence) -> Observable where Sequence.Element == Element { 18 | return self.asObservable().filter { !valuesToIgnore.contains($0) } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Source/RxSwift/ignoreErrors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ignoreErrors.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Florent Pillet on 18/05/16. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | extension ObservableType { 12 | /** 13 | Unconditionally ignore all errors produced by the source observable, effectively producing a sequence 14 | that never fails (any error will simply have no effect on the sequence). 15 | 16 | - returns: An observable sequence that never fails 17 | - seealso: `retry` operator 18 | */ 19 | public func ignoreErrors() -> Observable { 20 | return retry() 21 | } 22 | 23 | /** 24 | Conditionally ignore errors produced by the source observable 25 | 26 | - parameter predicate a predicate called when an error occurs and returns `true` to ignore the error (continuing), `false` to terminate the sequence with the given error. 27 | - returns: An observable sequence that errors only when `predicate` returns `false` 28 | */ 29 | public func ignoreErrors(_ predicate : @escaping (Error) -> Bool) -> Observable { 30 | return retry(when: { 31 | return $0.flatMap { error -> Observable in 32 | return predicate(error) ? Observable.just(true) : Observable.error(error) 33 | } 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Source/RxSwift/ignoreWhen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ignoreWhen.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Florent Pillet on 14/04/16. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension ObservableType { 13 | 14 | /** 15 | Ignores the elements of an observable sequence based on a predicate. 16 | 17 | - seealso: [filter operator on reactivex.io](http://reactivex.io/documentation/operators/filter.html) 18 | - seealso: [ignoreElements operator on reactivex.io](http://reactivex.io/documentation/operators/ignoreelements.html) 19 | 20 | - parameter predicate: A function to test each source element for a condition. 21 | - returns: An observable sequence that contains elements from the input sequence except those that satisfy the condition. 22 | */ 23 | public func ignoreWhen(_ predicate: @escaping (Element) throws -> Bool) -> Observable { 24 | return self.asObservable().filter { try !predicate($0) } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Source/RxSwift/mapAt.swift: -------------------------------------------------------------------------------- 1 | // 2 | // mapAt.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Michael Avila on 2/8/18. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | extension ObservableType { 12 | 13 | /** 14 | Returns an observable sequence containing as many elements as its input but all of them are mapped to the result at the given keyPath 15 | 16 | - parameter keyPath: A key path whose root type matches the element type of the input sequence 17 | - returns: An observable squence containing the values pointed to by the key path 18 | */ 19 | public func mapAt(_ keyPath: KeyPath) -> Observable { 20 | return self.map { $0[keyPath: keyPath] } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Source/RxSwift/mapMany.swift: -------------------------------------------------------------------------------- 1 | // 2 | // mapMany.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Joan Disho on 06/05/18. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | extension ObservableType where Element: Collection { 12 | /** 13 | Projects each element of an observable collection into a new form. 14 | 15 | - parameter transform: A transform function to apply to each element of the source collection. 16 | - returns: An observable collection whose elements are the result of invoking the transform function on each element of source. 17 | */ 18 | public func mapMany(_ transform: @escaping (Element.Element) throws -> Result) -> Observable<[Result]> { 19 | return map { collection -> [Result] in 20 | try collection.map(transform) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Source/RxSwift/mapTo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // mapTo.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Marin Todorov on 4/12/16. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension ObservableType { 13 | 14 | /** 15 | Returns an observable sequence containing as many elements as its input but all of them are the constant provided as a parameter 16 | 17 | - parameter value: A constant that each element of the input sequence is being replaced with 18 | - returns: An observable sequence containing the values `value` provided as a parameter 19 | */ 20 | public func mapTo(_ value: Result) -> Observable { 21 | return map { _ in value } 22 | } 23 | } 24 | 25 | extension PrimitiveSequence where Trait == SingleTrait { 26 | /** 27 | Returns a Single which success event is mapped to constant provided as a parameter 28 | 29 | - parameter value: A constant that element of the input sequence is being replaced with 30 | - returns: A Single containing the value `value` provided as a parameter in case of success 31 | */ 32 | public func mapTo(_ value: Result) -> Single { 33 | return map { _ in value } 34 | } 35 | } 36 | 37 | extension PrimitiveSequence where Trait == MaybeTrait { 38 | /** 39 | Returns a Maybe which success event is mapped to constant provided as a parameter 40 | 41 | - parameter value: A constant that element of the input sequence is being replaced with 42 | - returns: A Maybe containing the value `value` provided as a parameter in case of success 43 | */ 44 | public func mapTo(_ value: Result) -> Maybe { 45 | return map { _ in value } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Source/RxSwift/materialized+elements.swift: -------------------------------------------------------------------------------- 1 | // 2 | // materialized+elements.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Andy Chou on 1/5/17. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension ObservableType where Element: EventConvertible { 13 | 14 | /** 15 | Returns an observable sequence containing only next elements from its input 16 | - seealso: [materialize operator on reactivex.io](http://reactivex.io/documentation/operators/materialize-dematerialize.html) 17 | */ 18 | public func elements() -> Observable { 19 | return compactMap { $0.event.element } 20 | } 21 | 22 | /** 23 | Returns an observable sequence containing only error elements from its input 24 | - seealso: [materialize operator on reactivex.io](http://reactivex.io/documentation/operators/materialize-dematerialize.html) 25 | */ 26 | public func errors() -> Observable { 27 | return compactMap { $0.event.error } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Source/RxSwift/mergeWith.swift: -------------------------------------------------------------------------------- 1 | // 2 | // mergeWith.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Joan Disho on 12/05/18. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension Observable { 13 | /** 14 | Merges elements from the observable sequence with those of a different observable sequence into a single observable sequence. 15 | 16 | - parameter with: Other observable. 17 | - returns: The observable sequence that merges the elements of the observable sequences. 18 | */ 19 | public func merge(with other: Observable) -> Observable { 20 | return Observable.merge(self, other) 21 | } 22 | 23 | /** 24 | Merges elements from the observable sequence with those of a different observable sequences into a single observable sequence. 25 | 26 | - parameter with: Other observables. 27 | - returns: The observable sequence that merges the elements of the observable sequences. 28 | */ 29 | public func merge(with others: [Observable]) -> Observable { 30 | return Observable.merge([self] + others) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Source/RxSwift/not.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ignore.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Thane Gill on 18/04/16. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension ObservableType where Element == Bool { 13 | /// Boolean not operator 14 | public func not() -> Observable { 15 | return self.map(!) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Source/RxSwift/nwise.swift: -------------------------------------------------------------------------------- 1 | // 2 | // nwise.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Zsolt Váradi on 09/12/2017. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | extension ObservableType { 12 | /** 13 | Groups the elements of the source observable into arrays of N consecutive elements. 14 | 15 | The resulting observable: 16 | - does not emit anything until the source observable emits at least N elements; 17 | - emits an array for every element after that; 18 | - forwards any error or completed events. 19 | 20 | For example: 21 | 22 | --(1)--(2)--(3)-------(4)-------(5)-------> 23 | | 24 | | nwise(3) 25 | v 26 | ------------([1,2,3])-([2,3,4])-([3,4,5])-> 27 | 28 | - parameter n: size of the groups, must be greater than 1 29 | */ 30 | public func nwise(_ n: Int) -> Observable<[Element]> { 31 | assert(n > 1, "n must be greater than 1") 32 | return self 33 | .scan([]) { acc, item in Array((acc + [item]).suffix(n)) } 34 | .filter { $0.count == n } 35 | } 36 | 37 | /** 38 | Groups the elements of the source observable into tuples of the previous and current elements. 39 | 40 | The resulting observable: 41 | - does not emit anything until the source observable emits at least 2 elements; 42 | - emits a tuple for every element after that, consisting of the previous and the current item; 43 | - forwards any error or completed events. 44 | 45 | For example: 46 | 47 | --(1)--(2)--(3)-------(4)-------(5)-------> 48 | | 49 | | pairwise() 50 | v 51 | -------(1,2)-(2,3)----(3,4)-----(4,5)-----> 52 | */ 53 | public func pairwise() -> Observable<(Element, Element)> { 54 | return self.nwise(2) 55 | .map { ($0[0], $0[1]) } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Source/RxSwift/ofType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ofType.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Nate Kim on 19/01/2018. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension ObservableType { 13 | 14 | /** 15 | Filters the elements of an observable sequence, if that is an instance of the supplied type. 16 | 17 | - seealso: [filter operator on reactivex.io](http://reactivex.io/documentation/operators/filter.html) 18 | 19 | - parameter type: The Type to filter each source element. 20 | - returns: An observable sequence that contains elements which is an instance of the suppplied type. 21 | */ 22 | public func ofType(_ type: Result.Type) -> Observable { 23 | return self.filterMap { 24 | guard let result = $0 as? Result else { return .ignore } 25 | return .map(result) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Source/RxSwift/once.swift: -------------------------------------------------------------------------------- 1 | // 2 | // once.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Florent Pillet on 12/04/16. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension Observable { 13 | 14 | /** 15 | Returns an observable sequence that contains a single element. This element will be delivered to the first observer 16 | that subscribes. Further subscriptions to the same observable will get an empty sequence. 17 | 18 | In most cases, you want to use `Observable.just()`. This one is really for specific cases where you need to guarantee 19 | unique delivery of a value. 20 | 21 | - seealso: [just operator on reactivex.io](http://reactivex.io/documentation/operators/just.html) 22 | 23 | - parameter element: Single element in the resulting observable sequence. 24 | - returns: An observable sequence containing the single specified element delivered once. 25 | */ 26 | 27 | public static func once(_ element: Element) -> Observable { 28 | let lock = NSRecursiveLock() 29 | var isDelivered = false 30 | return create { observer in 31 | lock.lock() 32 | if !isDelivered { 33 | observer.onNext(element) 34 | } 35 | isDelivered = true 36 | lock.unlock() 37 | observer.onCompleted() 38 | return Disposables.create() 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /Source/RxSwift/partition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // partition.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Shai Mishali on 24/11/2018. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | public extension ObservableType { 12 | /** 13 | Partition a stream into two separate streams of elements that match, and don't match, the provided predicate. 14 | 15 | - parameter predicate: A predicate used to filter matching and non-matching elements. 16 | 17 | - returns: A tuple of two streams of elements that match, and don't match, the provided predicate. 18 | */ 19 | func partition(_ predicate: @escaping (Element) throws -> Bool) -> (matches: Observable, nonMatches: Observable) { 20 | let stream = self.map { ($0, try predicate($0)) }.share() 21 | 22 | let hits = stream.filter { $0.1 }.map { $0.0 } 23 | let misses = stream.filter { !$0.1 }.map { $0.0 } 24 | 25 | return (hits, misses) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Source/RxSwift/pausable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // pausable.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Jesse Farless on 12/09/16. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension ObservableType { 13 | /** 14 | Pauses the elements of the source observable sequence based on the latest element from the second observable sequence. 15 | 16 | Elements are ignored unless the second sequence has most recently emitted `true`. 17 | 18 | - seealso: [pausable operator on reactivex.io](http://reactivex.io/documentation/operators/backpressure.html) 19 | 20 | - parameter pauser: The observable sequence used to pause the source observable sequence. 21 | - returns: The observable sequence which is paused based upon the pauser observable sequence. 22 | */ 23 | 24 | public func pausable (_ pauser: Pauser) -> Observable where Pauser.Element == Bool { 25 | return withLatestFrom(pauser) { element, paused in 26 | (element, paused) 27 | } 28 | .filter { _, paused in paused } 29 | .map { element, _ in element } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Source/RxSwift/pausableBuffered.swift: -------------------------------------------------------------------------------- 1 | // 2 | // pausableBuffered.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Tanguy Helesbeux on 24/05/2017. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension ObservableType { 13 | 14 | /** 15 | Pauses the elements of the source observable sequence based on the latest element from the second observable sequence. 16 | 17 | While paused, elements from the source are buffered, limited to a maximum number of element. 18 | 19 | When resumed, all buffered elements are flushed as single events in a contiguous stream. 20 | 21 | - seealso: [pausable operator on reactivex.io](http://reactivex.io/documentation/operators/backpressure.html) 22 | 23 | - parameter pauser: The observable sequence used to pause the source observable sequence. 24 | - parameter limit: The maximum number of element buffered. Pass `nil` to buffer all elements without limit. Default 1. 25 | - parameter flushOnCompleted: If `true` buffered elements will be flushed when the source completes. Default `true`. 26 | - parameter flushOnError: If `true` buffered elements will be flushed when the source errors. Default `true`. 27 | - returns: The observable sequence which is paused and resumed based upon the pauser observable sequence. 28 | */ 29 | public func pausableBuffered (_ pauser: Pauser, limit: Int? = 1, flushOnCompleted: Bool = true, flushOnError: Bool = true) -> Observable where Pauser.Element == Bool { 30 | 31 | return Observable.create { observer in 32 | var buffer: [Element] = [] 33 | if let limit = limit { 34 | buffer.reserveCapacity(limit) 35 | } 36 | 37 | var paused = true 38 | var flushIndex = 0 39 | let lock = NSRecursiveLock() 40 | 41 | let flush = { 42 | while flushIndex < buffer.count { 43 | flushIndex += 1 44 | observer.onNext(buffer[flushIndex - 1]) 45 | } 46 | if buffer.count > 0 { 47 | flushIndex = 0 48 | buffer.removeAll(keepingCapacity: limit != nil) 49 | } 50 | } 51 | 52 | let boundaryDisposable = pauser.distinctUntilChanged(==).subscribe { event in 53 | lock.lock(); defer { lock.unlock() } 54 | switch event { 55 | case .next(let resume): 56 | if resume && buffer.count > 0 { 57 | flush() 58 | } 59 | paused = !resume 60 | 61 | case .completed: 62 | observer.onCompleted() 63 | case .error(let error): 64 | observer.onError(error) 65 | } 66 | } 67 | 68 | let disposable = self.subscribe { event in 69 | lock.lock(); defer { lock.unlock() } 70 | switch event { 71 | case .next(let element): 72 | if paused { 73 | buffer.append(element) 74 | if let limit = limit, buffer.count > limit { 75 | buffer.remove(at: 0) 76 | } 77 | } else { 78 | observer.onNext(element) 79 | } 80 | 81 | case .completed: 82 | if flushOnCompleted { flush() } 83 | observer.onCompleted() 84 | 85 | case .error(let error): 86 | if flushOnError { flush() } 87 | observer.onError(error) 88 | } 89 | } 90 | 91 | return Disposables.create([disposable, boundaryDisposable]) 92 | } 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /Source/RxSwift/repeatWithBehavior.swift: -------------------------------------------------------------------------------- 1 | // 2 | // repeatWithBehavior.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Marin Todorov on 05/08/2017. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | public typealias RepeatPredicate = () -> Bool 13 | 14 | /* 15 | Uses RepeatBehavior defined in retryWithBehavior 16 | */ 17 | 18 | /** Dummy error to use with catchError to restart the observable */ 19 | private enum RepeatError: Error { 20 | case catchable 21 | } 22 | 23 | extension ObservableType { 24 | 25 | /** 26 | Repeats the source observable sequence using given behavior when it completes 27 | - parameter behavior: Behavior that will be used when the observable completes 28 | - parameter scheduler: Schedular that will be used for delaying subscription after error 29 | - parameter shouldRepeat: Custom optional closure for decided whether the observable should repeat another round 30 | - returns: Observable sequence that will be automatically repeat when it completes 31 | */ 32 | public func repeatWithBehavior(_ behavior: RepeatBehavior, scheduler: SchedulerType = MainScheduler.instance, shouldRepeat: RepeatPredicate? = nil) -> Observable { 33 | return repeatWithBehavior(1, behavior: behavior, scheduler: scheduler, shouldRepeat: shouldRepeat) 34 | } 35 | 36 | /** 37 | Repeats the source observable sequence using given behavior when it completes 38 | - parameter currentRepeat: Number of the current repetition 39 | - parameter behavior: Behavior that will be used in case of completion 40 | - parameter scheduler: Schedular that will be used for delaying subscription after error 41 | - parameter shouldRepeat: Custom optional closure for decided whether the observable should repeat another round 42 | - returns: Observable sequence that will be automatically repeat when it completes 43 | */ 44 | internal func repeatWithBehavior(_ currentRepeat: UInt, behavior: RepeatBehavior, scheduler: SchedulerType = MainScheduler.instance, shouldRepeat: RepeatPredicate? = nil) 45 | -> Observable { 46 | guard currentRepeat > 0 else { return Observable.empty() } 47 | 48 | // calculate conditions for bahavior 49 | let conditions = behavior.calculateConditions(currentRepeat) 50 | 51 | return concat(Observable.error(RepeatError.catchable)) 52 | .catch { error in 53 | //if observable errors, forward the error 54 | guard error is RepeatError else { 55 | return Observable.error(error) 56 | } 57 | 58 | //repeat 59 | guard conditions.maxCount > currentRepeat else { return Observable.empty() } 60 | 61 | if let shouldRepeat = shouldRepeat, !shouldRepeat() { 62 | // also return error if predicate says so 63 | return Observable.empty() 64 | } 65 | 66 | guard conditions.delay != .never else { 67 | // if there is no delay, simply retry 68 | return self.repeatWithBehavior(currentRepeat + 1, behavior: behavior, scheduler: scheduler, shouldRepeat: shouldRepeat) 69 | } 70 | 71 | // otherwise retry after specified delay 72 | return Observable.just(()).delaySubscription(conditions.delay, scheduler: scheduler).flatMapLatest { 73 | self.repeatWithBehavior(currentRepeat + 1, behavior: behavior, scheduler: scheduler, shouldRepeat: shouldRepeat) 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Source/RxSwift/toSortedArray.swift: -------------------------------------------------------------------------------- 1 | // 2 | // toSortedArray.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Joan Disho on 17/02/18. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | public extension ObservableType { 13 | /** 14 | Converts an Observable into another Observable that emits the whole sequence as a single array sorted using the provided closure and then terminates. 15 | 16 | - parameter by: A comparator closure to sort emitted elements. 17 | - returns: An observable sequence containing all the sorted emitted elements as an array. 18 | */ 19 | func toSortedArray(by: @escaping (Element, Element) -> Bool) -> Single<[Element]> { 20 | return toArray().map { $0.sorted(by: by) } 21 | } 22 | } 23 | 24 | public extension ObservableType where Element: Comparable { 25 | /** 26 | Converts an Observable into another Observable that emits the whole sequence as a single sorted array and then terminates. 27 | 28 | - parameter ascending: Should the emitted items be ascending or descending. 29 | - returns: An observable sequence containing all the sorted emitted elements as an array. 30 | */ 31 | func toSortedArray(ascending: Bool = true) -> Single<[Element]> { 32 | return toSortedArray(by: { ascending ? $0 < $1 : $0 > $1 }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Source/RxSwift/unwrap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // unwrap.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Marin Todorov on 4/7/16. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | extension ObservableType { 13 | 14 | /** 15 | Takes a sequence of optional elements and returns a sequence of non-optional elements, filtering out any nil values. 16 | 17 | - returns: An observable sequence of non-optional elements 18 | */ 19 | 20 | public func unwrap() -> Observable where Element == Result? { 21 | return self.compactMap { $0 } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Source/RxSwift/zipWith.swift: -------------------------------------------------------------------------------- 1 | // 2 | // zipWith.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Arjan Duijzer on 26/12/2017. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | extension ObservableConvertibleType { 12 | 13 | /** 14 | Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index. 15 | 16 | - seealso: [zip operator on reactivex.io](http://reactivex.io/documentation/operators/zip.html) 17 | 18 | - Parameters: 19 | - with: A second Observable to zip with `self` 20 | - resultSelector: Function to invoke for each series of elements at corresponding indexes in the sources. 21 | 22 | - Returns: An observable sequence containing the result of combining elements of the sources using the specified result selector function. 23 | */ 24 | public func zip(with second: Other, resultSelector: @escaping (Element, Other.Element) throws -> ResultType) -> Observable { 25 | return Observable.zip(asObservable(), second.asObservable(), resultSelector: resultSelector) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Source/Tools/Observable+Alias.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Observable+Alias.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Shai Mishali on 23/08/2023. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import RxSwift 10 | 11 | public typealias Observable = RxSwift.Observable 12 | -------------------------------------------------------------------------------- /Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/RxCocoa/MapToTests+RxCocoa.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapToTests+RxCocoa.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Rafael Ferreira on 3/7/17. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import RxCocoa 11 | import RxSwift 12 | import RxSwiftExt 13 | import RxTest 14 | 15 | class MapToCocoaTests: XCTestCase { 16 | 17 | let numbers: [Int?] = [1, nil, Int?(3)] 18 | fileprivate var observer: TestableObserver! 19 | 20 | override func setUp() { 21 | super.setUp() 22 | 23 | let scheduler = TestScheduler(initialClock: 0) 24 | observer = scheduler.createObserver(String.self) 25 | 26 | _ = Observable.from(numbers) 27 | .asDriver(onErrorJustReturn: nil) 28 | .mapTo("candy") 29 | .drive(observer) 30 | 31 | scheduler.start() 32 | } 33 | 34 | func testReplaceWithResultCount() { 35 | XCTAssertEqual( 36 | observer.events.count-1 /*complete event*/, 37 | numbers.count 38 | ) 39 | } 40 | 41 | func testReplaceWithResultValues() { 42 | // test elements values and type 43 | let correctValues = Recorded.events([ 44 | .next(0, "candy"), 45 | .next(0, "candy"), 46 | .next(0, "candy"), 47 | .completed(0) 48 | ]) 49 | 50 | XCTAssertEqual(observer.events, correctValues) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Tests/RxCocoa/NotTests+RxCocoa.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotTests+RxCocoa.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Rafael Ferreira on 3/7/17. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import RxCocoa 11 | import RxSwift 12 | import RxSwiftExt 13 | import RxTest 14 | 15 | class NotCocoaTests: XCTestCase { 16 | func testNot() { 17 | let values = [true, false, true] 18 | 19 | let scheduler = TestScheduler(initialClock: 0) 20 | let observer = scheduler.createObserver(Bool.self) 21 | 22 | _ = Observable.from(values).asDriver(onErrorJustReturn: false) 23 | .not() 24 | .drive(observer) 25 | 26 | scheduler.start() 27 | 28 | let correct = Recorded.events([ 29 | .next(0, false), 30 | .next(0, true), 31 | .next(0, false), 32 | .completed(0) 33 | ]) 34 | 35 | XCTAssertEqual(observer.events, correct) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/RxCocoa/PartitionTests+RxCocoa.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PartitionTests+RxCocoa.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Shai Mishali on 24/11/2018. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | import RxCocoa 12 | import RxTest 13 | import XCTest 14 | import RxSwiftExt 15 | 16 | class PartitionSharedStrategyTests: XCTestCase { 17 | private var scheduler = TestScheduler(initialClock: 0) 18 | private var stream: Driver! 19 | 20 | override func setUp() { 21 | super.setUp() 22 | scheduler = TestScheduler(initialClock: 0) 23 | let events = (0...10).map { i -> Recorded> in 24 | return .next(i * 10, i) 25 | } + [.completed(100)] 26 | 27 | stream = scheduler 28 | .createHotObservable(events) 29 | .asDriver(onErrorDriveWith: .never()) 30 | } 31 | 32 | func testPartitionBothMatch() { 33 | let (evens, odds) = stream.partition { $0 % 2 == 0 } 34 | 35 | let evensObserver = scheduler.createObserver(Int.self) 36 | _ = evens.drive(evensObserver) 37 | 38 | let oddsObserver = scheduler.createObserver(Int.self) 39 | _ = odds.drive(oddsObserver) 40 | 41 | scheduler.start() 42 | 43 | XCTAssertEqual(oddsObserver.events, Recorded.events([ 44 | .next(10, 1), 45 | .next(30, 3), 46 | .next(50, 5), 47 | .next(70, 7), 48 | .next(90, 9), 49 | .completed(100) 50 | ])) 51 | 52 | XCTAssertEqual(evensObserver.events, Recorded.events([ 53 | .next(0, 0), 54 | .next(20, 2), 55 | .next(40, 4), 56 | .next(60, 6), 57 | .next(80, 8), 58 | .next(100, 10), 59 | .completed(100) 60 | ])) 61 | } 62 | 63 | func testPartitionOneSideMatch() { 64 | let (all, none) = stream.partition { $0 <= 10 } 65 | 66 | let allObserver = scheduler.createObserver(Int.self) 67 | _ = all.drive(allObserver) 68 | 69 | let noneObserver = scheduler.createObserver(Int.self) 70 | _ = none.drive(noneObserver) 71 | 72 | scheduler.start() 73 | 74 | XCTAssertEqual(allObserver.events, Recorded.events([ 75 | .next(0, 0), 76 | .next(10, 1), 77 | .next(20, 2), 78 | .next(30, 3), 79 | .next(40, 4), 80 | .next(50, 5), 81 | .next(60, 6), 82 | .next(70, 7), 83 | .next(80, 8), 84 | .next(90, 9), 85 | .next(100, 10), 86 | .completed(100) 87 | ])) 88 | 89 | XCTAssertEqual(noneObserver.events, [.completed(100)]) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Tests/RxCocoa/UIScrollView+reachedBottomTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+reachedBottomTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Anton Nazarov on 09/05/2019. 6 | // Copyright © 2019 RxSwift Community. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import XCTest 11 | import RxCocoa 12 | import RxSwift 13 | import RxTest 14 | import RxSwiftExt 15 | 16 | final class UIScrollViewReachedBottomTests: XCTestCase { 17 | private var tableView: UITableView! 18 | private var dataSource: StubDataSource! 19 | private var scheduler: TestScheduler! 20 | private var observer: TestableObserver! 21 | private var disposeBag: DisposeBag! 22 | 23 | override func setUp() { 24 | super.setUp() 25 | dataSource = StubDataSource() 26 | tableView = UITableView() 27 | tableView.dataSource = dataSource 28 | tableView.rowHeight = 44 29 | tableView.reloadData() 30 | scheduler = TestScheduler(initialClock: 0) 31 | observer = scheduler.createObserver(Void.self) 32 | disposeBag = DisposeBag() 33 | } 34 | 35 | override func tearDown() { 36 | dataSource = nil 37 | tableView = nil 38 | scheduler = nil 39 | observer = nil 40 | super.tearDown() 41 | } 42 | 43 | func testReachedBottomNotEmitsIfNotScrolledToBottom() { 44 | // Given 45 | let almostBottomY = CGFloat(dataSource.cellsCount) * tableView.rowHeight - 1 46 | tableView.rx.reachedBottom().bind(to: observer).disposed(by: disposeBag) 47 | // When 48 | tableView.contentOffset = CGPoint(x: 0, y: almostBottomY) 49 | // Then 50 | XCTAssertTrue(observer.events.isEmpty) 51 | } 52 | 53 | func testReachedBottomNotEmitsIfNotScrolledToBottomWithNonZeroOffset() { 54 | // Given 55 | let offset: CGFloat = 40.0 56 | let almostBottomY = CGFloat(dataSource.cellsCount) * tableView.rowHeight - offset - 1 57 | tableView.rx.reachedBottom(offset: offset).bind(to: observer).disposed(by: disposeBag) 58 | // When 59 | tableView.contentOffset = CGPoint(x: 0, y: almostBottomY) 60 | // Then 61 | XCTAssertTrue(observer.events.isEmpty) 62 | } 63 | 64 | func testReachedBottomEmitsIfScrolledToBottom() { 65 | // Given 66 | let actual: [Recorded>] = [.next(0, ())] 67 | let bottomY = CGFloat(dataSource.cellsCount) * tableView.rowHeight 68 | tableView.rx.reachedBottom().bind(to: observer).disposed(by: disposeBag) 69 | // When 70 | tableView.contentOffset = CGPoint(x: 0, y: bottomY) 71 | // Then 72 | XCTAssertEqual(actual.count, observer.events.count) 73 | } 74 | } 75 | 76 | private extension UIScrollViewReachedBottomTests { 77 | final class StubDataSource: NSObject, UITableViewDataSource { 78 | let cellsCount = 28 79 | 80 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 81 | return cellsCount 82 | } 83 | 84 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 85 | return UITableViewCell() 86 | } 87 | } 88 | } 89 | #endif 90 | -------------------------------------------------------------------------------- /Tests/RxCocoa/UIViewPropertyAnimatorTests+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewPropertyAnimatorTests+Rx.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Thibault Wittemberg on 3/4/18. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | #if canImport(UIKit) 10 | import XCTest 11 | import RxSwift 12 | import RxCocoa 13 | import RxSwiftExt 14 | import RxTest 15 | import UIKit 16 | 17 | @available(iOS 10.0, *) 18 | class UIViewPropertyAnimatorTests: XCTestCase { 19 | var disposeBag: DisposeBag! 20 | 21 | override func setUp() { 22 | disposeBag = DisposeBag() 23 | } 24 | 25 | func testAnimationCompleted() { 26 | let expectations = expectation(description: "Animation completed") 27 | 28 | let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) 29 | let animator = UIViewPropertyAnimator(duration: 0.5, curve: .linear) { 30 | view.transform = CGAffineTransform(translationX: 100, y: 100) 31 | } 32 | 33 | animator 34 | .rx.animate() 35 | .subscribe(onCompleted: { 36 | XCTAssertEqual(100, view.frame.origin.x) 37 | XCTAssertEqual(100, view.frame.origin.y) 38 | expectations.fulfill() 39 | }) 40 | .disposed(by: disposeBag) 41 | 42 | waitForExpectations(timeout: 1) 43 | } 44 | 45 | func testBindToFractionCompleted() { 46 | let animator = UIViewPropertyAnimator( 47 | duration: 0, curve: .linear, animations: { } 48 | ) 49 | 50 | let subject = PublishSubject() 51 | 52 | subject 53 | .bind(to: animator.rx.fractionComplete) 54 | .disposed(by: disposeBag) 55 | 56 | subject.onNext(0.3) 57 | XCTAssertEqual(animator.fractionComplete, 0.3) 58 | 59 | subject.onNext(0.5) 60 | XCTAssertEqual(animator.fractionComplete, 0.5) 61 | } 62 | } 63 | #endif 64 | -------------------------------------------------------------------------------- /Tests/RxCocoa/unrwapTests+SharedSequence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnwrapTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Hugo Saynac on 05/10/2018. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | import RxSwift 12 | import RxSwiftExt 13 | import RxTest 14 | 15 | class UnwrapSharedStrategyTests: XCTestCase { 16 | let numbers: [Int?] = [1, nil, Int?(3), 4] 17 | private var observer: TestableObserver! 18 | private let numbersSubject = BehaviorSubject(value: nil) 19 | 20 | override func setUp() { 21 | super.setUp() 22 | 23 | let scheduler = TestScheduler(initialClock: 0) 24 | observer = scheduler.createObserver(Int.self) 25 | 26 | _ = numbersSubject 27 | .asDriver(onErrorJustReturn: nil) 28 | .unwrap() 29 | .asObservable() 30 | .subscribe(observer) 31 | 32 | _ = Observable.from(numbers) 33 | .bind(to: numbersSubject) 34 | 35 | scheduler.start() 36 | } 37 | 38 | func testUnwrapFilterNil() { 39 | //test results count 40 | print(observer.events) 41 | XCTAssertEqual( 42 | observer.events.count, 43 | numbers.count 44 | ) 45 | } 46 | 47 | func testUnwrapResultValues() { 48 | // test elements values and type 49 | let correctValues = Recorded.events( 50 | .next(0, 1), 51 | .next(0, 3), 52 | .next(0, 4), 53 | .completed(0) 54 | ) 55 | XCTAssertEqual(observer.events, correctValues) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/RxSwift/BufferWithTriggerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BufferWithTriggerTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Patrick Maltagliati on 11/12/18. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | import RxSwift 12 | import RxSwiftExt 13 | import RxTest 14 | 15 | class BufferWithTriggerTests: XCTestCase { 16 | let testError = NSError(domain: "dummyError", code: -232, userInfo: nil) 17 | let scheduler = TestScheduler(initialClock: 0) 18 | 19 | func testBuffersUntilBoundaryEmits() { 20 | let underlying = scheduler.createHotObservable( 21 | [ 22 | .next(150, 1), 23 | .next(201, 2), 24 | .next(230, 3), 25 | .next(300, 4), 26 | .next(350, 5), 27 | .next(375, 6), 28 | .next(400, 7), 29 | .next(430, 8), 30 | .completed(500) 31 | ] 32 | ) 33 | 34 | let boundary = scheduler.createHotObservable( 35 | [ 36 | .next(201, ()), 37 | .next(301, ()), 38 | .next(401, ()) 39 | ] 40 | ) 41 | 42 | let res = scheduler.start(disposed: 1000) { 43 | underlying.bufferWithTrigger(boundary.asObservable()) 44 | } 45 | 46 | let expected = Recorded.events([ 47 | .next(201, [2]), 48 | .next(301, [3, 4]), 49 | .next(401, [5, 6, 7]), 50 | .next(500, [8]), 51 | .completed(500) 52 | ]) 53 | 54 | XCTAssertEqual(res.events, expected) 55 | 56 | XCTAssertEqual(underlying.subscriptions, [Subscription(200, 500)]) 57 | } 58 | 59 | func testPausedError() { 60 | let underlying = scheduler.createHotObservable( 61 | [ 62 | .next(150, 1), 63 | .next(210, 2), 64 | .error(230, testError), 65 | .completed(500) 66 | ] 67 | ) 68 | 69 | let boundary = scheduler.createHotObservable( 70 | [ 71 | .next(201, ()), 72 | .next(211, ()) 73 | ] 74 | ) 75 | 76 | let res = scheduler.start(disposed: 1000) { 77 | underlying.bufferWithTrigger(boundary.asObservable()) 78 | } 79 | 80 | let expected = Recorded.events([ 81 | .next(201, []), 82 | .next(211, [2]), 83 | .error(230, testError) 84 | ]) 85 | XCTAssertEqual(res.events, expected) 86 | 87 | XCTAssertEqual(underlying.subscriptions, [Subscription(200, 230)]) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Tests/RxSwift/MapAtTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapToKeyPath.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Michael Avila on 2/8/18. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | import RxSwift 12 | import RxSwiftExt 13 | import RxTest 14 | 15 | class MapAtTests: XCTestCase { 16 | struct Person { 17 | let name: String 18 | } 19 | 20 | let people: [Person] = [ 21 | Person(name: "Bart"), 22 | Person(name: "Lisa"), 23 | Person(name: "Maggie") 24 | ] 25 | 26 | private var observer: TestableObserver! 27 | 28 | override func setUp() { 29 | let scheduler = TestScheduler(initialClock: 0) 30 | observer = scheduler.createObserver(String.self) 31 | 32 | _ = Observable.from(people) 33 | .mapAt(\.name) 34 | .subscribe(observer) 35 | 36 | scheduler.start() 37 | } 38 | 39 | func testResultSequenceHasSameNumberOfItemsAsSourceSequence() { 40 | XCTAssertEqual( 41 | observer.events.count - 1, // completed event 42 | people.count 43 | ) 44 | } 45 | 46 | func testResultSequenceHasValuesAtProvidedKeypath() { 47 | // test elements values and type 48 | let correctValues = Recorded.events([ 49 | .next(0, "Bart"), 50 | .next(0, "Lisa"), 51 | .next(0, "Maggie"), 52 | .completed(0) 53 | ]) 54 | 55 | XCTAssertEqual(observer.events, correctValues) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/RxSwift/MapManyTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapManyTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Joan Disho on 06/05/18. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import RxSwiftExt 11 | import RxSwift 12 | import RxTest 13 | 14 | class MapManyTests: XCTestCase { 15 | func runAndObserve(_ sequence: Observable) -> TestableObserver { 16 | let scheduler = TestScheduler(initialClock: 0) 17 | let observer = scheduler.createObserver(C.self) 18 | _ = sequence.asObservable().subscribe(observer) 19 | scheduler.start() 20 | return observer 21 | } 22 | 23 | func testMapManyWithModel() { 24 | // swiftlint:disable:next nesting 25 | struct SomeModel: CustomStringConvertible { 26 | let number: Int 27 | var description: String { return "#\(number)" } 28 | 29 | init(_ number: Int) { 30 | self.number = number 31 | } 32 | } 33 | 34 | let sourceArray = Observable.of(1...4) 35 | 36 | let observer = runAndObserve(sourceArray.mapMany(SomeModel.init)) 37 | let correct = Recorded.events([ 38 | .next(0, [SomeModel(1), SomeModel(2), SomeModel(3), SomeModel(4)]), 39 | .completed(0) 40 | ]) 41 | 42 | XCTAssertEqual(observer.events.description, correct.description) 43 | } 44 | 45 | func testMapManyWithInts() { 46 | let sourceArray = Observable.of(1...10) 47 | 48 | let observer = runAndObserve(sourceArray.mapMany { $0 + 1 }) 49 | let correct = Recorded.events([ 50 | .next(0, [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]), 51 | .completed(0) 52 | ]) 53 | 54 | XCTAssertEqual(observer.events, correct) 55 | } 56 | 57 | func testMapManyWithStrings() { 58 | let sourceArray = Observable.just(["a", "b", "C"]) 59 | 60 | let observer = runAndObserve(sourceArray.mapMany { ($0 + "x").lowercased() }) 61 | let correct = Recorded.events([ 62 | .next(0, ["ax", "bx", "cx"]), 63 | .completed(0) 64 | ]) 65 | 66 | XCTAssertEqual(observer.events, correct) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Tests/RxSwift/MergeWithTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MergeWithTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Joan Disho on 28/05/18. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import RxSwift 11 | import RxTest 12 | 13 | class MergeWithTests: XCTestCase { 14 | fileprivate func runAndObserve(_ sequence: Observable) -> TestableObserver { 15 | let scheduler = TestScheduler(initialClock: 0) 16 | let observer = scheduler.createObserver(T.self) 17 | _ = sequence.asObservable().subscribe(observer) 18 | scheduler.start() 19 | return observer 20 | } 21 | 22 | func testMergeWith_Numbers() { 23 | let oddNumbers = [1, 3, 5, 7, 9] 24 | let evenNumbers = [2, 4, 6, 8, 10] 25 | 26 | let oddStream = Observable.from(oddNumbers) 27 | let evenStream = Observable.from(evenNumbers) 28 | 29 | let observer1 = runAndObserve(oddStream.merge(with: evenStream)) 30 | let observer2 = runAndObserve(evenStream.merge(with: oddStream)) 31 | 32 | let correct1 = Recorded.events([ 33 | .next(0, 1), 34 | .next(0, 2), 35 | .next(0, 3), 36 | .next(0, 4), 37 | .next(0, 5), 38 | .next(0, 6), 39 | .next(0, 7), 40 | .next(0, 8), 41 | .next(0, 9), 42 | .next(0, 10), 43 | .completed(0) 44 | ]) 45 | 46 | let correct2 = Recorded.events([ 47 | .next(0, 2), 48 | .next(0, 1), 49 | .next(0, 4), 50 | .next(0, 3), 51 | .next(0, 6), 52 | .next(0, 5), 53 | .next(0, 8), 54 | .next(0, 7), 55 | .next(0, 10), 56 | .next(0, 9), 57 | .completed(0) 58 | ]) 59 | 60 | XCTAssertEqual(observer1.events, correct1) 61 | XCTAssertEqual(observer2.events, correct2) 62 | } 63 | 64 | func testMergeWith_Error() { 65 | let numberStream = Observable.from([1, 2, 3, 4]) 66 | let errorStream = Observable.error(testError) 67 | 68 | let observer = runAndObserve(numberStream.merge(with: errorStream)) 69 | 70 | let expected = Recorded>.events([.error(0, testError)]) 71 | 72 | XCTAssertEqual(observer.events, expected) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Tests/RxSwift/OnceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnceTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Florent Pillet on 12/04/16. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | import RxSwift 12 | import RxSwiftExt 13 | import RxTest 14 | 15 | class OnceTests: XCTestCase { 16 | func testOnce() { 17 | let onceObservable = Observable.once("Hello") 18 | let scheduler = TestScheduler(initialClock: 0) 19 | 20 | let observer1 = scheduler.createObserver(String.self) 21 | _ = onceObservable.subscribe(observer1) 22 | 23 | let observer2 = scheduler.createObserver(String.self) 24 | _ = onceObservable.subscribe(observer2) 25 | 26 | scheduler.start() 27 | 28 | let correct1 = Recorded.events([ 29 | .next(0, "Hello"), 30 | .completed(0) 31 | ]) 32 | 33 | let correct2: [Recorded>] = [ 34 | .completed(0) 35 | ] 36 | 37 | XCTAssertEqual(observer1.events, correct1) 38 | XCTAssertEqual(observer2.events, correct2) 39 | 40 | } 41 | 42 | func testMultipleOnce() { 43 | let onceObservable1 = Observable.once("Hello") 44 | let onceObservable2 = Observable.once("world") 45 | let scheduler = TestScheduler(initialClock: 0) 46 | 47 | let observer1 = scheduler.createObserver(String.self) 48 | _ = onceObservable1.subscribe(observer1) 49 | let observer2 = scheduler.createObserver(String.self) 50 | _ = onceObservable1.subscribe(observer2) 51 | 52 | let observer3 = scheduler.createObserver(String.self) 53 | _ = onceObservable2.subscribe(observer3) 54 | let observer4 = scheduler.createObserver(String.self) 55 | _ = onceObservable2.subscribe(observer4) 56 | 57 | scheduler.start() 58 | 59 | let correct1 = Recorded.events([ 60 | .next(0, "Hello"), 61 | .completed(0) 62 | ]) 63 | 64 | let correct2: [Recorded>] = [ 65 | .completed(0) 66 | ] 67 | 68 | let correct3 = Recorded.events([ 69 | .next(0, "world"), 70 | .completed(0) 71 | ]) 72 | 73 | let correct4: [Recorded>] = [ 74 | .completed(0) 75 | ] 76 | 77 | XCTAssertEqual(observer1.events, correct1) 78 | XCTAssertEqual(observer2.events, correct2) 79 | XCTAssertEqual(observer3.events, correct3) 80 | XCTAssertEqual(observer4.events, correct4) 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Tests/RxSwift/PartitionTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PartitionTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Shai Mishali on 24/11/2018. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | import RxTest 12 | import XCTest 13 | import RxSwiftExt 14 | 15 | class PartitionTests: XCTestCase { 16 | private var scheduler = TestScheduler(initialClock: 0) 17 | private var stream: TestableObservable! 18 | 19 | override func setUp() { 20 | super.setUp() 21 | scheduler = TestScheduler(initialClock: 0) 22 | let events = (0...10).map { i -> Recorded> in 23 | return .next(i * 10, i) 24 | } + [.completed(100)] 25 | 26 | stream = scheduler.createHotObservable(events) 27 | } 28 | 29 | func testPartitionBothMatch() { 30 | let (evens, odds) = stream.partition { $0 % 2 == 0 } 31 | 32 | let evensObserver = scheduler.createObserver(Int.self) 33 | _ = evens.bind(to: evensObserver) 34 | 35 | let oddsObserver = scheduler.createObserver(Int.self) 36 | _ = odds.bind(to: oddsObserver) 37 | 38 | scheduler.start() 39 | 40 | XCTAssertEqual(oddsObserver.events, Recorded.events([ 41 | .next(10, 1), 42 | .next(30, 3), 43 | .next(50, 5), 44 | .next(70, 7), 45 | .next(90, 9), 46 | .completed(100) 47 | ])) 48 | 49 | XCTAssertEqual(evensObserver.events, Recorded.events([ 50 | .next(0, 0), 51 | .next(20, 2), 52 | .next(40, 4), 53 | .next(60, 6), 54 | .next(80, 8), 55 | .next(100, 10), 56 | .completed(100) 57 | ])) 58 | } 59 | 60 | func testPartitionOneSideMatch() { 61 | let (all, none) = stream.partition { $0 <= 10 } 62 | 63 | let allObserver = scheduler.createObserver(Int.self) 64 | _ = all.bind(to: allObserver) 65 | 66 | let noneObserver = scheduler.createObserver(Int.self) 67 | _ = none.bind(to: noneObserver) 68 | 69 | scheduler.start() 70 | 71 | XCTAssertEqual(allObserver.events, Recorded.events([ 72 | .next(0, 0), 73 | .next(10, 1), 74 | .next(20, 2), 75 | .next(30, 3), 76 | .next(40, 4), 77 | .next(50, 5), 78 | .next(60, 6), 79 | .next(70, 7), 80 | .next(80, 8), 81 | .next(90, 9), 82 | .next(100, 10), 83 | .completed(100) 84 | ])) 85 | 86 | XCTAssertEqual(noneObserver.events, [.completed(100)]) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Tests/RxSwift/ToSortedArrayTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToSortedArrayTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Joan Disho on 28/04/18. 6 | // Copyright © 2018 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import RxSwift 11 | import RxTest 12 | 13 | class ToSortedArrayTests: XCTestCase { 14 | func runAndObserve(_ sequence: Observable) -> TestableObserver { 15 | let scheduler = TestScheduler(initialClock: 0) 16 | let observer = scheduler.createObserver(T.self) 17 | _ = sequence.asObservable().subscribe(observer) 18 | scheduler.start() 19 | return observer 20 | } 21 | 22 | func testDefaultToSortedArray() { 23 | let source = Observable.of(1, 4, 6, 1, 7, 8) 24 | let observer = runAndObserve(source.toSortedArray().asObservable()) 25 | let correct = Recorded.events([ 26 | .next(0, [1, 1, 4, 6, 7, 8]), 27 | .completed(0) 28 | ]) 29 | XCTAssertEqual(observer.events, correct) 30 | } 31 | 32 | func testAscCase() { 33 | let source = Observable.of(1, 4, 6, 1, 7, 8) 34 | let observer = runAndObserve(source.toSortedArray(ascending: true).asObservable()) 35 | let correct = Recorded.events([ 36 | .next(0, [1, 1, 4, 6, 7, 8]), 37 | .completed(0) 38 | ]) 39 | XCTAssertEqual(observer.events, correct) 40 | } 41 | 42 | func testDescCase() { 43 | let source = Observable.of(1, 4, 6, 1, 7, 8) 44 | let observer = runAndObserve(source.toSortedArray(ascending: false).asObservable()) 45 | let correct = Recorded.events([ 46 | .next(0, [8, 7, 6, 4, 1, 1]), 47 | .completed(0) 48 | ]) 49 | XCTAssertEqual(observer.events, correct) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/RxSwift/UnwrapTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UnwrapTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Marin Todorov on 4/7/16. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | import RxSwift 12 | import RxSwiftExt 13 | import RxTest 14 | 15 | class UnwrapTests: XCTestCase { 16 | let numbers: [Int?] = [1, nil, Int?(3), 4] 17 | fileprivate var observer: TestableObserver! 18 | 19 | override func setUp() { 20 | super.setUp() 21 | 22 | let scheduler = TestScheduler(initialClock: 0) 23 | observer = scheduler.createObserver(Int.self) 24 | 25 | _ = Observable.from(numbers) 26 | .unwrap() 27 | .subscribe(observer) 28 | 29 | scheduler.start() 30 | } 31 | 32 | func testUnwrapFilterNil() { 33 | //test results count 34 | XCTAssertEqual( 35 | observer.events.count, 36 | numbers.count - 1/* the nr. of nil elements*/ + 1 /* complete event*/ 37 | ) 38 | } 39 | 40 | func testUnwrapResultValues() { 41 | // test elements values and type 42 | let correctValues = Recorded.events([ 43 | .next(0, 1), 44 | .next(0, 3), 45 | .next(0, 4), 46 | .completed(0) 47 | ]) 48 | 49 | XCTAssertEqual(observer.events, correctValues) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Tests/RxSwift/WeakTarget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeakTarget.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Ian Keen on 17/04/2016. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | enum RxEvent { 13 | case next, error, completed, disposed 14 | init(event: Event) { 15 | switch event { 16 | case .next: self = .next 17 | case .error: self = .error 18 | case .completed: self = .completed 19 | } 20 | } 21 | } 22 | 23 | var weakTargetReferenceCount: Int = 0 24 | 25 | class WeakTarget { 26 | let listener: ([RxEvent: Int]) -> Void 27 | fileprivate let observable: Observable 28 | fileprivate var disposeBag = DisposeBag() 29 | fileprivate var events: [RxEvent: Int] = [.next: 0, .error: 0, .completed: 0, .disposed: 0] 30 | fileprivate func updateEvents(_ event: RxEvent) { 31 | self.events[event] = (self.events[event] ?? 0) + 1 32 | self.listener(self.events) 33 | } 34 | 35 | init(obs: Observable, listener: @escaping ([RxEvent: Int]) -> Void) { 36 | weakTargetReferenceCount += 1 37 | self.listener = listener 38 | self.observable = obs 39 | } 40 | deinit { weakTargetReferenceCount -= 1 } 41 | 42 | // MARK: - Subscribers 43 | fileprivate func subscriber_on(_ event: Event) { self.updateEvents(RxEvent(event: event)) } 44 | fileprivate func subscriber_onNext(_ element: Type) { self.updateEvents(.next) } 45 | fileprivate func subscriber_onError(_ error: Error) { self.updateEvents(.error) } 46 | fileprivate func subscriber_onComplete() { self.updateEvents(.completed) } 47 | fileprivate func subscriber_onDisposed() { self.updateEvents(.disposed) } 48 | 49 | // MARK: - Subscription Setup 50 | func useSubscribe() { 51 | self.observable.subscribe(weak: self, WeakTarget.subscriber_on).disposed(by: self.disposeBag) 52 | } 53 | func useSubscribeNext() { 54 | //self.observable.subscribeNext(self.subscriber_onNext).addDisposableTo(self.disposeBag) //uncomment this line to create a retain cycle 55 | self.observable.subscribeNext(weak: self, WeakTarget.subscriber_onNext).disposed(by: self.disposeBag) 56 | } 57 | func useSubscribeError() { 58 | self.observable.subscribeError(weak: self, WeakTarget.subscriber_onError).disposed(by: self.disposeBag) 59 | } 60 | func useSubscribeComplete() { 61 | self.observable.subscribeCompleted(weak: self, WeakTarget.subscriber_onComplete).disposed(by: self.disposeBag) 62 | } 63 | func useSubscribeMulti() { 64 | self.observable 65 | .subscribe( 66 | weak: self, 67 | onNext: WeakTarget.subscriber_onNext, 68 | onError: WeakTarget.subscriber_onError, 69 | onCompleted: WeakTarget.subscriber_onComplete, 70 | onDisposed: WeakTarget.subscriber_onDisposed 71 | ) 72 | .disposed(by: self.disposeBag) 73 | } 74 | 75 | func dispose() { 76 | self.disposeBag = DisposeBag() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Tests/RxSwift/WeakTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeakTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Ian Keen on 17/04/2016. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | import RxSwift 12 | import RxSwiftExt 13 | import RxTest 14 | 15 | class WeakTests: XCTestCase { 16 | var target: WeakTarget? 17 | var events = [RxEvent: Int]() 18 | let source = PublishSubject() 19 | 20 | override func setUp() { 21 | super.setUp() 22 | 23 | self.events = [RxEvent: Int]() 24 | XCTAssertTrue(weakTargetReferenceCount == 0) 25 | self.target = WeakTarget(obs: self.source) { 26 | self.events = $0 27 | } 28 | XCTAssertTrue(weakTargetReferenceCount == 1) 29 | } 30 | 31 | override func tearDown() { 32 | super.tearDown() 33 | self.target = nil 34 | 35 | // If a retain cycle was present this would not return to 0 36 | XCTAssertTrue(weakTargetReferenceCount == 0) 37 | } 38 | 39 | // MARK: - Event Subscriber 40 | func testSubscribe() { 41 | self.target?.useSubscribe() 42 | 43 | self.source.onNext(0) 44 | self.source.onNext(0) 45 | self.target = nil 46 | self.source.onNext(0) 47 | self.source.onError(testError) 48 | self.target?.dispose() 49 | 50 | let expected: [RxEvent: Int] = [.next: 2, .error: 0, .completed: 0, .disposed: 0] 51 | XCTAssertTrue(events == expected) 52 | } 53 | 54 | // MARK: - Individual Subscribers 55 | func testSubscribeNext() { 56 | self.target?.useSubscribeNext() 57 | 58 | self.source.onNext(0) 59 | self.source.onNext(0) 60 | self.target = nil 61 | self.source.onNext(0) 62 | 63 | // If a retain cycle was present, the .next count would be 3 64 | let expected: [RxEvent: Int] = [.next: 2, .error: 0, .completed: 0, .disposed: 0] 65 | XCTAssertTrue(events == expected) 66 | } 67 | 68 | func testSubscribeError() { 69 | self.target?.useSubscribeError() 70 | 71 | self.source.onError(testError) 72 | self.target = nil 73 | 74 | // Errors only emit once 75 | let expected: [RxEvent: Int] = [.next: 0, .error: 1, .completed: 0, .disposed: 0] 76 | XCTAssertTrue(events == expected) 77 | } 78 | 79 | func testSubscribeCompleted() { 80 | self.target?.useSubscribeComplete() 81 | 82 | self.source.onCompleted() 83 | self.target = nil 84 | 85 | // Completed only emit once 86 | let expected: [RxEvent: Int] = [.next: 0, .error: 0, .completed: 1, .disposed: 0] 87 | XCTAssertTrue(events == expected) 88 | } 89 | 90 | // MARK: - Multiple Subscribers 91 | func testSubscribeOn_Next() { 92 | self.target?.useSubscribeMulti() 93 | 94 | self.source.onNext(0) 95 | self.source.onNext(0) 96 | self.target = nil 97 | self.source.onNext(0) 98 | 99 | // If a retain cycle was present, the .next count would be 3 100 | let expected: [RxEvent: Int] = [.next: 2, .error: 0, .completed: 0, .disposed: 0] 101 | XCTAssertTrue(events == expected) 102 | } 103 | 104 | func testSubscribeOn_Error() { 105 | self.target?.useSubscribeMulti() 106 | 107 | self.source.onError(testError) 108 | self.target = nil 109 | 110 | // Errors only emit once 111 | let expected: [RxEvent: Int] = [.next: 0, .error: 1, .completed: 0, .disposed: 1] 112 | XCTAssertTrue(events == expected) 113 | } 114 | 115 | func testSubscribeOn_Completed() { 116 | self.target?.useSubscribeMulti() 117 | 118 | self.source.onCompleted() 119 | self.target = nil 120 | 121 | // completed only emit once 122 | let expected: [RxEvent: Int] = [.next: 0, .error: 0, .completed: 1, .disposed: 1] 123 | XCTAssertTrue(events == expected) 124 | } 125 | 126 | func testSubscribeOn_Disposed() { 127 | self.target?.useSubscribeMulti() 128 | 129 | self.target?.dispose() 130 | self.target = nil 131 | 132 | // Completed only emit once 133 | let expected: [RxEvent: Int] = [.next: 0, .error: 0, .completed: 0, .disposed: 1] 134 | XCTAssertTrue(events == expected) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Tests/RxSwift/ZipWithTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ZipWithTest.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Arjan Duijzer on 26/12/2017. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import RxSwift 11 | import RxTest 12 | 13 | struct Pair { 14 | let first: F 15 | let second: S 16 | } 17 | 18 | extension Pair: Equatable { 19 | } 20 | 21 | func ==(lhs: Pair, rhs: Pair) -> Bool { 22 | return lhs.first == rhs.first && lhs.second == rhs.second 23 | } 24 | 25 | class ZipWithTest: XCTestCase { 26 | func testZipWith_SourcesNotEmpty_ZipCompletes() { 27 | let scheduler = TestScheduler(initialClock: 0) 28 | let source1 = Observable.from([1, 2, 3]) 29 | let source2 = Observable.from(["a", "b"]) 30 | 31 | let res = scheduler.start { 32 | source1.zip(with: source2) { 33 | Pair(first: $0, second: $1) 34 | } 35 | } 36 | 37 | let expected = Recorded.events([ 38 | .next(200, Pair(first: 1, second: "a")), 39 | .next(200, Pair(first: 2, second: "b")), 40 | .completed(200) 41 | ]) 42 | 43 | XCTAssertEqual(res.events, expected) 44 | } 45 | 46 | func testZipWith_SourceEmpty_ZipCompletesEmpty() { 47 | let scheduler = TestScheduler(initialClock: 0) 48 | let source1 = Observable.from([1, 2, 3]) 49 | let source2 = Observable.empty() 50 | 51 | let res = scheduler.start { 52 | source1.zip(with: source2) { 53 | Pair(first: $0, second: $1) 54 | } 55 | } 56 | 57 | let expected: [Recorded>>] = [.completed(200)] 58 | XCTAssertEqual(res.events, expected) 59 | } 60 | 61 | func testZipWith_SourceError_ZipCompletesWithError() { 62 | let scheduler = TestScheduler(initialClock: 0) 63 | let source1 = Observable.just(1) 64 | let source2 = Observable.error(testError) 65 | 66 | let res = scheduler.start { 67 | source1.zip(with: source2) { 68 | Pair(first: $0, second: $1) 69 | } 70 | } 71 | 72 | let expected: [Recorded>>] = [.error(200, testError)] 73 | XCTAssertEqual(res.events, expected) 74 | } 75 | 76 | func testMaybeZipWith_SourcesNotEmpty_ZipCompletes() { 77 | let scheduler = TestScheduler(initialClock: 0) 78 | let source1 = Maybe.just(1) 79 | let source2 = Observable.from(["a", "b", "c"]) 80 | 81 | let res = scheduler.start { 82 | source1.zip(with: source2) { 83 | Pair(first: $0, second: $1) 84 | } 85 | } 86 | 87 | let expected = Recorded.events([ 88 | .next(200, Pair(first: 1, second: "a")), 89 | .completed(200) 90 | ]) 91 | XCTAssertEqual(res.events, expected) 92 | } 93 | 94 | func testSingleZipWith_SourcesNotEmpty_ZipCompletes() { 95 | let scheduler = TestScheduler(initialClock: 0) 96 | let source1 = Single.just(1) 97 | let source2 = Observable.just(2) 98 | 99 | let res = scheduler.start { 100 | source1.zip(with: source2) { 101 | Pair(first: $0, second: $1) 102 | } 103 | } 104 | 105 | let expected = Recorded.events([ 106 | .next(200, Pair(first: 1, second: 2)), 107 | .completed(200) 108 | ]) 109 | XCTAssertEqual(res.events, expected) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Tests/RxSwift/catchErrorJustCompleteTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CatchErrorJustCompleteTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Florent Pillet on 31/07/16. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | import RxSwift 12 | import RxSwiftExt 13 | import RxTest 14 | 15 | class CatchErrorJustCompleteTests: XCTestCase { 16 | let testError = NSError(domain: "dummyError", code: -232, userInfo: nil) 17 | 18 | func testCatchErrorJustComplete_Empty() { 19 | let scheduler = TestScheduler(initialClock: 0) 20 | 21 | let events: [Recorded>] = [ 22 | .completed(250)] 23 | let xs = scheduler.createColdObservable(events) 24 | 25 | let res = scheduler.start { 26 | xs.catchErrorJustComplete() 27 | } 28 | 29 | XCTAssertEqual(res.events, [ 30 | .completed(450) 31 | ]) 32 | 33 | XCTAssertEqual(xs.subscriptions, [ 34 | Subscription(200, 450) 35 | ]) 36 | } 37 | 38 | func testCatchErrorJustComplete_NoError() { 39 | let scheduler = TestScheduler(initialClock: 0) 40 | 41 | let xs = scheduler.createColdObservable([ 42 | .next(100, 1), 43 | .next(150, 2), 44 | .next(200, 3), 45 | .completed(250) 46 | ]) 47 | 48 | let res = scheduler.start { 49 | xs.catchErrorJustComplete() 50 | } 51 | 52 | XCTAssertEqual(res.events, [ 53 | .next(300, 1), 54 | .next(350, 2), 55 | .next(400, 3), 56 | .completed(450) 57 | ]) 58 | 59 | XCTAssertEqual(xs.subscriptions, [ 60 | Subscription(200, 450) 61 | ]) 62 | } 63 | 64 | func testCatchErrorJustComplete_Infinite() { 65 | let scheduler = TestScheduler(initialClock: 0) 66 | 67 | let xs = scheduler.createColdObservable([ 68 | .next(100, 1), 69 | .next(150, 2), 70 | .next(200, 3) 71 | ]) 72 | 73 | let res = scheduler.start { 74 | xs.catchErrorJustComplete() 75 | } 76 | 77 | XCTAssertEqual(res.events, [ 78 | .next(300, 1), 79 | .next(350, 2), 80 | .next(400, 3) 81 | ]) 82 | 83 | XCTAssertEqual(xs.subscriptions, [ 84 | Subscription(200, 1000) 85 | ]) 86 | } 87 | 88 | func testCatchErrorJustComplete_Error() { 89 | let scheduler = TestScheduler(initialClock: 0) 90 | 91 | let xs = scheduler.createColdObservable([ 92 | .next(100, 1), 93 | .next(150, 2), 94 | .error(250, testError) 95 | ]) 96 | 97 | let res = scheduler.start { 98 | xs.catchErrorJustComplete() 99 | } 100 | 101 | XCTAssertEqual(res.events, [ 102 | .next(300, 1), 103 | .next(350, 2), 104 | .completed(450) 105 | ]) 106 | 107 | XCTAssertEqual(xs.subscriptions, [ 108 | Subscription(200, 450) 109 | ]) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Tests/RxSwift/filterMapTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FilterMapTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Jeremie Girault on 31/05/2017. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | import RxSwift 12 | import RxSwiftExt 13 | import RxTest 14 | 15 | final class FilterMapTests: XCTestCase { 16 | private var scheduler: TestScheduler! 17 | 18 | override func setUp() { 19 | super.setUp() 20 | scheduler = TestScheduler(initialClock: 0) 21 | } 22 | 23 | override func tearDown() { 24 | scheduler = nil 25 | super.tearDown() 26 | } 27 | 28 | func testIgnoreEvenAndEvenizeOdds() { 29 | let observer = scheduler.createObserver(Int.self) 30 | 31 | let values = 1..<10 32 | _ = Observable.from(values) 33 | .filterMap { $0 % 2 == 0 ? .ignore : .map(2*$0) } 34 | .subscribe(observer) 35 | 36 | scheduler.start() 37 | 38 | var correct = values 39 | .filter { $0 % 2 != 0 } 40 | .map { Recorded.next(0, 2 * $0) } 41 | 42 | correct.append(.completed(0)) 43 | 44 | XCTAssertEqual(observer.events, correct) 45 | } 46 | 47 | func testErrorsWithSource() { 48 | let observer = scheduler.createObserver(Int.self) 49 | 50 | let subject = PublishSubject() 51 | _ = subject 52 | .filterMap { $0 % 2 == 0 ? .ignore : .map(2*$0) } 53 | .subscribe(observer) 54 | 55 | subject.on(.next(1)) 56 | subject.on(.next(2)) 57 | subject.on(.next(3)) 58 | subject.on(.error(testError)) 59 | 60 | scheduler.start() 61 | 62 | let correct = Recorded.events([ 63 | .next(0, 2), 64 | .next(0, 6), 65 | .error(0, testError) 66 | ]) 67 | 68 | print(observer.events) 69 | 70 | XCTAssertEqual(observer.events, correct) 71 | } 72 | 73 | func testThrownError() { 74 | // Given 75 | let expectedErrored = 203 76 | let expectedEvents = Recorded.events([ 77 | .next(201, 2), 78 | .error(expectedErrored, testError) 79 | ]) 80 | let source = scheduler.createHotObservable([ 81 | .next(201, 1), 82 | .next(202, 2), 83 | .next(expectedErrored, 3), // should not fire due to error on 3 84 | .next(204, 4), 85 | .completed(205) 86 | ]) 87 | // When 88 | let result = scheduler.start { 89 | source.filterMap { element -> FilterMap in 90 | guard !element.isMultiple(of: 2) else { 91 | return .ignore 92 | } 93 | guard element != 3 else { 94 | throw testError 95 | } 96 | return .map(2 * element) 97 | } 98 | } 99 | // Then 100 | XCTAssertEqual(source.subscriptions, [Subscription(TestScheduler.Defaults.subscribed, expectedErrored)]) 101 | XCTAssertEqual(result.events, expectedEvents) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Tests/RxSwift/fromAsyncTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FromAsyncTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Vincent on 12/08/2017. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | import RxSwift 12 | import RxSwiftExt 13 | import RxTest 14 | 15 | class FromAsyncTests: XCTestCase { 16 | private var scheduler: TestScheduler! 17 | private var observer: TestableObserver! 18 | 19 | override func setUp() { 20 | super.setUp() 21 | scheduler = TestScheduler(initialClock: 0) 22 | observer = scheduler.createObserver(String.self) 23 | } 24 | 25 | override func tearDown() { 26 | scheduler = nil 27 | observer = nil 28 | super.tearDown() 29 | } 30 | 31 | func testResultEquality() { 32 | var correct = [Recorded>]() 33 | 34 | service(arg1: "Foo", arg2: 0) { result in 35 | correct.append(.next(0, result)) 36 | correct.append(.completed(0)) 37 | } 38 | 39 | _ = Observable 40 | .fromAsync(service(arg1:arg2:completionHandler:))("Foo", 2) 41 | .subscribe(observer) 42 | 43 | scheduler.start() 44 | 45 | XCTAssertEqual(observer.events, correct) 46 | } 47 | 48 | func testSingleResultEqualitySuccessCase() { 49 | // given 50 | let result = "result" 51 | let expectedEvents: [Recorded>] = [.next(0, result), .completed(0)] 52 | // when 53 | _ = Single 54 | .fromAsync(serviceWithError)(result) 55 | .asObservable() 56 | .subscribe(observer) 57 | scheduler.start() 58 | // then 59 | XCTAssertEqual(observer.events, expectedEvents) 60 | } 61 | 62 | func testSingleOptionalResultEqualitySuccessCase() { 63 | // given 64 | let result: String? = nil 65 | let expectedEvents: [Recorded>] = [.next(0, result), .completed(0)] 66 | let observer = scheduler.createObserver(Optional.self) 67 | // when 68 | _ = Single 69 | .fromAsync(serviceWithOptionalResult)(result) 70 | .asObservable() 71 | .subscribe(observer) 72 | scheduler.start() 73 | // then 74 | XCTAssertEqual(observer.events, expectedEvents) 75 | } 76 | 77 | func testSingleResultEqualityErrorCase() { 78 | // given 79 | let expectedEvents: [Recorded>] = [.error(0, testError)] 80 | // when 81 | _ = Single 82 | .fromAsync(serviceThrowingError) 83 | .asObservable() 84 | .subscribe(observer) 85 | scheduler.start() 86 | // then 87 | XCTAssertEqual(observer.events, expectedEvents) 88 | } 89 | 90 | private func service(arg1: String, arg2: Int, completionHandler: (String) -> Void) { 91 | completionHandler("Result") 92 | } 93 | 94 | private func serviceWithError(result: String, completionHandler: (String?, TestError?) -> Void) { 95 | completionHandler(result, nil) 96 | } 97 | 98 | private func serviceWithOptionalResult(result: String?, completionHandler: (String??, TestError?) -> Void) { 99 | completionHandler(result, nil) 100 | } 101 | 102 | private func serviceThrowingError(completionHandler: (String?, TestError?) -> Void) { 103 | completionHandler(nil, testError) 104 | } 105 | 106 | private func serviceWithOptionalResult(completionHandler: (String??, TestError?) -> Void) { 107 | completionHandler(.some(nil), nil) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Tests/RxSwift/mapToTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapToTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Marin Todorov on 4/12/16. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | import RxSwift 12 | import RxSwiftExt 13 | import RxTest 14 | 15 | class MapToTests: XCTestCase { 16 | 17 | private let numbers: [Int?] = [1, nil, Int?(3)] 18 | private var observer: TestableObserver! 19 | 20 | override func setUp() { 21 | super.setUp() 22 | 23 | let scheduler = TestScheduler(initialClock: 0) 24 | observer = scheduler.createObserver(String.self) 25 | 26 | _ = Observable.from(numbers) 27 | .mapTo("candy") 28 | .subscribe(observer) 29 | 30 | scheduler.start() 31 | } 32 | 33 | func testReplaceWithResultCount() { 34 | XCTAssertEqual( 35 | observer.events.count - 1, // completed event 36 | numbers.count 37 | ) 38 | } 39 | 40 | func testReplaceWithResultValues() { 41 | // test elements values and type 42 | let correctValues = Recorded.events([ 43 | .next(0, "candy"), 44 | .next(0, "candy"), 45 | .next(0, "candy"), 46 | .completed(0) 47 | ]) 48 | XCTAssertEqual(observer.events, correctValues) 49 | } 50 | } 51 | 52 | // MARK: - Single 53 | extension MapToTests { 54 | func testSingleReplaceSuccess() { 55 | // Given 56 | let expectedValue = "candy" 57 | let scheduler = TestScheduler(initialClock: 0) 58 | // When 59 | let result = scheduler.start { 60 | Single.just(1).mapTo(expectedValue).asObservable() 61 | } 62 | // Then 63 | XCTAssertEqual(result.events, [ 64 | .next(TestScheduler.Defaults.subscribed, expectedValue), 65 | .completed(TestScheduler.Defaults.subscribed) 66 | ]) 67 | } 68 | 69 | func testSingleNoReplaceFailure() { 70 | // Given 71 | let scheduler = TestScheduler(initialClock: 0) 72 | // When 73 | let result = scheduler.start { 74 | Single.error(testError).mapTo("candy").asObservable() 75 | } 76 | // Then 77 | XCTAssertEqual(result.events, [.error(TestScheduler.Defaults.subscribed, testError)]) 78 | } 79 | } 80 | 81 | // MARK: - Maybe 82 | extension MapToTests { 83 | func testMaybeReplaceSuccess() { 84 | // Given 85 | let expectedValue = "candy" 86 | let scheduler = TestScheduler(initialClock: 0) 87 | // When 88 | let result = scheduler.start { 89 | Maybe.just(1).mapTo(expectedValue).asObservable() 90 | } 91 | // Then 92 | XCTAssertEqual(result.events, [ 93 | .next(TestScheduler.Defaults.subscribed, expectedValue), 94 | .completed(TestScheduler.Defaults.subscribed) 95 | ]) 96 | } 97 | 98 | func testMaybeNoReplaceFailure() { 99 | // Given 100 | let scheduler = TestScheduler(initialClock: 0) 101 | // When 102 | let result = scheduler.start { 103 | Maybe.error(testError).mapTo("candy").asObservable() 104 | } 105 | // Then 106 | XCTAssertEqual(result.events, [.error(TestScheduler.Defaults.subscribed, testError)]) 107 | } 108 | 109 | func testMaybeNoReplaceEmpty() { 110 | // Given 111 | let scheduler = TestScheduler(initialClock: 0) 112 | // When 113 | let result = scheduler.start { 114 | Maybe.empty().mapTo("candy").asObservable() 115 | } 116 | // Then 117 | XCTAssertEqual(result.events, [.completed(TestScheduler.Defaults.subscribed)]) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Tests/RxSwift/materialized+elementsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Materialized+elementsTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Adam Borek on 12/04/2017. 6 | // Copyright © 2017 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import RxTest 11 | import RxSwift 12 | import RxSwiftExt 13 | 14 | final class MaterializedElementsTests: XCTestCase { 15 | private var testScheduler: TestScheduler! 16 | private var eventObservable: Observable>! 17 | private let dummyError = NSError(domain: "dummy", code: -102) 18 | private var disposeBag = DisposeBag() 19 | 20 | override func setUp() { 21 | super.setUp() 22 | testScheduler = TestScheduler(initialClock: 0) 23 | eventObservable = testScheduler.createHotObservable([ 24 | .next(0, Event.next(0)), 25 | .next(100, Event.next(1)), 26 | .next(200, Event.error(dummyError)), 27 | .next(300, Event.next(2)), 28 | .next(400, Event.error(dummyError)), 29 | .next(500, Event.next(3)) 30 | ]).asObservable() 31 | } 32 | 33 | override func tearDown() { 34 | super.tearDown() 35 | disposeBag = DisposeBag() 36 | } 37 | 38 | func test_elementsReturnsOnlyNextEvents() { 39 | let observer = testScheduler.createObserver(Int.self) 40 | 41 | eventObservable 42 | .elements() 43 | .subscribe(observer) 44 | .disposed(by: disposeBag) 45 | testScheduler.start() 46 | 47 | XCTAssertEqual(observer.events, [ 48 | .next(0, 0), 49 | .next(100, 1), 50 | .next(300, 2), 51 | .next(500, 3) 52 | ]) 53 | } 54 | 55 | func test_errorsReturnsOnlyErrorEvents() { 56 | let observer = testScheduler.createObserver(Error.self) 57 | 58 | eventObservable 59 | .errors() 60 | .subscribe(observer) 61 | .disposed(by: disposeBag) 62 | testScheduler.start() 63 | 64 | XCTAssertEqual(observer.events.map { $0.time }, [200, 400]) 65 | XCTAssertEqual(observer.events.map { $0.value.element! as NSError }, [dummyError, dummyError]) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Tests/RxSwift/notTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Thane Gill on 10/18/16. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | import RxSwift 12 | import RxSwiftExt 13 | import RxTest 14 | 15 | class NotTests: XCTestCase { 16 | 17 | func testNot() { 18 | let values = [true, false, true] 19 | 20 | let scheduler = TestScheduler(initialClock: 0) 21 | let observer = scheduler.createObserver(Bool.self) 22 | 23 | _ = Observable.from(values) 24 | .not() 25 | .subscribe(observer) 26 | 27 | scheduler.start() 28 | 29 | let correct = Recorded.events([ 30 | .next(0, false), 31 | .next(0, true), 32 | .next(0, false), 33 | .completed(0) 34 | ]) 35 | 36 | XCTAssertEqual(observer.events, correct) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Tests/RxSwift/pausableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PausableTests.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Jesse Farless on 12/09/16. 6 | // Copyright © 2016 RxSwift Community. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | import RxSwift 12 | import RxSwiftExt 13 | import RxTest 14 | 15 | class PausableTests: XCTestCase { 16 | let testError = NSError(domain: "dummyError", code: -232, userInfo: nil) 17 | let scheduler = TestScheduler(initialClock: 0) 18 | 19 | func testPausedNoSkip() { 20 | let underlying = scheduler.createHotObservable([ 21 | .next(150, 1), 22 | .next(210, 2), 23 | .next(230, 3), 24 | .next(301, 4), 25 | .next(350, 5), 26 | .next(399, 6), 27 | .completed(500) 28 | ]) 29 | 30 | let pauser = scheduler.createHotObservable([ 31 | .next(201, true), 32 | .next(205, false), 33 | .next(209, true) 34 | ]) 35 | 36 | let res = scheduler.start(disposed: 1000) { 37 | underlying.pausable(pauser) 38 | } 39 | 40 | XCTAssertEqual(res.events, [ 41 | .next(210, 2), 42 | .next(230, 3), 43 | .next(301, 4), 44 | .next(350, 5), 45 | .next(399, 6), 46 | .completed(500) 47 | ]) 48 | 49 | XCTAssertEqual(underlying.subscriptions, [ 50 | Subscription(200, 500) 51 | ]) 52 | 53 | } 54 | 55 | func testPausedSkips() { 56 | let underlying = scheduler.createHotObservable([ 57 | .next(150, 1), 58 | .next(210, 2), 59 | .next(230, 3), 60 | .next(301, 4), 61 | .next(350, 5), 62 | .next(399, 6), 63 | .completed(500) 64 | ]) 65 | 66 | let pauser = scheduler.createHotObservable([ 67 | .next(220, true), 68 | .next(300, false), 69 | .next(400, true) 70 | ]) 71 | 72 | let res = scheduler.start(disposed: 1000) { 73 | underlying.pausable(pauser) 74 | } 75 | 76 | XCTAssertEqual(res.events, [ 77 | .next(230, 3), 78 | .completed(500) 79 | ]) 80 | 81 | XCTAssertEqual(underlying.subscriptions, [ 82 | Subscription(200, 500) 83 | ]) 84 | 85 | } 86 | 87 | func testPausedError() { 88 | let underlying = scheduler.createHotObservable([ 89 | .next(150, 1), 90 | .next(210, 2), 91 | .error(230, testError), 92 | .next(301, 4), 93 | .next(350, 5), 94 | .next(399, 6), 95 | .completed(500) 96 | ]) 97 | 98 | let pauser = scheduler.createHotObservable([ 99 | .next(201, true), 100 | .next(300, false), 101 | .next(400, true) 102 | ]) 103 | 104 | let res = scheduler.start(disposed: 1000) { 105 | underlying.pausable(pauser) 106 | } 107 | 108 | XCTAssertEqual(res.events, [ 109 | .next(210, 2), 110 | .error(230, testError) 111 | ]) 112 | 113 | XCTAssertEqual(underlying.subscriptions, [ 114 | Subscription(200, 230) 115 | ]) 116 | 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Tests/TestErrors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestErrors.swift 3 | // RxSwiftExt 4 | // 5 | // Created by Anton Nazarov on 25/05/2019. 6 | // Copyright © 2019 RxSwift Community. All rights reserved. 7 | // 8 | 9 | enum TestError: Error { 10 | case dummyError 11 | } 12 | 13 | let testError = TestError.dummyError 14 | -------------------------------------------------------------------------------- /scripts/bootstrap-if-needed.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | RED='\033[1;31m' 6 | GREEN='\033[1;32m' 7 | NC='\033[0m' # No Color 8 | 9 | checksum_file="Carthage/cartSum.txt" 10 | 11 | # Computes current Carthage checksum using 'Cartfile.resolved' file, Swift version and checksum version. 12 | computeChecksum() { 13 | local version="1" 14 | { cat Cartfile.resolved; xcrun swift -version; echo "${version}"; } | md5 15 | } 16 | 17 | # Get previous checksum 18 | mkdir -p "Carthage" 19 | touch "${checksum_file}" 20 | if [ ! -f "${checksum_file}" ]; then 21 | prevSum="null"; 22 | else 23 | prevSum=`cat Carthage/cartSum.txt`; 24 | fi 25 | 26 | # Get checksum 27 | cartSum=`computeChecksum` 28 | 29 | if [ "$prevSum" != "$cartSum" ] || [ ! -d "Carthage/Build/iOS" ]; then 30 | printf "${RED}Dependencies out of date with cache.${NC} Bootstrapping...\n" 31 | rm -rf Carthage 32 | scripts/bootstrap.sh 33 | 34 | echo `computeChecksum` > "${checksum_file}" 35 | 36 | else 37 | printf "${GREEN}Cache up-to-date.${NC} Skipping bootstrap...\n" 38 | fi 39 | -------------------------------------------------------------------------------- /scripts/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | # https://github.com/mapbox/mapbox-navigation-ios/blob/master/scripts/wcarthage.sh 6 | applyXcode12Workaround() { 7 | echo "Applying Xcode 12 workaround..." 8 | 9 | xcconfig=$(mktemp /tmp/static.xcconfig.XXXXXX) 10 | trap 'rm -f "${xcconfig}"' INT TERM HUP EXIT 11 | 12 | # For Xcode 12 make sure EXCLUDED_ARCHS is set to arm architectures otherwise 13 | # the build will fail on lipo due to duplicate architectures. 14 | echo 'EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_simulator__NATIVE_ARCH_64_BIT_x86_64__XCODE_1200 = arm64 arm64e armv7 armv7s armv6 armv8' >> $xcconfig 15 | echo 'EXCLUDED_ARCHS = $(inherited) $(EXCLUDED_ARCHS__EFFECTIVE_PLATFORM_SUFFIX_$(EFFECTIVE_PLATFORM_SUFFIX)__NATIVE_ARCH_64_BIT_$(NATIVE_ARCH_64_BIT)__XCODE_$(XCODE_VERSION_MAJOR))' >> $xcconfig 16 | 17 | export XCODE_XCCONFIG_FILE="${xcconfig}" 18 | echo "Workaround applied. xcconfig here: ${XCODE_XCCONFIG_FILE}" 19 | } 20 | 21 | applyXcode12Workaround 22 | carthage bootstrap 23 | --------------------------------------------------------------------------------