Convert Figma logo to code with AI

RxSwiftCommunity logoRxSwiftExt

A collection of Rx operators & tools not found in the core RxSwift distribution

1,318
213
1,318
16

Top Related Projects

24,320

Reactive Programming in Swift

Cocoa framework and Obj-C dynamism bindings for ReactiveSwift.

4,233

A Swift binding framework

15,085

Network abstraction layer written in Swift.

A library for reactive and unidirectional Swift applications

Quick Overview

RxSwiftExt is a collection of operators and tools that extend the functionality of RxSwift, a popular reactive programming library for Swift. It provides additional operators, convenience methods, and utilities to simplify common tasks and enhance the overall RxSwift development experience.

Pros

  • Adds numerous useful operators not found in the core RxSwift library
  • Simplifies common reactive programming patterns
  • Well-maintained and actively developed by the community
  • Seamlessly integrates with existing RxSwift projects

Cons

  • May increase project complexity for developers new to reactive programming
  • Some operators might have performance overhead in certain scenarios
  • Requires keeping up with updates to both RxSwift and RxSwiftExt
  • Documentation could be more comprehensive for some advanced operators

Code Examples

  1. Using the unwrap operator to handle optionals:
Observable.of(1, nil, 3)
    .unwrap()
    .subscribe(onNext: { print($0) })
    // Prints: 1, 3
  1. Applying the mapAt operator to access dictionary values:
let dict: [String: Int] = ["a": 1, "b": 2, "c": 3]
Observable.just(dict)
    .mapAt("b")
    .subscribe(onNext: { print($0) })
    // Prints: Optional(2)
  1. Using the retry operator with exponential backoff:
someObservable
    .retry(.exponentialDelayed(maxCount: 3, initial: 1.0, multiplier: 2.0))
    .subscribe(onNext: { print($0) })

Getting Started

To use RxSwiftExt in your project:

  1. Add the following to your Podfile:

    pod 'RxSwiftExt'
    
  2. Run pod install in your terminal.

  3. Import RxSwiftExt in your Swift file:

    import RxSwift
    import RxSwiftExt
    
  4. Start using the additional operators and utilities provided by RxSwiftExt in your RxSwift code.

Competitor Comparisons

24,320

Reactive Programming in Swift

Pros of RxSwift

  • Core library with comprehensive reactive programming features
  • Extensive documentation and community support
  • Seamless integration with Apple's frameworks

Cons of RxSwift

  • Steeper learning curve for beginners
  • Larger codebase and potential overhead for smaller projects

Code Comparison

RxSwift:

Observable.from([1, 2, 3, 4, 5])
    .filter { $0 % 2 == 0 }
    .map { $0 * 2 }
    .subscribe(onNext: { print($0) })

RxSwiftExt:

Observable.from([1, 2, 3, 4, 5])
    .filterMap { $0 % 2 == 0 ? $0 * 2 : nil }
    .subscribe(onNext: { print($0) })

Key Differences

  • RxSwift is the core library, while RxSwiftExt is an extension
  • RxSwiftExt provides additional operators and conveniences
  • RxSwiftExt aims to simplify common tasks and reduce boilerplate code

Use Cases

  • RxSwift: Ideal for large-scale projects requiring full reactive programming capabilities
  • RxSwiftExt: Useful for enhancing RxSwift with additional operators and simplifying code

Community and Support

  • RxSwift: Larger community, more frequent updates, and extensive third-party resources
  • RxSwiftExt: Smaller but active community, focused on extending RxSwift functionality

Cocoa framework and Obj-C dynamism bindings for ReactiveSwift.

Pros of ReactiveCocoa

  • More mature and established framework with a longer history in the iOS community
  • Supports both Objective-C and Swift, providing better compatibility with older codebases
  • Offers a wider range of operators and utilities out of the box

Cons of ReactiveCocoa

  • Steeper learning curve due to its more complex architecture and concepts
  • Less frequent updates and potentially slower adoption of new Swift features
  • Smaller community compared to RxSwift ecosystem, which may result in fewer third-party extensions

Code Comparison

ReactiveCocoa:

let disposable = textField.reactive.continuousTextValues
    .throttle(0.3, on: QueueScheduler.main)
    .flatMap(.latest) { text in
        return API.search(text)
    }
    .observe(on: UIScheduler())
    .observe { event in
        // Handle search results
    }

RxSwiftExt:

let disposable = textField.rx.text.orEmpty
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .flatMapLatest { text in
        return API.search(text)
    }
    .observe(on: MainScheduler.instance)
    .subscribe(onNext: { results in
        // Handle search results
    })

Both examples demonstrate similar functionality, but ReactiveCocoa uses slightly different naming conventions and a more explicit scheduler approach. RxSwiftExt, being an extension of RxSwift, follows RxSwift's syntax more closely.

4,233

A Swift binding framework

Pros of Bond

  • More comprehensive data binding framework with a focus on UI
  • Supports SwiftUI in addition to UIKit
  • Provides a reactive collection type (ObservableArray) for easier list management

Cons of Bond

  • Steeper learning curve due to more complex API
  • Less focused on extending RxSwift functionality
  • May introduce more dependencies into your project

Code Comparison

Bond:

let text = Observable<String?>("")
let label = UILabel()
text.bind(to: label.reactive.text)

RxSwiftExt:

let text = BehaviorSubject<String?>("")
let label = UILabel()
text.asDriver(onErrorJustReturn: nil).drive(label.rx.text)

Both libraries extend RxSwift functionality, but Bond provides a more comprehensive data binding solution, while RxSwiftExt focuses on adding utility operators and convenience methods to RxSwift. Bond offers a higher-level abstraction for UI binding, which can simplify some tasks but may also introduce more complexity. RxSwiftExt, on the other hand, stays closer to the core RxSwift concepts and provides targeted extensions to enhance its functionality.

15,085

Network abstraction layer written in Swift.

Pros of Moya

  • Provides a network abstraction layer, simplifying API interactions
  • Supports plugins for easy customization and extension of network requests
  • Offers built-in testing support with stub responses

Cons of Moya

  • Steeper learning curve for developers new to the concept of network abstraction
  • May introduce unnecessary complexity for simple API integrations
  • Requires additional setup and configuration compared to direct URLSession usage

Code Comparison

Moya:

let provider = MoyaProvider<MyAPI>()
provider.request(.userProfile) { result in
    switch result {
    case let .success(response):
        let data = response.data
        // Handle the response data
    case let .failure(error):
        // Handle the error
    }
}

RxSwiftExt:

Observable.just("https://api.example.com/user")
    .map { URL(string: $0)! }
    .flatMap { URLSession.shared.rx.data(request: URLRequest(url: $0)) }
    .subscribe(onNext: { data in
        // Handle the response data
    }, onError: { error in
        // Handle the error
    })

While RxSwiftExt provides useful extensions for RxSwift, it doesn't offer network abstraction like Moya. RxSwiftExt is more focused on enhancing RxSwift functionality, whereas Moya specifically targets network layer abstraction and simplification.

A library for reactive and unidirectional Swift applications

Pros of ReactorKit

  • Provides a clear and structured architecture for building reactive apps
  • Offers better separation of concerns with its unidirectional data flow
  • Includes built-in testing utilities for easier unit testing

Cons of ReactorKit

  • Steeper learning curve for developers new to reactive programming
  • May be overkill for smaller projects or simple applications
  • Requires more boilerplate code compared to RxSwiftExt

Code Comparison

ReactorKit:

struct Reactor: ReactorKit.Reactor {
    enum Action {
        case increment
    }
    enum Mutation {
        case incrementCounter
    }
    struct State {
        var count: Int = 0
    }
    func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        case .increment:
            return Observable.just(.incrementCounter)
        }
    }
}

RxSwiftExt:

let counter = BehaviorRelay<Int>(value: 0)
let incrementButton = UIButton()

incrementButton.rx.tap
    .map { counter.value + 1 }
    .bind(to: counter)
    .disposed(by: disposeBag)

ReactorKit provides a more structured approach with clear separation of actions, mutations, and state, while RxSwiftExt offers a more lightweight and flexible solution for simple reactive programming tasks.

Convert Figma logo designs to code with AI

Visual Copilot

Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot

README

CircleCI pod Carthage compatible

RxSwiftExt

