We know that there are many high-order functions in Swift, which are very easy to use and efficient, such as map,fliter, flatMap and so on. See my previous article on higher-order functions for Swift for more details

Swift Book information download: download address

1. Higher-order functions

1.1 Mathematical and Aggregate Operators

1.1.1 toArray

  • This operator converts a sequence into an array, sends it as a single event, and then terminates.

  • Instance 1.1.1
let disposeBag = DisposeBag()
 
Observable.of(1, 2, 3)
    .toArray()
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)
Copy the code

Running results:

1.1.2 the reduce

  • Basic introduction
  1. Reduce takes an initial value and an operation symbol.
  2. Reduce aggregates a given initial value over each value in the sequence. Get a final result and send it as a single value.

  • Instance 1.1.2
let disposeBag = DisposeBag()
 
Observable.of(1, 2, 3, 4, 5)
    .reduce(0, accumulator: +)
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)
Copy the code

Running results:

15

1.1.3 concat

  • Basic introduction
  1. Concat merges (concatenates) multiple Observables into one Observable.
  2. And the next Observable starts sending events only after the last One emits a Completed event.

  • Instance 1.1.3
let disposeBag = DisposeBag()
 
let subject1 = BehaviorSubject(value: 1)
let subject2 = BehaviorSubject(value: 2)
 
let variable = Variable(subject1)
variable.asObservable()
    .concat()
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)
 
subject2.onNext(2)
subject1.onNext(1)
subject1.onNext(1)
subject1.onCompleted()
 
variable.value = subject2
subject2.onNext(2)
Copy the code

Running results:

1.2 Connection operators: connect, publish, replay, and multicast

1.2.1 Connectable sequences

  • What are concatenable sequences:

Connectable Observable: (1) A Connectable Observable differs from a common one in that it does not immediately send event messages when a subscription is made, and only sends values when connect() is called. (2) The connectable sequence allows all subscribers to subscribe before sending event messages, thus ensuring that all subscribers we want can receive event messages.

  • Before demonstrating a concatenable sequence, let’s look at an example of a normal sequence:
  • Instance 1.2.1
/ / every 1 seconds to send an event let interval = observables < Int >. The interval (1, the scheduler: Mainscheduler.instance) // First subscriber (start subscription immediately) _ = interval.subscribe (onNext: {print(" subscribe 1: \ ($0) ")}) / / second subscriber delay (5 seconds began to subscribe) delay (5) {_ = interval. The subscribe (onNext: {print (" subscription 2: \ ($0) ")})}Copy the code
  • For ease of use, here we define a deferred execution method:
Delays execution / / / / / / - Parameters: / / / - delay, delay time (in seconds) / / / - closure: delay the closure of public func delay (_ delay: Double, closure: @escaping () -> Void) { DispatchQueue.main.asyncAfter(deadline: .now() + delay) { closure() } }Copy the code
  • The result is as follows. You can see that the first subscriber receives a value every one second after subscribing. The second subscriber receives the first value 0 5 seconds later, so the values received by the two subscribers are out of sync.

1.2.2 the publish

  • publishMethod converts a normal sequence into a concatenable sequence. Also, the sequence does not send events immediately, only when calledconnectIt will start later.

  • 1.2.2 instance:

We modified example 1.2.1 above with the following code:

/ / every 1 seconds to send an event let interval = observables < Int >. The interval (1, the scheduler: Mainscheduler.instance).publish() _ = interval.subscribe (onNext: {print(" subscribe 1: \($0)")}) // Delay (2) {_ = interval.connect()} // delay(5) {_ = interval .subscribe(onNext: {print(" subscribe 2: \($0)")})}Copy the code

Running results:

1.2.3 replay

  • Basic introduction
  1. replayDitto belowpublishThe method is the same in that it converts a normal sequence into a concatenable sequence. Also, the sequence does not send events immediately, only when called connectIt will start later.
  2. replaypublishThe difference is that the new subscriber also receives the event messages before the subscription (the number is determined by the bufferSize set).

  • Instance 1.2.3
/ / every 1 seconds to send an event let interval = observables < Int >. The interval (1, the scheduler: Mainscheduler.instance).replay(5) // First subscriber (immediately subscribe) _ = interval.subscribe (onNext: {print(" subscribe 1: \($0)")}) // Delay (2) {_ = interval.connect()} // delay(5) {_ = interval .subscribe(onNext: {print(" subscribe 2: \($0)")})}Copy the code

