diff --git a/CHANGELOG.md b/CHANGELOG.md index 33580a81..436f2179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Changelog - removed `flatMapSync` operator - added `apply` for `Completable` and `Maybe` - added `mapTo` for `Single` and `Maybe` +- added `flatScan`, `flatScanFirst` and `flatScanLatest` operators - added SPM support 5.0.0 diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/Index.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/Index.xcplaygroundpage/Contents.swift index 91e405ed..697d8dff 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/Index.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/Index.xcplaygroundpage/Contents.swift @@ -1,10 +1,10 @@ /*: > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExt (playground)` scheme for a simulator target -1. Choose `View > Show Debug Area` + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` */ /*: diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/apply.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/apply.xcplaygroundpage/Contents.swift index 5d376511..4e77e892 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/apply.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/apply.xcplaygroundpage/Contents.swift @@ -1,9 +1,9 @@ /*: > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: - + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target 1. Choose `View > Show Debug Area` */ diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/bufferWithTrigger.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/bufferWithTrigger.xcplaygroundpage/Contents.swift index 041efb90..95b49a78 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/bufferWithTrigger.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/bufferWithTrigger.xcplaygroundpage/Contents.swift @@ -1,6 +1,6 @@ /*: > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: - + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/cascade.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/cascade.xcplaygroundpage/Contents.swift index c306b8f0..42aafe43 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/cascade.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/cascade.xcplaygroundpage/Contents.swift @@ -1,10 +1,10 @@ /*: > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target -1. Choose `View > Show Debug Area` + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` */ //: [Previous](@previous) diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/catchErrorJustComplete.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/catchErrorJustComplete.xcplaygroundpage/Contents.swift index 71dda533..5aab3688 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/catchErrorJustComplete.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/catchErrorJustComplete.xcplaygroundpage/Contents.swift @@ -1,10 +1,10 @@ /*: > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target -1. Choose `View > Show Debug Area` + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` */ //: [Previous](@previous) diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/distinct.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/distinct.xcplaygroundpage/Contents.swift index cb12e1f9..28b3be26 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/distinct.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/distinct.xcplaygroundpage/Contents.swift @@ -1,10 +1,10 @@ /*: > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target -1. Choose `View > Show Debug Area` + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` */ //: [Previous](@previous) diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/filterMap.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/filterMap.xcplaygroundpage/Contents.swift index 4d327f3e..11693998 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/filterMap.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/filterMap.xcplaygroundpage/Contents.swift @@ -1,11 +1,11 @@ /*: -> # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: - -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExt (playground)` scheme for a simulator target -1. Choose `View > Show Debug Area` -*/ + > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: + + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` + */ //: [Previous](@previous) diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/flatScan.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/flatScan.xcplaygroundpage/Contents.swift new file mode 100644 index 00000000..ad4e7658 --- /dev/null +++ b/Playground/RxSwiftExtPlayground.playground/Pages/flatScan.xcplaygroundpage/Contents.swift @@ -0,0 +1,68 @@ +/*: + > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: + + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` + */ + +//: [Previous](@previous) + +import RxSwift +import RxSwiftExt + +/*: +## flatScan() + + Sometimes you need s kind of flatMap that reuse its previous element and accumulate it with the new element. For example a common pattern is to reuse the previous API response to prepare next request with current offset. + */ + +struct ChunckedResource { + let elements: [Int] + let start: Int + let totalCount: Int + + init(elements: [Int] = [], start: Int = 0, totalCount: Int = 0) { + self.elements = elements + self.start = start + self.totalCount = totalCount + } + + func merging(with other: ChunckedResource) -> ChunckedResource { + guard other.start <= elements.count else { + return self + } + return ChunckedResource( + elements: elements[0.. Single { + Single.just( + ChunckedResource( + elements: Array(start ..< min(20, start + count)), + start: start, + totalCount: 20 + ) + ) + } + + Observable.from(0...10) + .flatScan(ChunckedResource()) { previous, _ in + getContent( + start: previous.elements.count, + count: .random(in: 1...3) + ) + .do(onSuccess: { print("+", $0)}) + .map(previous.merging) + } + .takeUntil(.inclusive) { $0.elements.count >= $0.totalCount } + .subscribe(onNext: { print("=", $0, "\n") }) +} + +//: [Next](@next) diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/fromAsync.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/fromAsync.xcplaygroundpage/Contents.swift index 160f4f1a..a31ec0b9 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/fromAsync.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/fromAsync.xcplaygroundpage/Contents.swift @@ -1,11 +1,11 @@ /*: -> # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: + > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExt (playground)` scheme for a simulator target -1. Choose `View > Show Debug Area` -*/ + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` + */ //: [Previous](@previous) import Foundation diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/ignore.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/ignore.xcplaygroundpage/Contents.swift index 59d14c93..d7cf6932 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/ignore.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/ignore.xcplaygroundpage/Contents.swift @@ -1,10 +1,10 @@ /*: > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target -1. Choose `View > Show Debug Area` + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` */ //: [Previous](@previous) diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/mapTo.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/mapTo.xcplaygroundpage/Contents.swift index 63d44f6e..ca2a2d80 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/mapTo.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/mapTo.xcplaygroundpage/Contents.swift @@ -1,10 +1,10 @@ /*: > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target -1. Choose `View > Show Debug Area` + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` */ //: [Previous](@previous) diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/not.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/not.xcplaygroundpage/Contents.swift index 6f07c121..bfe001f8 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/not.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/not.xcplaygroundpage/Contents.swift @@ -1,10 +1,10 @@ /*: > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target -1. Choose `View > Show Debug Area` + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` */ //: [Previous](@previous) diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/ofType.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/ofType.xcplaygroundpage/Contents.swift index a0cc02c8..21e696fd 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/ofType.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/ofType.xcplaygroundpage/Contents.swift @@ -1,10 +1,10 @@ /*: > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target -1. Choose `View > Show Debug Area` + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` */ //: [Previous](@previous) diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/once.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/once.xcplaygroundpage/Contents.swift index c1405ee3..6395fcb7 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/once.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/once.xcplaygroundpage/Contents.swift @@ -1,10 +1,10 @@ /*: > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target -1. Choose `View > Show Debug Area` + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` */ //: [Previous](@previous) diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/pausable.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/pausable.xcplaygroundpage/Contents.swift index 2b6c7239..5980965b 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/pausable.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/pausable.xcplaygroundpage/Contents.swift @@ -1,11 +1,11 @@ /*: -> # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: - -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target -1. Choose `View > Show Debug Area` -*/ + > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: + + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` + */ //: [Previous](@previous) diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/pausableBuffered.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/pausableBuffered.xcplaygroundpage/Contents.swift index 72ecb814..8414832e 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/pausableBuffered.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/pausableBuffered.xcplaygroundpage/Contents.swift @@ -1,11 +1,11 @@ /*: -> # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: - -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target -1. Choose `View > Show Debug Area` -*/ + > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: + + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` + */ //: [Previous](@previous) diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/repeatWithBehavior.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/repeatWithBehavior.xcplaygroundpage/Contents.swift index afa09a30..afc70902 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/repeatWithBehavior.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/repeatWithBehavior.xcplaygroundpage/Contents.swift @@ -1,10 +1,10 @@ /*: > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: - -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target -1. Choose `View > Show Debug Area` + + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` */ //: [Previous](@previous) diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/retryWithBehavior.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/retryWithBehavior.xcplaygroundpage/Contents.swift index 102a1f2c..86884f82 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/retryWithBehavior.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/retryWithBehavior.xcplaygroundpage/Contents.swift @@ -1,11 +1,11 @@ /*: -> # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: - -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target -1. Choose `View > Show Debug Area` -*/ + > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: + + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` + */ //: [Previous](@previous) diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/unwrap.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/unwrap.xcplaygroundpage/Contents.swift index 243c4323..b1af602f 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/unwrap.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/unwrap.xcplaygroundpage/Contents.swift @@ -1,10 +1,10 @@ /*: > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: - -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target -1. Choose `View > Show Debug Area` + + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` */ //: [Previous](@previous) diff --git a/Playground/RxSwiftExtPlayground.playground/Pages/zipWith.xcplaygroundpage/Contents.swift b/Playground/RxSwiftExtPlayground.playground/Pages/zipWith.xcplaygroundpage/Contents.swift index 170f2335..57558e9e 100644 --- a/Playground/RxSwiftExtPlayground.playground/Pages/zipWith.xcplaygroundpage/Contents.swift +++ b/Playground/RxSwiftExtPlayground.playground/Pages/zipWith.xcplaygroundpage/Contents.swift @@ -1,10 +1,10 @@ /*: > # IMPORTANT: To use `RxSwiftExtPlayground.playground`, please: -1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed -1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` -1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target -1. Choose `View > Show Debug Area` + 1. Make sure you have [Carthage](https://github.com/Carthage/Carthage) installed + 1. Fetch Carthage dependencies from shell: `carthage bootstrap --platform ios` + 1. Build scheme `RxSwiftExtPlayground` scheme for a simulator target + 1. Choose `View > Show Debug Area` */ //: [Previous](@previous) diff --git a/Playground/RxSwiftExtPlayground.playground/contents.xcplayground b/Playground/RxSwiftExtPlayground.playground/contents.xcplayground index b686b964..5613bfa3 100644 --- a/Playground/RxSwiftExtPlayground.playground/contents.xcplayground +++ b/Playground/RxSwiftExtPlayground.playground/contents.xcplayground @@ -1,5 +1,5 @@ - + @@ -17,6 +17,7 @@ + @@ -30,5 +31,7 @@ + + \ No newline at end of file diff --git a/Readme.md b/Readme.md index 79b023b8..e7351306 100644 --- a/Readme.md +++ b/Readme.md @@ -70,6 +70,7 @@ These operators are much like the RxSwift & RxCocoa core operators, but provide * [pausableBuffered](#pausablebuffered) * [apply](#apply) * [filterMap](#filtermap) +* [flatScan](#flatscan) * [Observable.fromAsync](#fromasync) * [Observable.zip(with:)](#zipwith) * [Observable.merge(with:)](#mergewith) @@ -463,6 +464,25 @@ Observable.of(1,2,3,4,5,6) The sequence above keeps even numbers 2, 4, 6 and produces the sequence 4, 8, 12. +#### flatScan + +Sometimes you need s kind of `flatMap` that reuse its previous element and accumulate it with the new element. For example a common pattern is to reuse the previous API response to prepare next request with current offset. + +```swift +loadMoreTrigger + .withLatestFrom(searchQuery) + .flatScan(SearchResults()) { previous, query in + searchResults( + for: query, + start: previous.elements.count, + count: 20 + ) + .map(previous.merging) + } +``` + +The sequence above "loads more" search results by making a new search request based on current results count so it can load results by chunks of 20 elements. + #### errors, elements These operators only apply to observable sequences that have been materialized with the `materialize()` operator (from RxSwift core). `errors` returns a sequence of filtered error events, ommitting elements. `elements` returns a sequence of filtered element events, ommitting errors. diff --git a/RxSwiftExt.xcodeproj/project.pbxproj b/RxSwiftExt.xcodeproj/project.pbxproj index 354ebb22..dd76537b 100644 --- a/RxSwiftExt.xcodeproj/project.pbxproj +++ b/RxSwiftExt.xcodeproj/project.pbxproj @@ -133,6 +133,12 @@ 62512CA71F0EB1BD0083A89F /* RxTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62512CA61F0EB1BD0083A89F /* RxTest.framework */; }; 6662395E1E9E0950009BB134 /* Materialized+elementsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6662395D1E9E0950009BB134 /* Materialized+elementsTests.swift */; }; 66C663061EA0ECD9005245C4 /* materialized+elements.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C663051EA0ECD9005245C4 /* materialized+elements.swift */; }; + 757A2E8D2530A579005D29DE /* flatScan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 757A2E8C2530A579005D29DE /* flatScan.swift */; }; + 757A2E8E2530A579005D29DE /* flatScan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 757A2E8C2530A579005D29DE /* flatScan.swift */; }; + 757A2E8F2530A579005D29DE /* flatScan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 757A2E8C2530A579005D29DE /* flatScan.swift */; }; + 757A2E9F2530B14F005D29DE /* FlatScanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 757A2E9E2530B14F005D29DE /* FlatScanTests.swift */; }; + 757A2EA72530B15F005D29DE /* FlatScanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 757A2E9E2530B14F005D29DE /* FlatScanTests.swift */; }; + 757A2EAF2530B15F005D29DE /* FlatScanTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 757A2E9E2530B14F005D29DE /* FlatScanTests.swift */; }; 780CB21520A0ED1C00FD3F39 /* toSortedArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780CB21420A0ED1C00FD3F39 /* toSortedArray.swift */; }; 780CB21620A0ED1C00FD3F39 /* toSortedArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780CB21420A0ED1C00FD3F39 /* toSortedArray.swift */; }; 780CB21720A0ED1C00FD3F39 /* toSortedArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 780CB21420A0ED1C00FD3F39 /* toSortedArray.swift */; }; @@ -357,6 +363,8 @@ 62512CA61F0EB1BD0083A89F /* RxTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxTest.framework; path = Carthage/Build/Mac/RxTest.framework; sourceTree = ""; }; 6662395D1E9E0950009BB134 /* Materialized+elementsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Materialized+elementsTests.swift"; sourceTree = ""; }; 66C663051EA0ECD9005245C4 /* materialized+elements.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "materialized+elements.swift"; path = "Source/RxSwift/materialized+elements.swift"; sourceTree = SOURCE_ROOT; }; + 757A2E8C2530A579005D29DE /* flatScan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = flatScan.swift; sourceTree = ""; }; + 757A2E9E2530B14F005D29DE /* FlatScanTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlatScanTests.swift; sourceTree = ""; }; 780CB21420A0ED1C00FD3F39 /* toSortedArray.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = toSortedArray.swift; sourceTree = ""; }; 780CB21820A0ED3B00FD3F39 /* ToSortedArrayTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToSortedArrayTests.swift; sourceTree = ""; }; 780CB21C20A0EE8300FD3F39 /* MapManyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapManyTests.swift; sourceTree = ""; }; @@ -545,6 +553,7 @@ B69B45482190C27D00F30418 /* count.swift */, 5386079D1E6F334B000361DE /* distinct.swift */, 98309EB01EDF159500BD07D9 /* filterMap.swift */, + 757A2E8C2530A579005D29DE /* flatScan.swift */, BF515CE11F3F371600492640 /* fromAsync.swift */, 5386079E1E6F334B000361DE /* ignore.swift */, 5386079F1E6F334B000361DE /* ignoreErrors.swift */, @@ -584,6 +593,7 @@ B69B454C2190C3BC00F30418 /* CountTests.swift */, 538607BE1E6F367A000361DE /* DistinctTests.swift */, 98309EB21EDF161700BD07D9 /* FilterMapTests.swift */, + 757A2E9E2530B14F005D29DE /* FlatScanTests.swift */, BF515CE31F3F3AC900492640 /* FromAsyncTests.swift */, 538607BF1E6F367A000361DE /* IgnoreErrorsTests.swift */, 538607C01E6F367A000361DE /* IgnoreTests.swift */, @@ -1053,6 +1063,7 @@ B69B45492190C27D00F30418 /* count.swift in Sources */, 538607AD1E6F334B000361DE /* distinct.swift in Sources */, D7C72A421FDC5D8F00EAAAAB /* nwise.swift in Sources */, + 757A2E8D2530A579005D29DE /* flatScan.swift in Sources */, 538607B61E6F334B000361DE /* pausable.swift in Sources */, 780CB21520A0ED1C00FD3F39 /* toSortedArray.swift in Sources */, E62D9D582199D1EF006636D7 /* bufferWithTrigger.swift in Sources */, @@ -1088,6 +1099,7 @@ 538607E81E6F36A9000361DE /* NotTests.swift in Sources */, BF515CE51F3F3AF400492640 /* FromAsyncTests.swift in Sources */, 538607EB1E6F36A9000361DE /* RetryWithBehaviorTests.swift in Sources */, + 757A2E9F2530B14F005D29DE /* FlatScanTests.swift in Sources */, D7C72A3E1FDC5C5D00EAAAAB /* NwiseTests.swift in Sources */, 782485952298A785005CF8CC /* MergeWithTests.swift in Sources */, 8CF5F8AF202D62AB00C1BA97 /* MapAtTests.swift in Sources */, @@ -1144,6 +1156,7 @@ 62512C6E1F0EAF950083A89F /* ignore.swift in Sources */, 3D11958B1FCAD9AE0095134B /* and.swift in Sources */, 62512C781F0EAF950083A89F /* repeatWithBehavior.swift in Sources */, + 757A2E8E2530A579005D29DE /* flatScan.swift in Sources */, 62512C731F0EAF950083A89F /* not.swift in Sources */, D7C72A431FDC5D8F00EAAAAB /* nwise.swift in Sources */, 62512C6D1F0EAF950083A89F /* distinct.swift in Sources */, @@ -1201,6 +1214,7 @@ 62512C921F0EB1850083A89F /* ApplyTests.swift in Sources */, 62512CA21F0EB1850083A89F /* WeakTarget.swift in Sources */, 62512C9D1F0EB1850083A89F /* PausableTests.swift in Sources */, + 757A2EA72530B15F005D29DE /* FlatScanTests.swift in Sources */, 782485B8229952FD005CF8CC /* TestErrors.swift in Sources */, 62512C991F0EB1850083A89F /* MapToTests.swift in Sources */, 3DBDE6001FBBB09A00DF47F9 /* AndTests.swift in Sources */, @@ -1231,6 +1245,7 @@ E39C41DF1F18B08A007F2ACD /* ignore.swift in Sources */, 3D11958C1FCAD9AF0095134B /* and.swift in Sources */, E39C41E91F18B08A007F2ACD /* repeatWithBehavior.swift in Sources */, + 757A2E8F2530A579005D29DE /* flatScan.swift in Sources */, E39C41E41F18B08A007F2ACD /* not.swift in Sources */, D7C72A441FDC5D8F00EAAAAB /* nwise.swift in Sources */, E39C41DE1F18B08A007F2ACD /* distinct.swift in Sources */, @@ -1288,6 +1303,7 @@ E39C420D1F18B13E007F2ACD /* RepeatWithBehaviorTests.swift in Sources */, E39C42091F18B13E007F2ACD /* NotTests.swift in Sources */, E39C42081F18B13E007F2ACD /* Materialized+elementsTests.swift in Sources */, + 757A2EAF2530B15F005D29DE /* FlatScanTests.swift in Sources */, 782485B9229952FD005CF8CC /* TestErrors.swift in Sources */, E39C42071F18B13E007F2ACD /* MapToTests.swift in Sources */, 3DBDE6011FBBB09A00DF47F9 /* AndTests.swift in Sources */, diff --git a/RxSwiftExt.xcworkspace/contents.xcworkspacedata b/RxSwiftExt.xcworkspace/contents.xcworkspacedata index 6368a8cb..2c8cd46e 100644 --- a/RxSwiftExt.xcworkspace/contents.xcworkspacedata +++ b/RxSwiftExt.xcworkspace/contents.xcworkspacedata @@ -1,6 +1,12 @@ + + + + diff --git a/Source/RxSwift/flatScan.swift b/Source/RxSwift/flatScan.swift new file mode 100644 index 00000000..5ce70e3b --- /dev/null +++ b/Source/RxSwift/flatScan.swift @@ -0,0 +1,120 @@ +// +// flatScan.swift +// RxSwiftExt +// +// Created by Jérôme Alves (Datadog) on 09/10/2020. +// Copyright © 2020 RxSwift Community. All rights reserved. +// + +import Foundation +import RxSwift + +extension ObservableType where Element == Void { + + /// Projects each element of an observable sequence to an accumulating observable sequence and merges the resulting observable sequences into one observable sequence. + /// The specified seed value is used as the initial accumulator value. + public func flatScan( + _ seed: Source.Element, + accumulator: @escaping (Source.Element) throws -> Source + ) -> Observable { + flatScan(seed) { seed, _ in try accumulator(seed) } + } + + /// Projects each element of an observable sequence to an accumulating observable sequence and merges the resulting observable sequences into one observable sequence. + /// If element is received while there is some projected observable sequence being merged it will simply be ignored. + /// The specified seed value is used as the initial accumulator value. + public func flatScanFirst( + _ seed: Source.Element, + accumulator: @escaping (Source.Element) throws -> Source + ) -> Observable { + flatScanFirst(seed) { seed, _ in try accumulator(seed) } + } + + /// Projects each element of an observable sequence to an accumulating observable sequence and merges the resulting observable sequences into one observable sequence. + /// If there is some projected observable sequence being merged when element is received it will be cancelled in favor of the new observable sequence. + /// The specified seed value is used as the initial accumulator value. + public func flatScanLatest( + _ seed: Source.Element, + accumulator: @escaping (Source.Element) throws -> Source + ) -> Observable { + flatScanLatest(seed) { seed, _ in try accumulator(seed) } + } +} + +extension ObservableType { + public typealias FlatScanAccumulator = ( + Source.Element, Element + ) throws -> Source + + public typealias AnyFlatMap = ( + _ source: Observable<(Element, Source.Element)>, + _ selector: @escaping (Element, Source.Element) throws -> Source + ) -> Observable + + /// Projects each element of an observable sequence to an accumulating observable sequence and merges the resulting observable sequences into one observable sequence. + /// The specified seed value is used as the initial accumulator value. + public func flatScan( + _ seed: Source.Element, + accumulator: @escaping FlatScanAccumulator + ) -> Observable { + _flatScan(seed, accumulator: accumulator, using: { $0.flatMap($1) }) + } + + /// Projects each element of an observable sequence to an accumulating observable sequence and merges the resulting observable sequences into one observable sequence. + /// If element is received while there is some projected observable sequence being merged it will simply be ignored. + /// The specified seed value is used as the initial accumulator value. + public func flatScanFirst( + _ seed: Source.Element, + accumulator: @escaping FlatScanAccumulator + ) -> Observable { + _flatScan(seed, accumulator: accumulator, using: { $0.flatMapFirst($1) }) + } + + /// Projects each element of an observable sequence to an accumulating observable sequence and merges the resulting observable sequences into one observable sequence. + /// If there is some projected observable sequence being merged when element is received it will be cancelled in favor of the new observable sequence. + /// The specified seed value is used as the initial accumulator value. + public func flatScanLatest( + _ seed: Source.Element, + accumulator: @escaping FlatScanAccumulator + ) -> Observable { + _flatScan(seed, accumulator: accumulator, using: { $0.flatMapLatest($1) }) + } + + private func _flatScan( + _ seed: Source.Element, + accumulator: @escaping FlatScanAccumulator, + using flatMap: @escaping AnyFlatMap + ) -> Observable { + Observable.using({ FlatScanResource(seed: seed) }, observableFactory: { resource -> Observable in + let latest = self.withLatestFrom(resource.asObservable(), resultSelector: { ($0, $1) }) + return flatMap(latest) { element, resource in + try accumulator(resource, element) + }.do(onNext: resource.accept) + } + ) + } +} + +private final class FlatScanResource: Disposable, ObservableConvertibleType { + private let subject: BehaviorSubject + + init(seed: A) { + self.subject = BehaviorSubject(value: seed) + } + + func accept(_ value: A) { + subject.onNext(value) + } + + func asObservable() -> Observable { + subject.asObservable() + } + + func dispose() { + subject.onCompleted() + } + + deinit { + dispose() + } +} diff --git a/Source/RxSwift/withUnretained.swift b/Source/RxSwift/withUnretained.swift index 474e101c..560ecd08 100644 --- a/Source/RxSwift/withUnretained.swift +++ b/Source/RxSwift/withUnretained.swift @@ -17,8 +17,10 @@ extension ObservableType { - parameter resultSelector: A function to combine the unretained referenced on `obj` and the value of the observable sequence. - returns: An observable sequence that contains the result of `resultSelector` being called with an unretained reference on `obj` and the values of the original sequence. */ - public func withUnretained(_ obj: Object, - resultSelector: @escaping ((Object, Element)) -> Out) -> Observable { + public func withUnretained( + _ obj: Object, + resultSelector: @escaping ((Object, Element)) -> Out + ) -> Observable { return map { [weak obj] element -> Out in guard let obj = obj else { throw UnretainedError.failedRetaining } diff --git a/Tests/RxSwift/FlatScanTests.swift b/Tests/RxSwift/FlatScanTests.swift new file mode 100644 index 00000000..6629d366 --- /dev/null +++ b/Tests/RxSwift/FlatScanTests.swift @@ -0,0 +1,199 @@ +// +// FlatScanTests.swift +// RxSwiftExt +// +// Created by Jérôme Alves (Datadog) on 09/10/2020. +// Copyright © 2020 RxSwift Community. All rights reserved. +// + +import XCTest + +import RxSwift +import RxSwiftExt +import RxTest + +// MARK: - Flat Scan + +class FlatScanTests: XCTestCase { + + func test_Empty() { + test( + operator: { $0.flatScan }, + elementsAccumulator: { _, _ in [] }, + expectedEvents: [ + .completed(200 + 200) + ] + ) + } + + func test_Chronologic_Events() { + test( + operator: { $0.flatScan }, + elementsAccumulator: { accumulated, element in [ + .next(10, (accumulated) + (element) + 1), + .next(20, (accumulated) + (element) + 2), + ]}, + expectedEvents: [ + .next(200 + 110, (seed) + (10) + 1), + .next(200 + 120, (seed) + (10) + 2), + .next(200 + 210, (seed + 10 + 2) + (20) + 1), + .next(200 + 220, (seed + 10 + 2) + (20) + 2), + .completed(200 + 220) + ] + ) + } + + func test_Mixed_Events() { + test( + operator: { $0.flatScan }, + elementsAccumulator: { accumulated, element in [ + .next(50, (accumulated) + (element) + 1), + .next(200, (accumulated) + (element) + 2), + ]}, + expectedEvents: [ + // First and Second Observable are mixed + .next(200 + 150, (seed) + (10) + 1), + .next(200 + 250, (seed + 10 + 1) + (20) + 1), + .next(200 + 300, (seed) + (10) + 2), + .next(200 + 400, (seed + 10 + 1) + (20) + 2), + .completed(200 + 400) + ] + ) + } + +} + +// MARK: - Flat Scan First + +class FlatScanFirstTests: XCTestCase { + + func test_Empty() { + test( + operator: { $0.flatScanFirst }, + elementsAccumulator: { _, _ in [] }, + expectedEvents: [ + .completed(200 + 200) + ] + ) + } + + func test_Chronologic_Events() { + test( + operator: { $0.flatScanFirst }, + elementsAccumulator: { accumulated, element in [ + .next(10, (accumulated) + (element) + 1), + .next(20, (accumulated) + (element) + 2), + ]}, + expectedEvents: [ + .next(200 + 110, (seed) + (10) + 1), + .next(200 + 120, (seed) + (10) + 2), + .next(200 + 210, (seed + 10 + 2) + (20) + 1), + .next(200 + 220, (seed + 10 + 2) + (20) + 2), + .completed(200 + 220) + ] + ) + } + + func test_Mixed_Events() { + test( + operator: { $0.flatScanFirst }, + elementsAccumulator: { accumulated, element in [ + .next(50, (accumulated) + (element) + 1), + .next(200, (accumulated) + (element) + 2), + ]}, + expectedEvents: [ + .next(200 + 150, (seed) + (10) + 1), + .next(200 + 300, (seed) + (10) + 2), + // Second Observable is skipped + .completed(200 + 300) + ] + ) + } + +} + +// MARK: - Flat Scan Latest + +class FlatScanLatestTests: XCTestCase { + + func test_Empty() { + test( + operator: { $0.flatScanLatest }, + elementsAccumulator: { _, _ in [] }, + expectedEvents: [ + .completed(200 + 200) + ] + ) + } + + func test_Chronologic_Events() { + test( + operator: { $0.flatScanLatest }, + elementsAccumulator: { accumulated, element in [ + .next(10, (accumulated) + (element) + 1), + .next(20, (accumulated) + (element) + 2), + ]}, + expectedEvents: [ + .next(200 + 110, (seed) + (10) + 1), + .next(200 + 120, (seed) + (10) + 2), + .next(200 + 210, (seed + 10 + 2) + (20) + 1), + .next(200 + 220, (seed + 10 + 2) + (20) + 2), + .completed(200 + 220) + ] + ) + } + + func test_Mixed_Events() { + test( + operator: { $0.flatScanLatest }, + elementsAccumulator: { accumulated, element in [ + .next(50, (accumulated) + (element) + 1), + .next(200, (accumulated) + (element) + 2), + ]}, + expectedEvents: [ + .next(200 + 150, (seed) + (10) + 1), + // First Observable is disposed + .next(200 + 250, (seed + 10 + 1) + (20) + 1), + .next(200 + 400, (seed + 10 + 1) + (20) + 2), + .completed(200 + 400) + ] + ) + } +} + +// MARK: - Abstraction + +private let seed = 42 + +private typealias FlatScan = (_ seed: T, _ accumulator: @escaping (T, T) -> Observable) -> Observable + +private func test( + operator flatScan: @escaping (Observable) -> FlatScan, + elementsAccumulator: @escaping (Int, Int) -> [Recorded>], + expectedEvents: [Recorded>], + file: StaticString = #file, + line: UInt = #line +) { + let scheduler = TestScheduler(initialClock: 0) + + let xs = scheduler.createColdObservable([ + .next(100, 10), + .next(200, 20), + .completed(200) + ]) + + let flatScan = flatScan(xs.asObservable()) + + let result = scheduler.start { + flatScan(seed) { accumulated, element in + var elements = elementsAccumulator(accumulated, element) + if let last = elements.last { + elements.append(.completed(last.time)) + } else { + elements.append(.completed(0)) // Empty + } + return scheduler.createColdObservable(elements).asObservable() + } + } + XCTAssertEqual(result.events, expectedEvents, file: file, line: line) +} diff --git a/Tests/RxSwift/MergeWithTests.swift b/Tests/RxSwift/MergeWithTests.swift index 73b9c06d..2896441b 100644 --- a/Tests/RxSwift/MergeWithTests.swift +++ b/Tests/RxSwift/MergeWithTests.swift @@ -10,7 +10,6 @@ import XCTest import RxSwift import RxTest - class MergeWithTests: XCTestCase { fileprivate func runAndObserve(_ sequence: Observable) -> TestableObserver { let scheduler = TestScheduler(initialClock: 0)