If you're using 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.

Installation

This branch of RxSwiftExt targets Swift 5.x and RxSwift 5.0.0 or later.

  • If you're looking for the Swift 4 version of RxSwiftExt, please use version 3.4.0 of the framework.

CocoaPods

Add to your Podfile:

pod 'RxSwiftExt', '~> 5'

This will install both the RxSwift and RxCocoa extensions. If you're interested in only installing the RxSwift extensions, without the RxCocoa extensions, simply use:

pod 'RxSwiftExt/Core'

Using Swift 4:

pod 'RxSwiftExt', '~> 3'

Carthage

Add this to your Cartfile

github "RxSwiftCommunity/RxSwiftExt"

Operators

RxSwiftExt is all about adding operators and Reactive Extensions to RxSwift!

Operators

These operators are much like the RxSwift & RxCocoa core operators, but provide additional useful abilities to your Rx arsenal.

There are two more available operators for materialize()'d sequences:

Read below for details about each operator.

Reactive Extensions

RxSwift/RxCocoa Reactive Extensions are provided to enhance existing objects and classes from the Apple-ecosystem with Reactive abilities.


Operator details

unwrap

Unwrap optionals and filter out nil values.

  Observable.of(1,2,nil,Int?(4))
    .unwrap()
    .subscribe { print($0) }
next(1)
next(2)
next(4)

ignore

Ignore specific elements.

  Observable.from(["One","Two","Three"])
    .ignore("Two")
    .subscribe { print($0) }
next(One)
next(Three)
completed

ignoreWhen

Ignore elements according to closure.

  Observable<Int>
    .of(1,2,3,4,5,6)
    .ignoreWhen { $0 > 2 && $0 < 6 }
    .subscribe { print($0) }
next(1)
next(2)
next(6)
completed

once

Send a next element exactly once to the first subscriber that takes it. Further subscribers get an empty sequence.

  let obs = Observable.once("Hello world")
  print("First")
  obs.subscribe { print($0) }
  print("Second")
  obs.subscribe { print($0) }
First
next(Hello world)
completed
Second
completed

distinct

Pass elements through only if they were never seen before in the sequence.

Observable.of("a","b","a","c","b","a","d")
    .distinct()
    .subscribe { print($0) }
next(a)
next(b)
next(c)
next(d)
completed

mapTo

Replace every element with the provided value.

Observable.of(1,2,3)
    .mapTo("Nope.")
    .subscribe { print($0) }
next(Nope.)
next(Nope.)
next(Nope.)
completed

mapAt

Transform every element to the value at the provided key path.

struct Person {
    let name: String
}

Observable
    .of(
        Person(name: "Bart"),
        Person(name: "Lisa"),
        Person(name: "Maggie")
    )
    .mapAt(\.name)
    .subscribe { print($0) }
next(Bart)
next(Lisa)
next(Maggie)
completed

not

Negate booleans.

Observable.just(false)
    .not()
    .subscribe { print($0) }
next(true)
completed

and

Verifies that every value emitted is true

Observable.of(true, true)
	.and()
	.subscribe { print($0) }

Observable.of(true, false)
	.and()
	.subscribe { print($0) }

Observable<Bool>.empty()
	.and()
	.subscribe { print($0) }

Returns a Maybe<Bool>:

success(true)
success(false)
completed

cascade

Sequentially cascade through a list of observables, dropping previous subscriptions as soon as an observable further down the list starts emitting elements.

let a = PublishSubject<String>()
let b = PublishSubject<String>()
let c = PublishSubject<String>()
Observable.cascade([a,b,c])
    .subscribe { print($0) }
a.onNext("a:1")
a.onNext("a:2")
b.onNext("b:1")
a.onNext("a:3")
c.onNext("c:1")
a.onNext("a:4")
b.onNext("b:4")
c.onNext("c:2")
next(a:1)
next(a:2)
next(b:1)
next(c:1)
next(c:2)

pairwise

Groups elements emitted by an Observable into arrays, where each array consists of the last 2 consecutive items; similar to a sliding window.

Observable.from([1, 2, 3, 4, 5, 6])
    .pairwise()
    .subscribe { print($0) }
