RxSwift

repository·main·Indexed 12 days ago

https://github.com/reactivex/rxswift

A Swift-specific implementation of the Reactive Extensions (Rx) standard. It provides a generic abstraction of computation through the Observable<Element> interface to compose asynchronous operations and data streams. The ecosystem includes RxCocoa for iOS/macOS/watchOS/tvOS development, RxRelay for wrappers around Subjects, and RxTest and RxBlocking for unit testing.

Tokens
20K
Snippets
54
Records
74
Agent score
95%

What's inside RxSwift

  1. Use RxBlocking for unit testing

    main

    RxBlocking provides a set of blocking operators designed to simplify unit testing by allowing you to synchronously wait for observable emissions.

    CRITICAL: Do not use these operators in production applications. They are intended exclusively for testing purposes to avoid complex asynchronous testing setups.

  2. Handle errors in Observables

    main

    RxSwift uses a short-circuit logic for errors: if one sequence terminates with an error, all dependent sequences also terminate.

    To manage errors:

    • Use the catch operator to recover from a failure by providing a new sequence.
    • Use the retry operator to re-subscribe to the sequence if it encounters an error.
    • For UI binding, ensure an Observable cannot fail by using catchErrorJustReturn(value) (note: this completes the underlying sequence) or retry if you need the sequence to continue producing elements.
    // Example of catching an error to return a fallback value
    images = word
        .filter { $0.containsString("important") }
        .flatMap { word in
            return self.api.loadFlickrFeed("karate")
                .catchError { error in
                    return just(JSON(1))
                }
          }
  3. How to switch execution contexts using observeOn and subscribeOn

    main

    Schedulers abstract the mechanism for performing work (e.g., threads, dispatch queues, or run loops). To control which scheduler performs specific tasks, use two primary operators:

    1. observeOn(scheduler): Used to change the scheduler for all subsequent operators in the chain. This is the most common way to switch contexts (e.g., moving from a background thread to the main thread for UI updates).
    2. subscribeOn(scheduler): Used to specify which scheduler should be used to initiate sequence generation (the subscribe method) and to call dispose.

    If no scheduler is explicitly specified, work is performed on the current thread/scheduler where elements are generated or where the subscription/disposal was initiated.

    sequence1
      .observeOn(backgroundScheduler)
      .map { n in
          print("This is performed on the background scheduler")
      }
      .observeOn(MainScheduler.instance)
      .map { n in
          print("This is performed on the main scheduler")
      }
  4. Share subscriptions using the `share` operator

    main

    By default, every subscriber to an Observable generates its own separate sequence of elements (stateless behavior). If you want multiple observers to share the same underlying subscription and receive the same events, use the share operator.

    To implement sharing, you must define:

    1. Replay behavior: How to handle elements received before a new subscriber joins (e.g., replay(1) to replay the latest element).
    2. Subscription trigger: When to fire the shared subscription (e.g., refCount() to manage subscription based on the number of active observers).

    A common pattern for sharing is replay(1).refCount(), which is equivalent to share(replay: 1).

    let counter = myInterval(.milliseconds(100))
        .share(replay: 1)
    
    // Now, subscription1 and subscription2 share the same underlying timer/sequence
    let subscription1 = counter.subscribe(onNext: { n in print("First \(n)") })
    let subscription2 = counter.subscribe(onNext: { n in print("Second \(n)") })
  5. Understand the duality between Observer and Iterator patterns

    main

    RxSwift is built on the duality between two ways of accessing sequences: the Push interface and the Pull interface.

    • Push interface (Observer pattern): Elements are pushed to the observer as they occur over time. This is the foundation of an Observable sequence. For example, observing mouse cursor positions over time forms an observable sequence where each new position is 'pushed' to you.
    • Pull interface (Iterator / Enumerator / Generator): The consumer requests the next element from the sequence. This is the standard way to handle synchronous sequences.

    This duality is what allows RxSwift to bridge the gap between asynchronous callback-based worlds and the synchronous world of sequence transformations.

  6. Compare RxSwift with ReactiveSwift

    main

    While ReactiveSwift borrows many concepts from Rx, RxSwift differs in its architectural approach and concurrency model:

    • Abstraction Model: RxSwift provides environment-agnostic compositional glue via observable sequences. Platform-specific semantics (like Driver, Signal, ControlProperty, or ControlEvent in RxCocoa) are built on top of these sequences, ensuring all abstractions are composable using the same fundamental interface.
    • Concurrency: RxSwift offers a fine-tunable concurrency model that supports both concurrent and serial schedulers. Operators are designed to detect and optimally use serial schedulers. ReactiveSwift's concurrency model is more limited, primarily allowing serial schedulers.
    • Fault Tolerance: RxSwift operators are built to be fault-tolerant regarding recursion. If element generation occurs during element processing, operators attempt to handle the situation to prevent deadlocks. In extreme cases of programming errors, the library aims to trigger a stack overflow (resulting in a crash report) rather than a silent deadlock or requiring a manual app kill.
  7. Understand the difference between Hot and Cold Observables

    main

    In RxSwift, both hot and cold observables are represented by the same Observable abstraction. The distinction lies in when the sequence begins emitting items and how resources are managed:

    Cold Observables

    • Behavior: Wait until an observer subscribes before they begin emitting items. An observer is guaranteed to see the entire sequence from the beginning.
    • Resource Management: Resources (like computation or connections) are typically allocated per subscriber. They do not use resources until someone is listening.
    • Common Examples: Async operations, HTTP connections, TCP connections, and data streams.
    • Characteristics: Usually stateless and often emit a single complete sequence.

    Hot Observables

    • Behavior: May begin emitting items as soon as they are created. An observer subscribing later might only see elements from the middle of the sequence.
    • Resource Management: Resources are used regardless of whether there are active subscribers. Computation resources are usually shared among all current subscribers.
    • Common Examples: Variables, properties, constants, tap coordinates, mouse coordinates, UI control values, and current time.
    • Characteristics: Usually stateful and often contain a finite or continuous set of elements.
  8. What are Traits and how do they work?

    main

    Traits (formerly known as Units) are wrapper structs that wrap a single read-only Observable sequence. They use Swift's type system to communicate specific observable properties across interface boundaries, providing semantic meaning and syntactical sugar for specific use cases.

    Key characteristics:

    • Optionality: Traits are entirely optional; you can use raw Observable sequences everywhere as all core APIs support them.
    • Implementation: They are essentially a builder pattern. You can transform any Trait back into a vanilla observable sequence by calling .asObservable().
    • Termination: Most RxSwift traits (Single, Completable, Maybe) are designed to terminate after their specific event (success, completion, or error).
    struct Single<Element> {
        let source: Observable<Element>
    }
    
    struct Driver<Element> {
        let source: Observable<Element>
    }
  9. Manage transient state in complex async flows

    main

    Rx handles 'transient state' (like managing pending requests, debouncing input, or handling errors during a sequence) without requiring manual flags or extra variables.

    For example, in an autocomplete search box, you can combine throttle, distinctUntilChanged, and flatMapLatest to automatically cancel pending requests when new input arrives and manage the loading/error states.

    searchTextField.rx.text
        .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
        .distinctUntilChanged()
        .flatMapLatest { query in
            API.getSearchResults(query)
                .retry(3)
                .startWith([]) // clears results on new search term
                .catchErrorJustReturn([])
        }
        .subscribe(onNext: { results in
          // bind to ui
        })
        .disposed(by: disposeBag)
  10. Understand implicit Observable guarantees

    main

    RxSwift guarantees that Observer callbacks are executed serially. Even if elements are produced on different threads, a producer cannot send a new .next event until the previous observer.on(.next(element)) call has finished execution. Similarly, a terminating .completed or .error event cannot be sent until the final .next event processing has finished. This ensures that event processing remains predictable and avoids race conditions within the observer's logic.

    ```swift
    someObservable
      .subscribe { (e: Event<Element>) in
          print("Event processing started")
          // processing
          print("Event processing ended")
      }

    // Guaranteed output order: // Event processing started // Event processing ended // Event processing started // Event processing ended

  11. Use Relays instead of Subjects to avoid termination

    main

    In RxSwift, Relay types are specialized versions of Subjects designed for scenarios where you want to ensure the stream never terminates. While standard Subjects can receive .completed or .error events which terminate the sequence, Relays are guaranteed to only emit .next events.

    RxRelay provides three types of Relays that mirror the behavior of their corresponding Subjects:

    • PublishRelay (mirrors PublishSubject)
    • BehaviorRelay (mirrors BehaviorSubject)
    • ReplayRelay (mirrors ReplaySubject)

    Use a Relay when you need a long-lived stream that should never stop emitting values due to an error or completion event.

  12. Understand the Observable lifecycle and subscription

    main

    An Observable (or observable sequence) defines how a sequence is generated and what parameters are used, but it does not perform any work or cause side effects upon creation.

    Sequence generation and side effects (like network requests or mouse events) only begin when the subscribe method is called. This is a fundamental concept: calling a method that returns an Observable is a lazy operation.

    func searchWikipedia(searchTerm: String) -> Observable<Results> {}
    
    let searchForMe = searchWikipedia("me") // No work is performed here
    
    let cancel = searchForMe
      .subscribe(onNext: { results in // Sequence generation starts here
          print(results)
      })