{"id":13719114,"url":"https://github.com/RxSwiftCommunity/RxSwiftExt","last_synced_at":"2025-05-07T11:31:02.051Z","repository":{"id":6913653,"uuid":"55688297","full_name":"RxSwiftCommunity/RxSwiftExt","owner":"RxSwiftCommunity","description":"A collection of Rx operators \u0026 tools not found in the core RxSwift distribution","archived":false,"fork":false,"pushed_at":"2023-09-20T03:20:20.000Z","size":851,"stargazers_count":1328,"open_issues_count":16,"forks_count":213,"subscribers_count":47,"default_branch":"main","last_synced_at":"2024-11-09T07:39:30.602Z","etag":null,"topics":["reactive-programming","rxswift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RxSwiftCommunity.png","metadata":{"files":{"readme":"Readme.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-04-07T11:12:37.000Z","updated_at":"2024-11-08T15:19:33.000Z","dependencies_parsed_at":"2024-06-18T11:08:35.871Z","dependency_job_id":"359deb32-b928-48c7-84dd-113dd6aea84d","html_url":"https://github.com/RxSwiftCommunity/RxSwiftExt","commit_stats":{"total_commits":420,"total_committers":59,"mean_commits":7.11864406779661,"dds":0.7119047619047619,"last_synced_commit":"eb4adf9f00a21b3efc3869a5218a6d7517e95222"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxSwiftExt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxSwiftExt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxSwiftExt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RxSwiftCommunity%2FRxSwiftExt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RxSwiftCommunity","download_url":"https://codeload.github.com/RxSwiftCommunity/RxSwiftExt/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224120323,"owners_count":17259047,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["reactive-programming","rxswift"],"created_at":"2024-08-03T01:00:42.678Z","updated_at":"2024-11-14T08:31:17.072Z","avatar_url":"https://github.com/RxSwiftCommunity.png","language":"Swift","readme":"[![CircleCI](https://img.shields.io/circleci/project/github/RxSwiftCommunity/RxSwiftExt/main.svg)](https://circleci.com/gh/RxSwiftCommunity/RxSwiftExt/tree/main)\n![pod](https://img.shields.io/cocoapods/v/RxSwiftExt.svg)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n\nRxSwiftExt\n===========\n\nIf you're using [RxSwift](https://github.com/ReactiveX/RxSwift), you may have encountered situations where the built-in operators do not bring the exact functionality you want. The RxSwift core is being intentionally kept as compact as possible to avoid bloat. This repository's purpose is to provide additional convenience operators and Reactive Extensions.\n\nInstallation\n===========\n\nThis branch of RxSwiftExt targets Swift 5.x and RxSwift 5.0.0 or later.\n\n* If you're looking for the Swift 4 version of RxSwiftExt, please use version `3.4.0` of the framework.\n\n#### CocoaPods\n\nAdd to your `Podfile`:\n\n```ruby\npod 'RxSwiftExt', '~\u003e 5'\n```\n\nThis will install both the `RxSwift` and `RxCocoa` extensions.\nIf you're interested in only installing the `RxSwift` extensions, without the `RxCocoa` extensions, simply use:\n\n```ruby\npod 'RxSwiftExt/Core'\n```\n\nUsing Swift 4:\n\n```ruby\npod 'RxSwiftExt', '~\u003e 3'\n```\n\n#### Carthage\n\nAdd this to your `Cartfile`\n\n```\ngithub \"RxSwiftCommunity/RxSwiftExt\"\n```\n\nOperators\n===========\n\nRxSwiftExt is all about adding operators and Reactive Extensions to [RxSwift](https://github.com/ReactiveX/RxSwift)!\n\n## Operators\n\nThese operators are much like the RxSwift \u0026 RxCocoa core operators, but provide additional useful abilities to your Rx arsenal.\n\n* [unwrap](#unwrap)\n* [ignore](#ignore)\n* [ignoreWhen](#ignorewhen)\n* [Observable.once](#once)\n* [distinct](#distinct)\n* [map](#map)\n* [not](#not)\n* [and](#and)\n* [Observable.cascade](#cascade)\n* [pairwise](#pairwise)\n* [nwise](#nwise)\n* [retry](#retry)\n* [repeatWithBehavior](#repeatwithbehavior)\n* [catchErrorJustComplete](#catcherrorjustcomplete)\n* [pausable](#pausable)\n* [pausableBuffered](#pausablebuffered)\n* [apply](#apply)\n* [filterMap](#filtermap)\n* [Observable.fromAsync](#fromasync)\n* [Observable.zip(with:)](#zipwith)\n* [Observable.merge(with:)](#mergewith)\n* [count](#count)\n* [partition](#partition)\n* [bufferWithTrigger](#bufferWithTrigger)\n\nThere are two more available operators for `materialize()`'d sequences:\n\n* [errors](#errors-elements)\n* [elements](#errors-elements)\n\nRead below for details about each operator.\n\n## Reactive Extensions\n\nRxSwift/RxCocoa Reactive Extensions are provided to enhance existing objects and classes from the Apple-ecosystem with Reactive abilities.\n\n* [UIViewPropertyAnimator.animate](#uiviewpropertyanimatoranimate)\n* [UIScrollView.reachedBottom](#uiscrollviewreachedbottom)\n\n--------\n\nOperator details\n===========\n\n#### unwrap\n\nUnwrap optionals and filter out nil values.\n\n```swift\n  Observable.of(1,2,nil,Int?(4))\n    .unwrap()\n    .subscribe { print($0) }\n```\n\n```\nnext(1)\nnext(2)\nnext(4)\n```\n\n#### ignore\n\nIgnore specific elements.\n\n```swift\n  Observable.from([\"One\",\"Two\",\"Three\"])\n    .ignore(\"Two\")\n    .subscribe { print($0) }\n```\n\n```\nnext(One)\nnext(Three)\ncompleted\n```\n\n#### ignoreWhen\n\nIgnore elements according to closure.\n\n```swift\n  Observable\u003cInt\u003e\n    .of(1,2,3,4,5,6)\n    .ignoreWhen { $0 \u003e 2 \u0026\u0026 $0 \u003c 6 }\n    .subscribe { print($0) }\n```\n```\nnext(1)\nnext(2)\nnext(6)\ncompleted\n```\n\n#### once\n\nSend a next element exactly once to the first subscriber that takes it. Further subscribers get an empty sequence.\n\n```swift\n  let obs = Observable.once(\"Hello world\")\n  print(\"First\")\n  obs.subscribe { print($0) }\n  print(\"Second\")\n  obs.subscribe { print($0) }\n```\n```\nFirst\nnext(Hello world)\ncompleted\nSecond\ncompleted\n```\n\n#### distinct\n\nPass elements through only if they were never seen before in the sequence.\n\n```swift\nObservable.of(\"a\",\"b\",\"a\",\"c\",\"b\",\"a\",\"d\")\n    .distinct()\n    .subscribe { print($0) }\n```\n```\nnext(a)\nnext(b)\nnext(c)\nnext(d)\ncompleted\n```\n\n#### mapTo\n\nReplace every element with the provided value.\n\n```swift\nObservable.of(1,2,3)\n    .mapTo(\"Nope.\")\n    .subscribe { print($0) }\n```\n```\nnext(Nope.)\nnext(Nope.)\nnext(Nope.)\ncompleted\n```\n\n#### mapAt\n\nTransform every element to the value at the provided key path.\n\n```swift\nstruct Person {\n    let name: String\n}\n\nObservable\n    .of(\n        Person(name: \"Bart\"),\n        Person(name: \"Lisa\"),\n        Person(name: \"Maggie\")\n    )\n    .mapAt(\\.name)\n    .subscribe { print($0) }\n```\n```\nnext(Bart)\nnext(Lisa)\nnext(Maggie)\ncompleted\n```\n#### not\n\nNegate booleans.\n\n```swift\nObservable.just(false)\n    .not()\n    .subscribe { print($0) }\n```\n\n```\nnext(true)\ncompleted\n```\n\n#### and\n\nVerifies that every value emitted is `true`\n\n```swift\nObservable.of(true, true)\n\t.and()\n\t.subscribe { print($0) }\n\nObservable.of(true, false)\n\t.and()\n\t.subscribe { print($0) }\n\nObservable\u003cBool\u003e.empty()\n\t.and()\n\t.subscribe { print($0) }\n```\n\nReturns a `Maybe\u003cBool\u003e`:\n\n```\nsuccess(true)\nsuccess(false)\ncompleted\n```\n\n#### cascade\n\nSequentially cascade through a list of observables, dropping previous subscriptions as soon as an observable further down the list starts emitting elements.\n\n```swift\nlet a = PublishSubject\u003cString\u003e()\nlet b = PublishSubject\u003cString\u003e()\nlet c = PublishSubject\u003cString\u003e()\nObservable.cascade([a,b,c])\n    .subscribe { print($0) }\na.onNext(\"a:1\")\na.onNext(\"a:2\")\nb.onNext(\"b:1\")\na.onNext(\"a:3\")\nc.onNext(\"c:1\")\na.onNext(\"a:4\")\nb.onNext(\"b:4\")\nc.onNext(\"c:2\")\n```\n\n```\nnext(a:1)\nnext(a:2)\nnext(b:1)\nnext(c:1)\nnext(c:2)\n```\n\n#### pairwise\n\nGroups elements emitted by an Observable into arrays, where each array consists of the last 2 consecutive items; similar to a sliding window.\n\n```swift\nObservable.from([1, 2, 3, 4, 5, 6])\n    .pairwise()\n    .subscribe { print($0) }\n```\n\n```\nnext((1, 2))\nnext((2, 3))\nnext((3, 4))\nnext((4, 5))\nnext((5, 6))\ncompleted\n```\n\n#### nwise\n\nGroups elements emitted by an Observable into arrays, where each array consists of the last N consecutive items; similar to a sliding window.\n\n```swift\nObservable.from([1, 2, 3, 4, 5, 6])\n    .nwise(3)\n    .subscribe { print($0) }\n```\n\n```\nnext([1, 2, 3])\nnext([2, 3, 4])\nnext([3, 4, 5])\nnext([4, 5, 6])\ncompleted\n```\n\n#### retry\n\nRepeats the source observable sequence using given behavior in case of an error or until it successfully terminated.\nThere are four behaviors with various predicate and delay options: `immediate`, `delayed`, `exponentialDelayed` and\n`customTimerDelayed`.\n\n```swift\n// in case of an error initial delay will be 1 second,\n// every next delay will be doubled\n// delay formula is: initial * pow(1 + multiplier, Double(currentAttempt - 1)), so multiplier 1.0 means, delay will doubled\n_ = sampleObservable.retry(.exponentialDelayed(maxCount: 3, initial: 1.0, multiplier: 1.0), scheduler: delayScheduler)\n    .subscribe(onNext: { event in\n        print(\"Receive event: \\(event)\")\n    }, onError: { error in\n        print(\"Receive error: \\(error)\")\n    })\n```\n\n```\nReceive event: First\nReceive event: Second\nReceive event: First\nReceive event: Second\nReceive event: First\nReceive event: Second\nReceive error: fatalError\n```\n\n#### repeatWithBehavior\n\nRepeats the source observable sequence using given behavior when it completes. This operator takes the same parameters as the [retry](#retry) operator.\nThere are four behaviors with various predicate and delay options: `immediate`, `delayed`, `exponentialDelayed` and `customTimerDelayed`.\n\n```swift\n// when the sequence completes initial delay will be 1 second,\n// every next delay will be doubled\n// delay formula is: initial * pow(1 + multiplier, Double(currentAttempt - 1)), so multiplier 1.0 means, delay will doubled\n_ = completingObservable.repeatWithBehavior(.exponentialDelayed(maxCount: 3, initial: 1.0, multiplier: 1.2), scheduler: delayScheduler)\n    .subscribe(onNext: { event in\n        print(\"Receive event: \\(event)\")\n})\n```\n\n```\nReceive event: First\nReceive event: Second\nReceive event: First\nReceive event: Second\nReceive event: First\nReceive event: Second\n```\n\n#### catchErrorJustComplete\n\nCompletes a sequence when an error occurs, dismissing the error condition\n\n```swift\nlet _ = sampleObservable\n    .do(onError: { print(\"Source observable emitted error \\($0), ignoring it\") })\n    .catchErrorJustComplete()\n    .subscribe {\n        print (\"\\($0)\")\n}\n```\n\n```\nnext(First)\nnext(Second)\nSource observable emitted error fatalError, ignoring it\ncompleted\n```\n\n#### pausable\n\nPauses the elements of the source observable sequence unless the latest element from the second observable sequence is `true`.\n\n```swift\nlet observable = Observable\u003cInt\u003e.interval(1, scheduler: MainScheduler.instance)\n\nlet trueAtThreeSeconds = Observable\u003cInt\u003e.timer(3, scheduler: MainScheduler.instance).map { _ in true }\nlet falseAtFiveSeconds = Observable\u003cInt\u003e.timer(5, scheduler: MainScheduler.instance).map { _ in false }\nlet pauser = Observable.of(trueAtThreeSeconds, falseAtFiveSeconds).merge()\n\nlet pausedObservable = observable.pausable(pauser)\n\nlet _ = pausedObservable\n    .subscribe { print($0) }\n```\n\n```\nnext(2)\nnext(3)\n```\n\nMore examples are available in the project's Playground.\n\n#### pausableBuffered\n\nPauses the elements of the source observable sequence unless the latest element from the second observable sequence is `true`. Elements emitted by the source observable are buffered (with a configurable limit) and \"flushed\" (re-emitted) when the observable resumes.\n\nExamples are available in the project's Playground.\n\n#### apply\n\nApply provides a unified mechanism for applying transformations on Observable\nsequences, without having to extend ObservableType or repeating your\ntransformations. For additional rationale for this see\n[discussion on github](https://github.com/RxSwiftCommunity/RxSwiftExt/issues/73)\n\n```swift\n// An ordinary function that applies some operators to its argument, and returns the resulting Observable\nfunc requestPolicy(_ request: Observable\u003cVoid\u003e) -\u003e Observable\u003cResponse\u003e {\n    return request.retry(maxAttempts)\n        .do(onNext: sideEffect)\n        .map { Response.success }\n        .catchError { error in Observable.just(parseRequestError(error: error)) }\n\n// We can apply the function in the apply operator, which preserves the chaining style of invoking Rx operators\nlet resilientRequest = request.apply(requestPolicy)\n```\n\n#### filterMap\n\nA common pattern in Rx is to filter out some values, then map the remaining ones to something else. `filterMap` allows you to do this in one step:\n\n```swift\n// keep only even numbers and double them\nObservable.of(1,2,3,4,5,6)\n\t.filterMap { number in\n\t\t(number % 2 == 0) ? .ignore : .map(number * 2)\n\t}\n```\n\nThe sequence above keeps even numbers 2, 4, 6 and produces the sequence 4, 8, 12.\n\n#### errors, elements\n\nThese 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.\n\n```swift\nlet imageResult = _chooseImageButtonPressed.asObservable()\n    .flatMap { imageReceiver.image.materialize() }\n    .share()\n\nlet image = imageResult\n    .elements()\n    .asDriver(onErrorDriveWith: .never())\n\nlet errorMessage = imageResult\n    .errors()\n    .map(mapErrorMessages)\n    .unwrap()\n    .asDriver(onErrorDriveWith: .never())\n```\n\n#### fromAsync\n\nTurns simple asynchronous completion handlers into observable sequences. Suitable for use with existing asynchronous services which call a completion handler with only one parameter. Emits the result produced by the completion handler then completes.\n\n```swift\nfunc someAsynchronousService(arg1: String, arg2: Int, completionHandler:(String) -\u003e Void) {\n    // a service that asynchronously calls\n\t// the given completionHandler\n}\n\nlet observableService = Observable\n    .fromAsync(someAsynchronousService)\n\nobservableService(\"Foo\", 0)\n    .subscribe(onNext: { (result) in\n        print(result)\n    })\n    .disposed(by: disposeBag)\n```\n\n#### zip(with:)\n\nConvenience version of `Observable.zip(_:)`. Merges the specified observable sequences into one observable sequence by using the selector function whenever all\n of the observable sequences have produced an element at a corresponding index.\n\n```swift\nlet first = Observable.from(numbers)\nlet second = Observable.from(strings)\n\nfirst.zip(with: second) { i, s in\n        s + String(i)\n    }.subscribe(onNext: { (result) in\n        print(result)\n    })\n```\n\n```\nnext(\"a1\")\nnext(\"b2\")\nnext(\"c3\")\n```\n\n#### merge(with:)\n\nConvenience version of `Observable.merge(_:)`. Merges elements from the observable sequence with those of a different observable sequences into a single observable sequence.\n\n```swift\nlet oddStream = Observable.of(1, 3, 5)\nlet evenStream = Observable.of(2, 4, 6)\nlet otherStream = Observable.of(1, 5, 6)\n\noddStream.merge(with: evenStream, otherStream)\n    .subscribe(onNext: { result in\n        print(result)\n    })\n```\n\n```\n1 2 1 3 4 5 5 6 6\n```\n\n#### ofType\n\nThe ofType operator filters the elements of an observable sequence, if that is an instance of the supplied type.\n\n```swift\nObservable.of(NSNumber(value: 1),\n                  NSDecimalNumber(string: \"2\"),\n                  NSNumber(value: 3),\n                  NSNumber(value: 4),\n                  NSDecimalNumber(string: \"5\"),\n                  NSNumber(value: 6))\n        .ofType(NSDecimalNumber.self)\n        .subscribe { print($0) }\n```\n```\nnext(2)\nnext(5)\ncompleted\n```\nThis example emits 2, 5 (`NSDecimalNumber` Type).\n\n#### [count](http://reactivex.io/documentation/operators/count.html)\n\nEmits the number of items emitted by an Observable once it terminates with no errors. If a predicate is given, only elements matching the predicate will be counted.\n\n```swift\nObservable.from([1, 2, 3, 4, 5, 6])\n    .count { $0 % 2 == 0 }\n    .subscribe()\n```\n\n```\nnext(3)\ncompleted\n```\n\n#### partition\n\nPartition a stream into two separate streams of elements that match, and don't match, the provided predicate.\n\n```swift\nlet numbers = Observable\n        .of(1, 2, 3, 4, 5, 6)\n\n    let (evens, odds) = numbers.partition { $0 % 2 == 0 }\n\n    _ = evens.debug(\"even\").subscribe() // emits 2, 4, 6\n    _ = odds.debug(\"odds\").subscribe() // emits 1, 3, 5\n```\n\n#### bufferWithTrigger\nCollects the elements of the source observable, and emits them as an array when the trigger emits.\n\n```swift\nlet observable = Observable\u003cInt\u003e.interval(1, scheduler: MainScheduler.instance)\nlet signalAtThreeSeconds = Observable\u003cInt\u003e.timer(3, scheduler: MainScheduler.instance).map { _ in () }\nlet signalAtFiveSeconds = Observable\u003cInt\u003e.timer(5, scheduler: MainScheduler.instance).map { _ in () }\nlet trigger = Observable.of(signalAtThreeSeconds, signalAtFiveSeconds).merge()\nlet buffered = observable.bufferWithTrigger(trigger)\nbuffered.subscribe { print($0) }\n// prints next([0, 1, 2]) @ 3, next([3, 4]) @ 5\n```\n\nA live demonstration is available in the Playground.\n\nReactive Extensions details\n===========\n\n#### UIViewPropertyAnimator.animate\n\nThe `animate(afterDelay:)` operator provides a Completable that triggers the animation upon subscription and completes when the animation ends.\n\n```swift\nbutton.rx.tap\n    .flatMap {\n        animator1.rx.animate()\n            .andThen(animator2.rx.animate(afterDelay: 0.15))\n            .andThen(animator3.rx.animate(afterDelay: 0.1))\n    }\n```\n\n#### UIViewPropertyAnimator.fractionComplete\n\nThe `fractionComplete` binder provides a reactive way to bind to `UIViewPropertyAnimator.fractionComplete`.\n\n```swift\nslider.rx.value.map(CGFloat.init)\n    .bind(to: animator.rx.fractionComplete)\n```\n\n#### UIScrollView.reachedBottom\n\n`reachedBottom` provides a sequence that emits every time the `UIScrollView` is scrolled to the bottom, with an optional offset.\n\n```swift\ntableView.rx.reachedBottom(offset: 40)\n            .subscribe { print(\"Reached bottom\") }\n```\n\n## License\n\nThis library belongs to _RxSwift Community_.\n\nRxSwiftExt is available under the MIT license. See the LICENSE file for more info.\n","funding_links":[],"categories":["Libraries"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRxSwiftCommunity%2FRxSwiftExt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRxSwiftCommunity%2FRxSwiftExt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRxSwiftCommunity%2FRxSwiftExt/lists"}