next((1, 2))
next((2, 3))
next((3, 4))
next((4, 5))
next((5, 6))
completed

nwise

Groups elements emitted by an Observable into arrays, where each array consists of the last N consecutive items; similar to a sliding window.

Observable.from([1, 2, 3, 4, 5, 6])
    .nwise(3)
    .subscribe { print($0) }
next([1, 2, 3])
next([2, 3, 4])
next([3, 4, 5])
next([4, 5, 6])
completed

retry

Repeats the source observable sequence using given behavior in case of an error or until it successfully terminated. There are four behaviors with various predicate and delay options: immediate, delayed, exponentialDelayed and customTimerDelayed.

// in case of an error initial delay will be 1 second,
// every next delay will be doubled
// delay formula is: initial * pow(1 + multiplier, Double(currentAttempt - 1)), so multiplier 1.0 means, delay will doubled
_ = sampleObservable.retry(.exponentialDelayed(maxCount: 3, initial: 1.0, multiplier: 1.0), scheduler: delayScheduler)
    .subscribe(onNext: { event in
        print("Receive event: \(event)")
    }, onError: { error in
        print("Receive error: \(error)")
    })
Receive event: First
Receive event: Second
Receive event: First
Receive event: Second
Receive event: First
Receive event: Second
Receive error: fatalError

repeatWithBehavior

Repeats the source observable sequence using given behavior when it completes. This operator takes the same parameters as the retry operator. There are four behaviors with various predicate and delay options: immediate, delayed, exponentialDelayed and customTimerDelayed.

// when the sequence completes initial delay will be 1 second,
// every next delay will be doubled
// delay formula is: initial * pow(1 + multiplier, Double(currentAttempt - 1)), so multiplier 1.0 means, delay will doubled
_ = completingObservable.repeatWithBehavior(.exponentialDelayed(maxCount: 3, initial: 1.0, multiplier: 1.2), scheduler: delayScheduler)
    .subscribe(onNext: { event in
        print("Receive event: \(event)")
})
Receive event: First
Receive event: Second
Receive event: First
Receive event: Second
Receive event: First
Receive event: Second

catchErrorJustComplete

Completes a sequence when an error occurs, dismissing the error condition

let _ = sampleObservable
    .do(onError: { print("Source observable emitted error \($0), ignoring it") })
    .catchErrorJustComplete()
    .subscribe {
        print ("\($0)")
}
next(First)
next(Second)
Source observable emitted error fatalError, ignoring it
completed

pausable

Pauses the elements of the source observable sequence unless the latest element from the second observable sequence is true.

let observable = Observable<Int>.interval(1, scheduler: MainScheduler.instance)

let trueAtThreeSeconds = Observable<Int>.timer(3, scheduler: MainScheduler.instance).map { _ in true }
let falseAtFiveSeconds = Observable<Int>.timer(5, scheduler: MainScheduler.instance).map { _ in false }
let pauser = Observable.of(trueAtThreeSeconds, falseAtFiveSeconds).merge()

let pausedObservable = observable.pausable(pauser)

let _ = pausedObservable
    .subscribe { print($0) }
next(2)
next(3)

More examples are available in the project's Playground.

pausableBuffered

Pauses 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.

Examples are available in the project's Playground.

apply

Apply provides a unified mechanism for applying transformations on Observable sequences, without having to extend ObservableType or repeating your transformations. For additional rationale for this see discussion on github