Running results:

1. Multicast

  • Basic introduction
  1. multicastThe method is also to convert a normal sequence into a concatenable sequence.
  2. At the same timemulticastThe method can also pass in oneSubjectThis is emitted whenever the sequence sends an eventSubjectTo send.

  • Example 1:
// Create a Subject (in the multicast() method below) let Subject = PublishSubject<Int>() // This Subject's subscription _ = subject.subscribe (onNext: {print("Subject: \($0)")}) // Send an event every 1 second let interval = Observable<Int>. Mainscheduler.instance). Multicast (subject) // first subscriber (start subscription now) _ = interval.subscribe (onNext: {print(" subscribe 1: \($0)")}) // Delay (2) {_ = interval.connect()} // delay(5) {_ = interval .subscribe(onNext: {print(" subscribe 2: \($0)")})}Copy the code

Running results:

1.2.5 refCount

  • Basic introduction
  1. refCountOperators can be concatenableObservableConvert to ordinary Observable
  2. That is, the operator can automatically connect and disconnect the connectable Observable. When the first observer is connected to theObservableWhen subscribing, then the underlyingObservableWill be automatically connected. When the last observer leaves, then the bottomObservableWill be automatically disconnected.

  • Instance 1.2.5
/ / every 1 seconds to send an event let interval = observables < Int >. The interval (1, the scheduler: Mainscheduler.instance).publish().refcount () _ = interval.subscribe (onNext: {print(" subscribe 1: \ ($0) ")}) / / second subscriber delay (5 seconds began to subscribe) delay (5) {_ = interval. The subscribe (onNext: {print (" subscription 2: \ ($0) ")})}Copy the code

Running results:

1.2.6 share (replay:)

  • Basic introduction
  1. This operator causes observers to share the source ObservableAnd cache the latestnElement, which is sent directly to the new observer.
  2. In simple termsshareReplayisreplayrefCountThe combination of.
  • Instance 1.2.6
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController {override func viewDidLoad() {// Send an event every 1 second. Let interval = Observable<Int>. Mainscheduler.instance).share(replay: 5) // First subscriber (immediately subscribe) _ = interval.subscribe (onNext: {print(" subscribe 1: \($0)")}) delay(5) {_ = interval. subscribe(onNext: {print(" subscribe 2: \($0)")})}} // -parameters: // -parameters: // -parameters: // -parameters: // -parameters: // -parameters: // -parameters: // -parameters: // -parameters: // -parameters: // -parameters: // -parameters: // -closure: public func delay(_ delay: Double, closure: @escaping () -> Void) { DispatchQueue.main.asyncAfter(deadline: .now() + delay) { closure() } }Copy the code

Running results:

1.3 Other operators: Delay, materialize, timeout, etc

1.3.1 delay

  • The operator will setObservableAll of the elements are delayed for a set amount of time before they are sent.

  • Instance 1.3.1
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() override func viewDidLoad() { Observable.of(1, 2, 1) .delay(3, Scheduler: mainScheduler.instance) // Element delay 3 seconds. Subscribe (onNext: {print($0)}).Copy the code

Running results:

1

2

1

1.3.2 delaySubscription

  • Use this operator to make deferred subscriptions. That is, after the set time, just rightObservableSubscribe.

  • Instance 1.3.2
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() override func viewDidLoad() { Observable.of(1, 2, DelaySubscription (3, scheduler: mainScheduler.instance) // Delay 3 seconds before subscribe.subscribe (onNext: {print($0)}). disposeBag) } }Copy the code

Running results:

1

2

1

1.3.3 materialize

  • Basic introduction
  1. This operator converts events generated by a sequence into elements.

    Usually a limited numberObservableZero or more will be generated onNextThe event,
  2. And you end up with a onCompletedoronErrorEvents. whilematerializeThe operator will set ObservableThese generated events are all converted into elements and sent out.

  • Instance 1.3.3
import UIKit
import RxSwift
import RxCocoa
 
class ViewController: UIViewController {
     
    let disposeBag = DisposeBag()
     
    override func viewDidLoad() {
         
        Observable.of(1, 2, 1)
            .materialize()
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
    }
}
Copy the code

Running results:

1.3.4 dematerialize

  • The value of the operator andmaterializeOn the contrary, it canmaterializeElement reduction after transformation.

  • Instance 1.3.4
import UIKit
import RxSwift
import RxCocoa
 
class ViewController: UIViewController {
     
    let disposeBag = DisposeBag()
     
    override func viewDidLoad() {
         
        Observable.of(1, 2, 1)
            .materialize()
            .dematerialize()
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
    }
}
Copy the code

Running results:

1

2

1

1.3.5 the timeout

  • Use this operator to set a timeout. If the sourceObservableA timeout occurs when no element is emitted within the specified timeerrorEvents.

  • Instance 1.3.5
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController {let disposeBag = disposeBag () override func viewDidLoad() {let times = [[ "Value" : 1, "time" : 0], [" value ": 2," time ": 0.5], [3," value ":" the time ": 1.5], [" value" : 4, "time" : 4 ], [ "value": 5, "time": From (times).flatmap {item in return Observable.of(Int(item["value"]!) ) .delaySubscription(Double(item["time"]!) , scheduler: MainScheduler.instance) } .timeout(2, scheduler: Mainscheduler.instance).subscribe(onNext: {element in print(element)}, onError: { error in print(error) }) .disposed(by: disposeBag) } }Copy the code

Results of running J:

1.3.6 using

  • useusingOperator creationObservableAt the same time a resource will be created that can be purged onceObservableTerminate, and the resource will be cleared.

  • Instance 1.3.6
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController {override func viewDidLoad() {let infiniteInterval$= Observable<Int> .interval(0.1, scheduler: mainScheduler.instance).do(onNext: {print("infinite$: \($0)")}, onSubscribe: {print(" start subscribed infinite$")}, onDispose: {print(" destroy infinite$")}) // Select * from the scheduler where Observable<Int>. MainScheduler.instance) .take(2) .do( onNext: { print("limited$: \($0)") }, onSubscribe: Dispose: {print(" start subscribe limited$")}, onDispose: {print(" destroy limited$")}) Observable<Int> = Observable.using({ () -> AnyDisposable in return AnyDisposable(infiniteInterval$.subscribe()) }, observableFactory: { _ in return limited$ } ) o.subscribe() } } class AnyDisposable: Disposable { let _dispose: () -> Void init(_ disposable: Disposable) { _dispose = disposable.dispose } func dispose() { _dispose() } }Copy the code

Running results:

1.4 Error Handling

1.4.1 catchErrorJustReturn

  • When faced witherrorEvent, returns the specified value, and terminates.
  • Instance 1.4.1:
let disposeBag = DisposeBag()
 
let sequenceThatFails = PublishSubject<String>()
 
sequenceThatFails
    .catchErrorJustReturn("错误")
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)
 
sequenceThatFails.onNext("a")
sequenceThatFails.onNext("b")
sequenceThatFails.onNext("c")
sequenceThatFails.onError(MyError.A)
sequenceThatFails.onNext("d")
Copy the code

Running results:

1.4.2 catchError

  • Basic introduction
  1. This method catches errors and processes them.
  2. It also returns another Observable sequence to subscribe to (switching to a new one).

  • Instance 1.4.2:
let disposeBag = DisposeBag() let sequenceThatFails = PublishSubject<String>() let recoverySequence = Observable.of("1",  "2", "3") sequenceThatFails .catchError { print("Error:", $0) return recoverySequence } .subscribe(onNext: { print($0) }) .disposed(by: disposeBag) sequenceThatFails.onNext("a") sequenceThatFails.onNext("b") sequenceThatFails.onNext("c") sequenceThatFails.onError(MyError.A) sequenceThatFails.onNext("d")Copy the code

Running results:

1.4.3 retry

  • Basic introduction
  1. Using this method, the sequence is re-subscribed when an error is encountered. For example, if a network request fails, you can reconnect.
  2. retry()Method can be passed a number indicating the number of retries. If not, it will only retry once.

  • Instance 1.4.3
let disposeBag = DisposeBag() var count = 1 let sequenceThatErrors = Observable<String>.create { observer in Observer.onnext ("a") observer.onnext ("b") print("Error ") if count == 1 {observer.onerror (MyError encountered") count += 1 } observer.onNext("c") observer.onNext("d") observer.onCompleted() return Disposables.create() Sequencethaterrors. retry(2) // Retry 2 times. Subscribe (onNext: {print($0)}). Disposed (by: disposeBag)Copy the code

Running results:

1.5 Debugging Operations

1.5.1 the debug

  • We can putdebugThe debug operator is added to a chain of steps so that the system can print out all subscriber, event, and processing details for debugging.
  • Instance 1.5.1
let disposeBag = DisposeBag()
 
Observable.of("2", "3")
    .startWith("1")
    .debug()
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)
Copy the code

Running results:

  • The debug() method can also pass in marker parameters, making it easy to distinguish multiple Debugs in a project.
  • Instance 1.5.1.1
Let disposeBag = disposeBag () Observable. Of ("2", "3").startwith ("1").debug(" debug 1").subscribe(onNext: { print($0) }) .disposed(by: disposeBag)Copy the code

Running results:

1.5.2 RxSwift. Resources. The total

  • By putting theRxSwift.Resources.totalPrint it out and we can view the currentRxSwiftThe number of all resources requested. This is useful when checking for memory leaks.
  • Instance 1.5.2
print(RxSwift.Resources.total)
         
let disposeBag = DisposeBag()
 
print(RxSwift.Resources.total)
         
Observable.of("BBB", "CCC")
    .startWith("AAA")
    .subscribe(onNext: { print($0) })
    .disposed(by: disposeBag)
         
print(RxSwift.Resources.total)
Copy the code

The results

1.6 Feature sequence 1: Single, Completable, Maybe

  • In addition to Observable, RxSwift also provides us with some feature sequences (Traits) :Single,Completable,Maybe,Driver,ControlEvent.

1.6.1 Single

  • Basic introduction
  1. Single is another version of Observable. Unlike an Observable, which emits multiple elements, it either emits one element or an error event.
  2. Raises an element, or an error event
  3. State changes are not shared
  4. A common example is to perform an HTTP request and then return a reply or error. But we can also use Single to describe any sequence that has only one element.
  • SingleEvent: is an enumeration

Success: contains an element value for the Single. Error: Is used to contain errors

public enum SingleEvent<Element> {
    case success(Element)
    case error(Swift.Error)
}
Copy the code
  • Example 1.6.1: (1) Creating a Single is very similar to creating an Observable. Here we define a function that generates the network request Single:
Func getPlaylist(_ channel: String) -> Single<[String: Any]> {return Single<[String: Any] Any]>.create { single in let url = "https://douban.fm/j/mine/playlist?" + "type=n&channel=\(channel)&from=mainsite" let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, _, error in if let error = error { single(.error(error)) return } guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves), let result = json as? [String: Any] else { single(.error(DataError.cantParseJSON)) return } single(.success(result)) } task.resume() return Disposables. Create {task.cancel()}}} // Datarelated Error type enum DataError: Error {case cantParseJSON}Copy the code

(2) We can then use the Single as follows:

import UIKit import RxSwift import RxCocoa class ViewController: UIViewController {let disposeBag = disposeBag () override func viewDidLoad() {getPlaylist("0"); .subscribe {event in switch event {case.success (let json): print(" json result: ", json) case.error (let error): Prompt (" Prompt: ", error)}}. Disposed (by: disposeBag)}}Copy the code

Subscribe (onSuccess:onError:)

import UIKit import RxSwift import RxCocoa class ViewController: UIViewController {let disposeBag = disposeBag () override func viewDidLoad() {getPlaylist("0"); Prompt (onSuccess: {json in print(" json result: ", json)}, onError: {error in print(" error: ", error)}). Subscribe (by: disposeBag) } }Copy the code

(4) The running results are as follows:

1.6.2 asSingle ()

  • We can do this by callingObservableThe sequence of.asSingle()Method to convert it toSingle.
let disposeBag = DisposeBag()
 
Observable.of("1")
    .asSingle()
    .subscribe({ print($0) })
    .disposed(by: disposeBag)
Copy the code

Running results:

1.6.3 Completable

  • Basic introduction:
  1. CompletableObservableAnother version of “. Don’t likeObservableMultiple elements can be emitted, or it can only produce onecompletedEvent, or produce oneerrorEvents.
  2. No elements are emitted
  3. Only one Completed event or one error event is emitted
  4. State changes are not shared
  • Application Scenarios:

Completable is similar to Observable

. This is useful for cases where you only care about the completion of the task, not the return value of the task. For example, some data is cached to a local file when the program exits for loading at next startup. In cases like this we only care if the cache is successful.

  • CompletableEvent:

For ease of use, RxSwift provides an enumeration (CompletableEvent) for Completable subscriptions:

  1. .completed: Used to generate a completion event
  2. .error: Used to generate an error

    public enum CompletableEvent { case error(Swift.Error) case completed }
  • Instance 1.6.3:

(1) Creating a Completable is very similar to creating an Observable. In the following code we use Completable to simulate a data cache local operation:

Func cacheLocally() -> Completable {return Completable. Create {Completable in Let success = (arc4random() % 2 == 0) Guard success else {Completable (. Error (CacheError. FailedCaching)) return Create {}} Completable (.completed) return Disposables. Create {}}} // Cache-related errors type enum CacheError: Error { case failedCaching }Copy the code

(2) Then we can use the Completable as follows:

CacheLocally (). Subscribe {Completable in switch Completable {case. Completed: print(" Saved successfully!" ) case. The error (let the error) : print (" save failed: \ (error. LocalizedDescription) ")}}. Disposed (by: disposeBag)Copy the code

Subscribe (onCompleted:onError:)

CacheLocally (). Subscribe (onCompleted: {print(" Save successfully!" )}, onError: {the error in print (" save failed: \ (error. LocalizedDescription) ")}) disposed (by: disposeBag)Copy the code

(4) The operating results are as follows (failure) :

1.6.4 Maybe

  • Basic introduction
  1. MaybeThe same isObservableAnother version of “. It is betweenSingle and CompletableIn between, it can either emit one element or produce one completedEvent, or produce one errorEvents.
  • Application scenarios

Maybe is good for situations where an element may or may not need to be emitted.

  • MaybeEvent

For ease of use, RxSwift provides an enumeration (MaybeEvent) for the Maybe subscription:.success: contains an element value for the MaybeEvent. Completed: Generates the completion event. Error: Public enum MaybeEvent

{case success(Element) case error(swift.error) case completed} public enum MaybeEvent

{case success(Element) case error(Swift.

Example 1.6.4 (1) Creating a Maybe is very similar to creating an Observable:

Func generateString() -> Maybe<String> {return Maybe<String>. Create {Maybe in // succeed and issue an element Maybe (. Success (" hangge.com ")) / / success but maybe don't emit any elements (. Completed) / / / / maybe failure (. Error (StringError. FailedGenerate)) return Create {}}} // Cache related Error type enum StringError: Error {case failedGenerate}Copy the code

(2) Then we can use Maybe as follows:

generateString() .subscribe { maybe in switch maybe { case .success(let element): Print (" Completed with element: \(element)") case. Completed: print(" Completed with no element." ) case. The error (let the error) : print (" failure: \ (error. LocalizedDescription) ")}}. Disposed (by: disposeBag)Copy the code

(3) can also use the subscribe (onSuccess: onCompleted: onError:) this way:

GenerateString ().subscribe(onSuccess: {element in print(" execute and get element: \(element)")}, onError: {error in print(" execute failed: \ [error. LocalizedDescription) ")}, onCompleted: {print (" is performed, and no element." ) }) .disposed(by: disposeBag)Copy the code

Running results:

1.6.5 asMaybe ()

– (1) Convert it to Maybe by calling the.asmaybe () method of Observable.

let disposeBag = DisposeBag()
 
Observable.of("1")
    .asMaybe()
    .subscribe({ print($0) })
    .disposed(by: disposeBag)
Copy the code

The running results are as follows:

1.7 Feature Sequence 2: Driver

1.7.1 Basic Introduction

(1) Drivers are arguably the most complex traits, and their goal is to provide an easy way to write responsive code at the UI layer. (2) We can use it if our sequence meets the following characteristics: A. Does not generate error events B. Must be in the main thread to monitor (MainScheduler) c. Shared state changes (shareReplayLatestWhileConnected)

1.7.2 Why use Driver?

(1) The most common scenario used by a Driver is when a sequence is required to drive an application, such as:

  • The UI is driven through the CoreData model
  • Use one UI element value (binding) to drive another UI element value

(2) As with normal operating system drivers, the application will stop responding to user input if a sequence error occurs. (3) It is also extremely important to observe these elements on the main thread, because UI elements and application logic are generally not thread-safe. (4) In addition, using observable sequences of build drivers, it is shared state changes.

1.7.3 Example Usage

Request data from an input field based on the key, and bind the result to another Label and TableView.

  • A novice Observable with the bindTo binding might say something like this:

  • Instance 1.7.3.1:

Let results = query.rx.text. Throttle (0.3, scheduler: Mainscheduler.instance) // If the value changes several times within 0.3 seconds, FlatMapLatest {query in // Select null, FetchAutoCompleteItems (query) // Ask the server for a set of results} // Bind the returned results to the label used to display the number of results results.map {"\($0.count)"}. Bind (to: Resultcount.rx.text). Prompt (by: disposeBag) // Bind the returned result to the tableView resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in cell.textLabel? .text = "\(result)" } .disposed(by: disposeBag)Copy the code
  • There are three problems with this code in example 1.7.3.1:
  1. if fetchAutoCompleteItemsThe sequence generated an error (network request failure) that will cancel all bindings. After the user enters a new keyword, the user cannot initiate a new network request.
  2. if fetchAutoCompleteItemsIf the sequence is returned in the background, the page refresh will also occur in the background, causing an exception crash.
  3. The returned result is bound to twoUIElement. This means that each time a user enters a new keyword, an HTTP request is made for both UI elements, which is not what we want.
  • After modifying the above problems, the code looks like this:

  • Instance 1.7.3.2:

Let results = query.rx.text. Throttle (0.3, scheduler: Mainscheduler.instance)// If the value changes several times within 0.3 seconds, FlatMapLatest {query in // Select null, FetchAutoCompleteItems (query) // Requests a set of results from the server. ObserveOn (mainScheduler.instance) // switches the returned results to the main thread .catchErrorJustreturn ([]) // Error was handled, ShareReplay (1) // The HTTP request is shared // Bind the returned result to the label showing the number of results results.map {"\($0.count)"}.bind(to: Resultcount.rx.text). Prompt (by: disposeBag) // Bind the returned result to the tableView resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in cell.textLabel? .text = "\(result)" } .disposed(by: disposeBag)Copy the code
  • Although we made it work by adding some extra processing. For a large project, it would be too cumbersome to do all this and prone to errors.
  • If we use Driver to implement this, the code is as follows:
  • Instance 1.7.3.3:
Let results = query.rx.text.asdriver () // Convert the normal sequence to driver.throttle (0.3, scheduler: MainScheduler.instance) .flatMapLatest { query in fetchAutoCompleteItems(query) .asDriver(onErrorJustReturn: Results.map {"\($0.count)"}.drive(resultcount.rx.text) // used here Drive instead of bindto. disposed(by: DisposeBag) / / returns the result of the binding to the results on the tableView. The drive (resultsTableView. Rx. The items (cellIdentifier: "Cell")) {// Also use drive instead of bindTo (_, result, Cell) in cell.textLabel? .text = "\(result)" } .disposed(by: disposeBag)Copy the code
  • The drive method can only be called by Driver. This means that if the code has a drive, the sequence does not generate an error event and must be listened on the main thread. This way we can safely bind UI elements.
  • Here’s an explanation for example 1.7.3.3 above:

Code explanation:

  1. First we useasDriverMethod will be ControlPropertyconvert Driver.
  2. And then we can use .asDriver(onErrorJustReturn: [])Method takes any ObservableThe sequence is all transformed into DriverBecause we know that the sequence transforms to DriverHe was asked to meet three conditions:

    A. NoerrorThe event

    B. Must be listening on the main thread (MainScheduler)

    C. Share state changes (shareReplayLatestWhileConnected)

    asDriver(onErrorJustReturn: [])Equivalent to the following code:

    let safeSequence = xs

    .observeon (mainScheduler.instance) // Main thread listener

    .catchErrorJustreturn (onErrorJustReturn) // Failed to generate an error

    .share(replay: 1, scope:.whileconnected)// Share state changes

    Return Driver(raw: safeSequence) // Encapsulation
  3. At the same time DriverThe framework has been added for us by defaultshareReplayLatestWhileConnectedSo we don’t have to add”replay“Related statement.
  4. Finally remember to usedriveRather than bindTo

1.8 Feature Sequence 3: ControlProperty and ControlEvent

, version 1.8.1 ControlProperty

  • Basic introduction

(1) ControlProperty is specially used to describe UI control properties. All properties of this type are observables. (2) The ControlProperty has the following characteristics: A. Does not generate error event B. Be sure to subscribe to C in MainScheduler. Must be listening on MainScheduler (MainScheduler listener) D. Shared state changes

  • Instance 1.8.1.1

(1) Many UI control properties in RxCocoa are observed (observable sequences). For example, if we look at the source code (UITextField+ rx. swift), we can find that the rx.text property type of UITextField is ControlProperty

:
?>

import RxSwift import UIKit extension Reactive where Base: UITextField { public var text: ControlProperty<String? > { return value } public var value: ControlProperty<String? > { return base.rx.controlPropertyWithDefaultEvents( getter: { textField in textField.text }, setter: { textField, value in if textField.text ! = value { textField.text = value } } ) } //...... }Copy the code

(2) If we want the input content in one textField to be displayed on another label in real time, that is, the former as the observed, and the latter as the observer. It could be written like this:

import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { @IBOutlet weak var textField: UITextField! @IBOutlet weak var label: UILabel! Func viewDidLoad() {// Bind the input text of textField to the label textfield.rx.text.bind (to: label.rx.text) .disposed(by: disposeBag) } } extension UILabel { public var fontSize: Binder<CGFloat> { return Binder(self) { label, fontSize in label.font = UIFont.systemFont(ofSize: fontSize) } } }Copy the code

1.8.2 ControlEvent

  • Basic introduction

The ControlEvent is used to describe events generated by the UI. The properties of the ControlEvent type are all Observables. (2) ControlEvent and ControlProperty have the following characteristics: A. No error event is generated. B. Must subscribe to MainScheduler C. Must be listening on MainScheduler (MainScheduler listener) D. Shared state changes

  • Instance 1.8.2.1
  1. Similarly, the event methods of many UI controls in RxCocoa are observed (observable sequences). UIButton Rx. Tap method ControlEvent UIButton Rx. Tap method ControlEvent:
import RxSwift
import UIKit
 
extension Reactive where Base: UIButton {
    public var tap: ControlEvent<Void> {
        return controlEvent(.touchUpInside)
    }
}
Copy the code
  1. So if we want to implement a text output in the console when a button is clicked. That is, the former as the observed and the latter as the observer. It could be written like this:
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() @IBOutlet weak var button: UIButton! Override func viewDidLoad() {// Subscribe button click event button.rx.tap.subscribe (onNext: {print(" Welcome to hangge.com")}).subscribe(by: disposeBag) } }Copy the code

1.9 Add the RxSwift extension to UIViewController

  • Here we extend UIViewController: \
  1. willviewDidLoad,viewDidAppear,viewDidLayoutSubviewsWait for all sorts of ViewControllerLife cycle method into ControlEventConvenient inRxSwiftUsed in the project.
  2. increaseisVisibleSequence property, which makes it easy to subscribe to the display state of a view.
  3. increase isDismissingSequence property, which makes it easy to subscribe to the release of a view.
  • UIViewController+Rx. Swift:
import UIKit import RxCocoa import RxSwift public extension Reactive where Base: UIViewController { public var viewDidLoad: ControlEvent<Void> { let source = self.methodInvoked(#selector(Base.viewDidLoad)).map { _ in } return ControlEvent(events: source) } public var viewWillAppear: ControlEvent<Bool> { let source = self.methodInvoked(#selector(Base.viewWillAppear)) .map { $0.first as? Bool ?? false } return ControlEvent(events: source) } public var viewDidAppear: ControlEvent<Bool> { let source = self.methodInvoked(#selector(Base.viewDidAppear)) .map { $0.first as? Bool ?? false } return ControlEvent(events: source) } public var viewWillDisappear: ControlEvent<Bool> { let source = self.methodInvoked(#selector(Base.viewWillDisappear)) .map { $0.first as? Bool ?? false } return ControlEvent(events: source) } public var viewDidDisappear: ControlEvent<Bool> { let source = self.methodInvoked(#selector(Base.viewDidDisappear)) .map { $0.first as? Bool ?? false } return ControlEvent(events: source) } public var viewWillLayoutSubviews: ControlEvent<Void> { let source = self.methodInvoked(#selector(Base.viewWillLayoutSubviews)) .map { _ in } return ControlEvent(events: source) } public var viewDidLayoutSubviews: ControlEvent<Void> { let source = self.methodInvoked(#selector(Base.viewDidLayoutSubviews)) .map { _ in } return ControlEvent(events: source) } public var willMoveToParentViewController: ControlEvent<UIViewController? > { let source = self.methodInvoked(#selector(Base.willMove)) .map { $0.first as? UIViewController } return ControlEvent(events: source) } public var didMoveToParentViewController: ControlEvent<UIViewController? > { let source = self.methodInvoked(#selector(Base.didMove)) .map { $0.first as? UIViewController } return ControlEvent(events: source) } public var didReceiveMemoryWarning: ControlEvent<Void> { let source = self.methodInvoked(#selector(Base.didReceiveMemoryWarning)) .map { _ in } return ControlEvent(events: source)} // Public var isVisible when VC displays state changes: Observable<Bool> { let viewDidAppearObservable = self.base.rx.viewDidAppear.map { _ in true } let viewWillDisappearObservable = self.base.rx.viewWillDisappear .map { _ in false } return Observables < Bool >. The merge (viewDidAppearObservable viewWillDisappearObservable)} / / says the page is release can be observed sequence, Public var isDismissing when VC is dismissed: ControlEvent<Bool> { let source = self.sentMessage(#selector(Base.dismiss)) .map { $0.first as? Bool ?? false } return ControlEvent(events: source) } }Copy the code
  • With extensions, we can subscribe directly to VC’s various methods.
import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { let disposeBag = DisposeBag() required init? (coder aDecoder: NSCoder) {super.init(coder: aDecoder) // page display status complete sell.rx.isvisible. Subscribe (onNext: Prompt ({visible in print(" Prompt page display state: \(visible)")}).subscribe(by: disposeBag) // Page load completed sell.rx.viewdidLoad. Prompt (by: disposeBag) {print("viewDidLoad")}).prompt (by: disposeBag) // The page will display self.rx.viewwillAppear. Subscribe (onNext: {animated in print("viewWillAppear")}).prompt (by: disposeBag) // Page display complete self.rx.viewDidAppear. Subscribe (onNext: { animated in print("viewDidAppear") }).disposed(by: disposeBag) } override func viewDidLoad() { super.viewDidLoad() } }Copy the code

Running results:

1.10 Schedulers

Introduction of 1.10.1 Schedulers

  • (1) Scheduler (Schedulers) is RxSwiftThe core module of multithreading, which is mainly used to control which thread or queue the task runs in.
  • (2)RxSwiftThe following are built in Scheduler:
  1. CurrentThreadScheduler: Indicates the current threadScheduler. (This is used by default)
  2. MainScheduler: indicates the main thread. If we need to perform some UI-related tasks, we need to switch to this Scheduler.
  3. SerialDispatchQueueScheduler: encapsulates the serial queue of the GCD. If we need to perform some serial tasks, we can switch to this Scheduler.
  4. ConcurrentDispatchQueueScheduler: encapsulates the parallel queue of GCD. If we need to perform some concurrent tasks, we can switch to thisSchedulerRun.
  5. OperationQueueScheduler: encapsulatesNSOperationQueue.

1.10.2 instance

  • Here is an example of requesting network data and displaying it. We make a network request in the background, then parse the data, and finally refresh the page in the main thread.

  • In the past we used GCD, and the code would look something like this:
Dispatchqueue.global (qos:.userinitiated). Async {let data = try? Dispatchqueue.main. async {self. Data = Data}}Copy the code
  • If implemented using RxSwift, the code would look something like this:
let rxData: Observable<Data> = ... rxData .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userinitiated)) // the background builds the sequence. ObserveOn (mainScheduler.instance) // The main thread listens and processes the sequence result. Subscribe (onNext: { [weak self] data in self? .data = data }) .disposed(by: disposeBag)Copy the code

1.10.3 Difference between subscribeOn and observeOn

  • 1)subscribeOn()
  1. This method determines where the constructor of the data sequence is locatedSchedulerTo run on.
  2. For example, in the above example, it takes some time to obtain and parse the data, so it passes subscribeOnSwitch it to the backgroundSchedulerTo execute. This prevents the main thread from being blocked.
  • 2). observeOn()
  1. The method determines whichSchedulerListen for this data sequence.
  2. In the example above, we fetch and parse the data and then pass observeOnMethod switches to the main thread to listen and process the results.

Since the | address

Swift Books download:Download address