1.RxSwift purpose -> simplify asynchronous programming framework

1.1 concept

“It expands the observer model. Allows you to freely combine multiple asynchronous events without worrying about threads, synchronization, thread safety, concurrent data, and I/O blocking.”

1.2 What are the main functions

1.2.1 Implement the Target Action method of clicking a button

button.rx.tap
    .subscribe(onNext: {
        print("button Tapped")
    })
    .disposed(by: disposeBag)
    
button.rx.tap
    .bind { [weak self] _ -> Void inself? .openAppPreferences() } .disposed(by: disposeBag)Copy the code

1.2.2 Implement proxy

scrollView.rx.contentOffset
    .subscribe(onNext: { contentOffset in
        print("contentOffset: \(contentOffset)")
    })
    .disposed(by: disposeBag)
Copy the code

1.2.3. Closure callback

URLSession.shared.rx.data(request: URLRequest(url: url))
    .subscribe(onNext: { data in
        print("Data Task Success with count: \(data.count)")
    }, onError: { error in
        print("Data Task Error: \(error)")
    })
    .disposed(by: disposeBag)
Copy the code

1. Inform

NotificationCenter.default.rx
    .notification(.UIApplicationWillEnterForeground)
    .subscribe(onNext: { (notification) in
        print("Application Will Enter Foreground"Disposed (by: disposeBag) You don't need to manage the lifecycle of the observer, so you have more energy to focus on the business logicCopy the code

1.2.5. KVO

user.rx.observe(String.self, #keyPath(User.name))
    .subscribe(onNext: { newValue in
        print("do something with newValue"Prompt (by: disposeBag) The code that implements KVO should be clearer, more concise, and more accurate.Copy the code

1.2.6. Multiple tasks have dependencies

Rx encapsulate interface enum Api {/// obtain a token through the username and password static func token(username: String, password: String) -> Observable<String> { ... } static func userInfo(token: String) -> Observable< userInfo > {... } // Obtain user information api. token(username:"beeth0ven", password: "987654321")
    .flatMapLatest(Api.userInfo)
    .subscribe(onNext: { userInfo in
        print("Succeeded in obtaining user information: \(userInfo)")
    }, onError: { error in
        print("Failed to obtain user information: \(error)"Disposed (by: disposeBag) You don't need to nest too many layers, making your code readable and maintainableCopy the code

1.2.7. Processing results after multiple concurrent tasks are completed

/ / will need two merged into one network request, / / / use enum Rx encapsulated interface Api {/ / / get the teacher's detailed information static func the teacher (teacherId: Int) -> Observable<Teacher> { ... TeacherComments (teacherId: Int) -> Observable<[Comment]> {... teacherComments(teacherId: Int) -> Observable<[Comment]> {... TeacherComments (teacherId: teacherId), teacherComments(teacherId: teacherId), teacherComments(teacherId: teacherId) teacherId) ).subscribe(onNext: { (teacher, comments)in
        print("Success in Obtaining Teacher Information: \(Teacher)")
        print("Get teacher's comments successfully: \(comments.count)")
    }, onError: { error in
        print("Failed to get teacher information or comment: \(error)")
    })
    .disposed(by: disposeBag)
Copy the code

1.3 Advantages of using RxSwift

Reason: So why use RxSwift

  • Compound – Rx is just another word for compound
  • Reuse – because it is easy to compound
  • Clear – because declarations are immutable
  • Easy to use – because it abstracts asynchronous programming, it allows us to unify our code style
  • Stable – because Rx is fully unit tested

1.4 RxSwift principle

Functional programming is a programming paradigm that requires us to pass functions as arguments or return them as return values. Data binding (subscription) relationships:

Data binding sequence that can be listened to ==========> observerCopy the code

The core content

  • Observable – Generates events. Everything is a sequence that describes a sequence of elements generated asynchronously.
  • Observer-response event
  • Operator – Creates a change combination event
  • Disposable – Manages the binding (subscription) life cycle
  • Schedulers – Thread queue allocation

1.4.1 Generating Events

// Observable<String>
let text = usernameOutlet.rx.text.orEmpty.asObservable()

// Observable<Bool>
let passwordValid = text
    // Operator
    .map { $0.characters.count >= minimalUsernameLength }
Copy the code

1.4.2 Observer – Response event, Observer, concrete implementation

// Observer<Bool>
let observer = passwordValidOutlet.rx.isHidden
Copy the code

1.4.3 Canceling the Binding

// Disposable
letDisposable = passwordValid // Scheduler is used to control which thread queue tasks run on. .observeon (mainscheduler.instance).bind(to: observer) // Unbind disposable.dispose() when you exit the pageCopy the code

2. How to create events (sequences)

2.1 Simplest Creation

let numbers: Observable<Int> = Observable.create({ observer -> Disposable inOnNext (2) observer.onnext (3) observer.oncompleted () observer.oncompleted () observer.onnext (1) observer.oncompleted () observer.oncompleted () observer.oncompleted () observer.onnext (3) observer.oncompleted (return Disposables.create()
    })
Copy the code

2.2 the decision tree

• Generate a specific element: just • After a delay: timer • Pull an element from a sequence: FROM • Generate a repeatElement: repeatElement • Custom logic exists: create • Generate on each subscription: Deferred • Emits an element at intervals: interval • After a delay: Timer • An empty sequence with only one completion event: Empty • A sequence with no events generated: Never I want to create an Observable by combining other Observables • any Observable that generates an element emits this element: Merge • Let Observables emit elements one by one, so that the next Observable can emit elements after the last one is sent: When each Observable emits a new element: zip • When any Observable emits a new element: I want to convert Observable elements and then emit them • convert each element directly: map • Convert to another Observable: flatMap • only receive elements generated by the latest transform Observable: FlatMapLatest • Every element transformed Observable produces elements in order: concatMap • Scan based on all traversed elements I want to emit each element after a delay: Delay I want to wrap the generated events as elements and send them • Wrap them as Event<Element> : Materialize • Then unwrap them: Dematerialize I want to ignore all next events and only receive completed and error events. IgnoreElements I want to create a new Observable that adds elements in front of the original sequence: StartWith I want to collect elements from Observables, cache them and issue: buffer I want to split Observables into multiple Observables: window • Common features based on elements: GroupBy I want to only receive specific elements in the Observable • issue unique elements: single I want to re-issue elements from the Observable • Filter elements by criteria: filter • Issue only the first elements: TakeLast • Emit only the NTH element: elementAt • Skip over a few elements • Skip over a few elements that satisfy the criteria: SkipWhile, skipWhileWithIndex • Skips the first elements generated in a period of time until another Observable issues an element: skipUntil • fetches only the first elements that satisfy the rule: Take only the first elements generated in a period of time until another Observable issues an element: takeUntil Sample • Emits those elements for which no new elements were created for a specific period of time after they were created: debounce • Emits new elements until their value changes: distinctUntilChanged • Provides a function to determine whether elements are equal or not: DistinctUntilChanged • Delay the subscription when starting to emit elements: delaySubscription I want to take only the first Observable that produces an element from some Observables: Amb I want to evaluate all Observable elements and apply the aggregation method to each element. After the aggregation method is applied to all elements, issue the result: reduce. Scan I want to convert Observable to some other data structure: as... I want to apply the subscribeOn operator to a Scheduler that listens on observeOn. I want Observable to take action when an event occurs:doI want Observable to issue an error event: error • If no element is generated within the specified time: Timeout I want an Observable to gracefully recover when an error occurs. If no element is generated within a specified time, switch to alternative Observable: timeout. If an error occurs, replace the error with an element: CatchErrorJustReturn switch to alternative Observable: catchError Using I create an Observable that can't generate elements until I tell it to do so: Publish • And, even after the element is generated, issue all elements: replay • And, once all observers cancel the observation, it is released: refCount • Notify it that it is ready to generate elements: connectCopy the code

2.3 Events onNext, onError, onCompleted Events. We call these events events:

public enum Event<Element> {
    caseNext (Element) // The sequence produces a new ElementcaseError (swift.error) // An error occurred while creating the sequence, causing the sequence to terminatecaseCompleted // All elements of the sequence were successfully generated and the entire sequence was completed}Copy the code

2.4. Feature sequence

  • Single // It can either emit a Single element or an error event. Example: HTTP request and then return a reply or error
  • Completable. // Can only generate one completed event, or one error event; State changes are not shared
  • // Emit an element or a completed event or an error event; State changes are not shared
  • Driver // does not generate error events; Be sure to listen on MainScheduler; Shared state changes; AsDriver (onErrorJustReturn: []) converts drive instead of bindTo. The drive method can only be called by the Driver
  • ControlEvent // Is used to describe events generated by UI controls; No error event is generated; Be sure to subscribe at MainScheduler; Must be listening on MainScheduler; Shared state changes

2.4.1 Enumeration of Sigle events

public enum SingleEvent<Element> {
    caseSuccess (Element) // Produces a single ElementcaseError (swift.error) // Generates an error}Copy the code

Create a way

Single<[String: Any]>.create calls the.assingle () method on Observable to convert it to Single

2.4.2 Completable

You only care about the completion of the task, not the return value of the task. It’s kind of like an Observable

2.5 Observer. It listens for an event, and then it needs that event to respond, and the people that respond to that event are observers

For example, the pop-up box is the observer, and it responds to the button clicking event.

2.5.1 Creating an Observer

The most straightforward way to create an observer is to describe the subscribe method of an Observable that responds to an event. The observer is built from closures onNext, onError, and onCompleted.

tap.subscribe(onNext: { [weak self] inself? .showAlert() }, onError: { errorin
    print(Error: \ "(error. LocalizedDescription)")
}, onCompleted: {
    print("Mission accomplished.")})Copy the code

2.5.1 Special Observer

AnyObserver AnyObserver binders have two characteristics: they do not handle error events; Ensure that bindings are executed on the given Scheduler (default MainScheduler)

Print results: network request URLSession. Shared. Rx. The data (request: URLRequest (url: the url). The subscribe (onNext: {datain
        print("Data Task Success with count: \(data.count)")
    }, onError: { error in
        print("Data Task Error: \(error)"Disposed (by: disposeBag)let observer: AnyObserver<Data> = AnyObserver { (event) in
    switch event {
    case .next(let data):
        print("Data Task Success with count: \(data.count)")
    case .error(let error):
        print("Data Task Error: \(error)")
    default:
        break}}Copy the code
Since this observer is a UI observer, it will only handle the next event in response to events, and the UI update needs to be performed on the main thread.let observer: Binder<Bool> = Binder(usernameValidOutlet) { (view, isHidden) in
    view.isHidden = isHidden
}

usernameValid
    .bind(to: observer)
    .disposed(by: disposeBag)
Copy the code

Binder can handle only next events and guarantee that code responding to next events will be executed on the given Scheduler, which uses the default MainScheduler.

Reuse page hiding is a common observer, so all UIViews should provide this observer:

extension Reactive where Base: UIView {
  public var isHidden: Binder<Bool> {
      return Binder(self.base) { view, hidden inview.isHidden = hidden } } } usernameValid .bind(to: usernameValidOutlet.rx.isHidden) .disposed(by: DisposeBag) label current text label.rx.text: Extension ReactivewhereBase: UILabel { public var text: Binder<String? > {return Binder(self.base) { label, text in
          label.text = text
      }
  }
}
Copy the code

2.6 Observable & Observer is both a monitored sequence and an Observer

// as a sequence that can be listened onlet observable = textField.rx.text
observable.subscribe(onNext: { text inShow (text: text)}) // As observerlet observer = textField.rx.text
let text: Observable<String?> = ...
text.bind(to: observer)

Copy the code

Also has the feature of UI controls: state of the switch of the switch, the selected segmentedControl index number, datePicker selected date, UISlider, controlPropertyWithDefaultEvents UIStepper, etc

Other helper classes also have sequences that can be listened to as objects of the observer

> AsyncSubject // Observable generates an event Emit the last element (just the last element) > PublishSubject > ReplaySubject > BehaviorSubject > Variable > ControlPropertyCopy the code

2.6.1 AsyncSubject

After the source Observable emits a complete event, it emits the last element (and only the last element). If the source Observable emits no elements, it emits only a complete event. So AsyncSubject has only one completion event; If the source Observable aborts because it generates an error event,

AsyncSubject

let disposeBag = DisposeBag()
let subject = AsyncSubject<String>()

subject
  .subscribe { print("Subscription: 1 Event:".$0) }
  .disposed(by: disposeBag)

subject.onNext("🐶")
subject.onNext("🐱")
subject.onNext("🐹") subject.oncompleted () Subscription: 1 Event: next(🐹) Subscription: 1 Event: completedCopy the code

2.6.2 ReplaySubject

The PublishSubject will send elements generated after the subscription to the observer, and elements issued before the subscription will not be sent to the observer. If you want the observer to receive all elements, you can create an Observable by using the Create method of an Observable, or by using ReplaySubject.

Observable
error
PublishSubject
error

let disposeBag = DisposeBag()
let subject = PublishSubject<String>()

subject
  .subscribe { print("Subscription: 1 Event:".$0) }
  .disposed(by: disposeBag)

subject.onNext("🐶")
subject.onNext("🐱")

subject
  .subscribe { print("Subscription: 2 Event:".$0) }
  .disposed(by: disposeBag)

subject.onNext("🅰 ️")
subject.onNext("🅱 ️"Subscription: 1 Event: Next (🐶) Subscription: 1 Event: next(🐱) Subscription: 1 Event: Next (🅰️) 2 Event: Next (🅰️) Subscription: 1 Event: Next (🅱️) Subscription: 2 Event: Next (️)Copy the code

2.6.3 ReplaySubject

The ReplaySubject will send all elements to the observer, regardless of when the observer subscribed.

ReplaySubject
onNext
onError
onCompleted

let disposeBag = DisposeBag()
let subject = ReplaySubject<String>.create(bufferSize: 1)

subject
  .subscribe { print("Subscription: 1 Event:".$0) }
  .disposed(by: disposeBag)

subject.onNext("🐶")
subject.onNext("🐱")

subject
  .subscribe { print("Subscription: 2 Event:".$0) }
  .disposed(by: disposeBag)

subject.onNext("🅰 ️")
subject.onNext("🅱 ️"Subscription: 1 Event: Next (🐶) Subscription: 1 Event: next(🐱) Subscription: 2 Event: Next (🐱) 1 Event: Next (🅰️) Subscription: 2 Event: Next (🅰️) Subscription: 1 Event: Next (️) Subscription: 2 Event: Next (async)Copy the code

2.6.4 BehaviorSubject

When the observer subscribles to the BehaviorSubject, it emits the latest element in the source Observable (default element if none exists). The subsequent elements are then sent out.

Observable
error
BehaviorSubject
error

let disposeBag = DisposeBag()
let subject = BehaviorSubject(value: "🔴")

subject
  .subscribe { print("Subscription: 1 Event:".$0) }
  .disposed(by: disposeBag)

subject.onNext("🐶")
subject.onNext("🐱")

subject
  .subscribe { print("Subscription: 2 Event:".$0) }
  .disposed(by: disposeBag)

subject.onNext("🅰 ️")
subject.onNext("🅱 ️")

subject
  .subscribe { print("Subscription: 3 Event:".$0) }
  .disposed(by: disposeBag)

subject.onNext("🍐")
subject.onNext("🍊"Subscription: 1 Event: Next (🔴) Subscription: 1 Event: next(🐶) Subscription: 1 Event: Next (🐱) 2 Event: Next (🐱) Subscription: 1 Event: Next (🅰️) Subscription: 2 Event: Next (🅰 disk) Subscription: 1 Event: Next (🅱️) Subscription: 2 Event: Next (🅱️) Subscription: 3 Event: Next (️) Subscription: 1 Event: Next (🍐) Subscription: 2 Event: Next (🍐) Subscription: 3 Event: Next (🍐) Subscription: 1 Event: Next (🍊) Subscription: 2 Event: Next (🍊) Subscription: 3 Event: Next (🍊)Copy the code

2.6.5 Variable

RxSwift provides Variable, Variable version

Using var:

// In ViewController var model: model? = nil { didSet { updateUI(with: model) } } override funcviewDidLoad() { super.viewDidLoad() model = getModel() } func updateUI(with model: Model?) {... } func getModel() -> Model { ... }Copy the code

Using a Variable:

/ / in the ViewControllerletmodel: Variable<Model? > = Variable(nil) override funcviewDidLoad() {
    super.viewDidLoad()

    model.asObservable()
        .subscribe(onNext: { [weak self] model in
            self?.updateUI(with: model)
        })
        .disposed(by: disposeBag)

    model.value = getModel()
}

func updateUI(with model: Model?) { ... }
func getModel() -> Model { ... }
Copy the code

The first way to use var is quite common: listen for changes to the Model in the ViewController and then refresh the page.

The second use of Variable is unique to RxSwift. Variable provides almost all the functionality of VAR. In addition, a very important feature is that it can be converted to a sequence by calling the asObservable() method. You can then apply operators to this sequence to synthesize other sequences. So, if we declare a Variable that needs Rx support, use Variable

The BehaviorSubject of Variable encapsulates a BehaviorSubject, so it holds the current value and Variable sends the current value to the new observer. It does not raise error events. When Variable is deinit, a completed event is emitted

2.6.6 ControlProperty

ControlProperty is specifically used to describe UI control properties, which have the following characteristics:

  • No error events are generated
  • Be sure to subscribe to MainScheduler
  • Must be listening on MainScheduler
  • Shared state changes

2.7 the operator

The operator

2.7.1 filter – This sequence only emits elements whose temperature is greater than 33 degrees

/ / temperatureletrxTemperature: Observable<Double> = ... // filter operator rxtemperature. filter {temperaturein temperature > 33 }
    .subscribe(onNext: { temperature in
        print("Temperature: temperature")
    })
    .disposed(by: disposeBag)
Copy the code
2.7.2 MAP – Transform to create a new sequence. This sequence converts the original JSON into a Model. This transformation is essentially parsing JSON.

// JSON
letjson: Observable<JSON> = ... Json. Map (model.init). Subscribe (onNext: {Modelin
        print("Get Model: \(Model)")
    })
    .disposed(by: disposeBag)
Copy the code
2.7.3 zip – Pairing. This sequence pairs the elements of the hamburger sequence with those of the French fries sequence to generate a new meal sequence.

/ / HamburgletrxHamburg: Observable<Hamburg> = ... / / French friesletrxFrenchFries: Observable<FrenchFries> = ... Observable.zip(rxHamburg, rxFrenchFries).subscribe(onNext: {(Hamburg, frenchFries))in
        print("Get hamburger: \(Hamburg) and frenchFries: \(frenchFries)")
    })
    .disposed(by: disposeBag)
Copy the code

List of other operators

amb
buffer
catchError
combineLatest
concat
concatMap
connect
create
debounce
debug
deferred
delay
delaySubscription
dematerialize
distinctUntilChanged
doelementAt empty error filter flatMap flatMapLatest from groupBy ignoreElements interval just map merge materialize never  observeOn publish reduce refCount repeatElement replay retry sample scan shareReplay single skip skipUntil skipWhile startWith subscribeOn take takeLast takeUntil takeWhile timeout timer using window withLatestFrom zipCopy the code

2.8 Disposable – A resource that can be removed

var disposeBag = DisposeBag()
textField.rx.text.orEmpty
        .subscribe(onNext: { text in print(text) })
        .disposed(by: self.disposeBag)
Copy the code

DisposeBag and ViewController have the same life cycle. When you exit the page, the ViewController is released, disposeBag is released with it, and the five bindings (subscriptions) here are canceled. This is exactly what we need.

2. Automatically unsubscribe

_ = usernameValid
        .takeUntil(self.rx.deallocated)
        .bind(to: passwordOutlet.rx.isEnabled)
Copy the code

This will cause the subscription to continue until the controller’s Dealloc event is generated.

2.9 Schedulers – Schedulers

Dispatchqueue.global (qos:.userinitiated).async {dispatchqueue.global (qos:.userinitiated).async {let data = try? Data(contentsOf: url)
    DispatchQueue.main.async {
        self.data = data
    }
}
Copy the code

If implemented with RxSwift, it looks something like this:

let rxData: Observable<Data> = ...

rxData
    .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated))
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { [weak self] data inself? .data = data }) .disposed(by: disposeBag)Copy the code

In the above example, the builder function which uses subscribeOn to determine the Data sequence runs on which Scheduler, because it takes a long time to obtain Data, the subscribeOn is switched to the background Scheduler to obtain Data. This prevents the main thread from being blocked. Use observeOn to determine which Scheduler listens on this data sequence by switching to the main thread using the observeOn method to listen on and process the results. A typical example would be to make a network request in the background, parse the data, and refresh the page in the main thread. You can then skip to the background to send the request and parse the data using subscribeOn, and then switch to the main thread to update the page using observeOn.

  • MainScheduler MainScheduler represents the main thread. If you need to perform some UI-related tasks, you need to switch to this Scheduler.

  • Serial DispatchQueue SerialDispatchQueueScheduler SerialDispatchQueueScheduler abstraction. If you need to perform some serial tasks, you can switch to this Scheduler.

  • Parallel DispatchQueue ConcurrentDispatchQueueScheduler ConcurrentDispatchQueueScheduler abstraction. If you need to perform concurrent tasks, you can switch to this Scheduler.

  • OperationQueueScheduler OperationQueueScheduler abstracts NSOperationQueue. It has some characteristics of NSOperationQueue, for example, you can set maxConcurrentOperationCount, to control the execution at the same time the maximum number of concurrent tasks.

2.10 Error Handling – Error Handling

Once an error event is emitted in the sequence, the entire sequence is terminated. RxSwift has two main error handling mechanisms:

  • Retry to try again
  • Catch – recovery

Retry causes a sequence to retry after an error occurs:

// If the request fails, try again immediately. // If the request fails three times, the error will be thrownlet rxJson: Observable<JSON> = ...

rxJson
    .retry(3)
    .subscribe(onNext: { json in
        print("JSON success: \(JSON)")
    }, onError: { error in
        print("Failed to get JSON: \(error)")
    })
    .disposed(by: disposeBag)
Copy the code

The code above is very direct retry(3), which means that when an error occurs, it retries up to three times.

RetryWhen If we need to retry after a delay when an error occurs, we can do this: This operator mainly describes when to retry, and controls the retry timing by the Observable returned in the closure. When it issues an error or completed event, it does not retry and passes the event to subsequent observers.

// If the request fails, wait 5 seconds and try again.letRxJson. RetryWhen {(rxError: Observable<Error>) -> Observable<Int>in
        return Observable.timer(retryDelay, scheduler: MainScheduler.instance)
    }
    .subscribe(...)
    .disposed(by: disposeBag)
Copy the code
// If the request fails, wait 5 seconds and try again. // If the request still fails after four retries, an error will be thrown. If the number of errors is less than four, wait five seconds and try againletMaxRetryCount = 4 // Retry a maximum of 4 timesletRxJson. RetryWhen {(rxError: Observable<Error>) -> Observable<Int>in
        return rxError.flatMapWithIndex { (error, index) -> Observable<Int> in
            guard index < maxRetryCount else {
                return Observable.error(error)
            }
            return Observable<Int>.timer(retryDelay, scheduler: MainScheduler.instance)
        }
    }
    .subscribe(...)
    .disposed(by: disposeBag)
Copy the code

The flatMapWithIndex operator, because it can give us the wrong index number index. This index number is then used to determine whether the maximum number of retries has been exceeded, and if so, an error is thrown. If no, wait 5 seconds and try again.

CatchError – Recover to replace the error with an alternate element or set of alternate elements when it occurs

// Get the data from the network first. If that fails, get the data from the local cacheletrxData: Observable<Data> = ... // Network request dataletcahcedData: Observable<Data> = ... Rxdata.catcherror {_in cahcedData }
   .subscribe(onNext: { date in
       print("Obtain data successfully: \(date.count)")
   })
   .disposed(by: disposeBag)
Copy the code