// An ordinary function that applies some operators to its argument, and returns the resulting Observable
func requestPolicy(_ request: Observable<Void>) -> Observable<Response> {
    return request.retry(maxAttempts)
        .do(onNext: sideEffect)
        .map { Response.success }
        .catchError { error in Observable.just(parseRequestError(error: error)) }

// We can apply the function in the apply operator, which preserves the chaining style of invoking Rx operators
let resilientRequest = request.apply(requestPolicy)

filterMap

A 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:

// keep only even numbers and double them
Observable.of(1,2,3,4,5,6)
	.filterMap { number in
		(number % 2 == 0) ? .ignore : .map(number * 2)
	}

The sequence above keeps even numbers 2, 4, 6 and produces the sequence 4, 8, 12.

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.

let imageResult = _chooseImageButtonPressed.asObservable()
    .flatMap { imageReceiver.image.materialize() }
    .share()

let image = imageResult
    .elements()
    .asDriver(onErrorDriveWith: .never())

let errorMessage = imageResult
    .errors()
    .map(mapErrorMessages)
    .unwrap()
    .asDriver(onErrorDriveWith: .never())

fromAsync

Turns 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.

func someAsynchronousService(arg1: String, arg2: Int, completionHandler:(String) -> Void) {
    // a service that asynchronously calls
	// the given completionHandler
}

let observableService = Observable
    .fromAsync(someAsynchronousService)

observableService("Foo", 0)
    .subscribe(onNext: { (result) in
        print(result)
    })
    .disposed(by: disposeBag)

zip(with:)

Convenience version of Observable.zip(_:). 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.

let first = Observable.from(numbers)
let second = Observable.from(strings)

first.zip(with: second) { i, s in
        s + String(i)
    }.subscribe(onNext: { (result) in
        print(result)
    })
next("a1")
next("b2")
next("c3")

merge(with:)

Convenience version of Observable.merge(_:). Merges elements from the observable sequence with those of a different observable sequences into a single observable sequence.

let oddStream = Observable.of(1, 3, 5)
let evenStream = Observable.of(2, 4, 6)
let otherStream = Observable.of(1, 5, 6)

oddStream.merge(with: evenStream, otherStream)
    .subscribe(onNext: { result in
        print(result)
    })
1 2 1 3 4 5 5 6 6

ofType

The ofType operator filters the elements of an observable sequence, if that is an instance of the supplied type.

Observable.of(NSNumber(value: 1),
                  NSDecimalNumber(string: "2"),
                  NSNumber(value: 3),
                  NSNumber(value: 4),
                  NSDecimalNumber(string: "5"),
                  NSNumber(value: 6))
        .ofType(NSDecimalNumber.self)
        .subscribe { print($0) }
next(2)
next(5)
completed

This example emits 2, 5 (NSDecimalNumber Type).

count

Emits 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.

Observable.from([1, 2, 3, 4, 5, 6])
    .count { $0 % 2 == 0 }
    .subscribe()
next(3)
completed

partition

Partition a stream into two separate streams of elements that match, and don't match, the provided predicate.

let numbers = Observable
        .of(1, 2, 3, 4, 5, 6)

    let (evens, odds) = numbers.partition { $0 % 2 == 0 }

    _ = evens.debug("even").subscribe() // emits 2, 4, 6
    _ = odds.debug("odds").subscribe() // emits 1, 3, 5

bufferWithTrigger

Collects the elements of the source observable, and emits them as an array when the trigger emits.

let observable = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
let signalAtThreeSeconds = Observable<Int>.timer(3, scheduler: MainScheduler.instance).map { _ in () }
let signalAtFiveSeconds = Observable<Int>.timer(5, scheduler: MainScheduler.instance).map { _ in () }
let trigger = Observable.of(signalAtThreeSeconds, signalAtFiveSeconds).merge()
let buffered = observable.bufferWithTrigger(trigger)
buffered.subscribe { print($0) }
// prints next([0, 1, 2]) @ 3, next([3, 4]) @ 5

A live demonstration is available in the Playground.

Reactive Extensions details

UIViewPropertyAnimator.animate

The animate(afterDelay:) operator provides a Completable that triggers the animation upon subscription and completes when the animation ends.

button.rx.tap
    .flatMap {
        animator1.rx.animate()
            .andThen(animator2.rx.animate(afterDelay: 0.15))
            .andThen(animator3.rx.animate(afterDelay: 0.1))
    }

UIViewPropertyAnimator.fractionComplete

The fractionComplete binder provides a reactive way to bind to UIViewPropertyAnimator.fractionComplete.

slider.rx.value.map(CGFloat.init)
    .bind(to: animator.rx.fractionComplete)

UIScrollView.reachedBottom

reachedBottom provides a sequence that emits every time the UIScrollView is scrolled to the bottom, with an optional offset.

tableView.rx.reachedBottom(offset: 40)
            .subscribe { print("Reached bottom") }

License

This library belongs to RxSwift Community.

RxSwiftExt is available under the MIT license. See the LICENSE file for more info.