preface

This article from the actual code analysis, in RxSwift why to use Driver and how to use Driver

Bind UI via network request

Now simulate a common situation, listen to UI -> request network -> update UI as shown in the following code, here is the basic use of RxSwift, listen to textField changes, request network, and then update UI

// Listen for textField changes and send network requestslet result  = inputTF.rx.text.skip(1)
            .flatMap { [weak self](input) -> Observable<Any> in
                return(self? .dealwithData(inputText:input ??""))! }.share(replay: 1, scope:.whileconnected) {// Assign a value to a label, subscribe(onNext: {(element)in
            print("Subscribed to 1.")
            self.textLabel.text = element as? String
        }, onError: { (error) inSubscribe (onNext: {(element)) {subscribe(element);in
            print("Subscribed to 2.") self.btn.titleLabel? .text = element as? String }, onError: { (error)in}) // look at dealwithData(inputText:String)-> Observable<Any>{dealwithData(inputText:String)-> Observable<Any>{print("Requested network \(thread.current)") // data
        return Observable<Any>.create({ (ob) -> Disposable in
            if inputText == "1234" {
                ob.onError(NSError.init(domain: "com.error.cn", code: 10086, userInfo: nil)} dispatchqueue.global ().async {print("Look before sending: \(thread.current)")
                ob.onNext("Already entered :\(inputText)")
                ob.onCompleted()
            }
            return Disposables.create()
        })
    }
Copy the code

When we ran the code, we found several problems

  1. self.textLabel.text = element as? StringCauses a crash because we are updating the UI in the child thread and need to switch to the main thread
  2. “Requested network” prints twice, which means subscribed twice, sent two network requests, which is certainly not what we want
  3. Comment out the code that updated the UI, and test the error event (enter 1234) to see that after the error occurs, the error unbinds all bindings, and when we enter a new number, it does not generate a response

Optimize the code to solve the above problems

Let’s solve this problem first with the basic RxSwift provided function, which is commented below

// Listen for textField changes and send network requestslet result  = inputTF.rx.text.skip(1)
            .flatMap { [weak self](input) -> Observable<Any> in
                return(self? .dealwithData(inputText:input ??""))! .observeon (MainScheduler()))"Error event detected"3. Share (replay: 1) // Share network request, solve problem 2}Copy the code

Running the code again, we see that the above problem is solved and everything is fine, but can we optimize the code again? This feels cumbersome, and in a large system, it’s not easy to make sure every step is not missed. A better choice is to use the compiler and feature sequences properly to ensure that these prerequisites are met.

So it’s time to start using RxSwift’s Driver feature sequence, which is, after all, the old Driver sequence, specifically designed to solve UI problems.

Optimize again using Driver

Take a look at the following code, clean, solve all problems, the use of the Driver as follows code OK

let result  = inputTF.rx.text.orEmpty
            .asDriver()
            .flatMap {
                return self.dealwithData(inputText: $0)
                    .asDriver(onErrorJustReturn: "Error event detected")
            }.map{ $0as! String} // Convert Any to String // Subscribe code to: Drive (self.btn.rx.title()); // Assign a label to update one UI result.drive(self.btn.rx.title()); // Assign a Btn to update another UI result.drive(self.btn.rx.title()).Copy the code

Analyze the code above:

  1. AsDriver converts a listening sequence into a Driver. Any listening sequence can be converted into a Driver if it meets three criteria:
    • No error events are generated
    • Must be listening on MainScheduler
    • Share add-ons (similar to the above subscription twice, but with only one network request)
  2. OnErrorJustReturn: [] Caught an error
  3. Calling Drive directly binds the data to the UI. The drive method can only be called by the Driver. This means that if you find the code in drive, the sequence will not generate an error event and must be listened on the main thread. This way you can safely bind UI elements.

Explore the principles of Driver

As in the previous articles, this article explores how the Driver works, but I won’t go into that much detail here, so you can try exploring the source code for yourself. CatchError is an example of how to catch errors in asDriver

    public func asDriver(onErrorRecover: @escaping (_ error: Swift.Error) -> Driver<Element>) -> Driver<Element> {
        let source = self
            .asObservable()
            .observeOn(DriverSharingStrategy.scheduler)
            .catchError { error in
                onErrorRecover(error).asObservable()
            }
        return Driver(source)}Copy the code

ObserveOn subscribed threads DriverSharingStrategy. The scheduler is the main thread MainScheduler, found by clicking in looking for the source code. So the Driver runs on the main thread and can update the UI

     public static var scheduler: SchedulerType { return SharingScheduler.make() }
     
     
     public private(set) static var make: () -> SchedulerType = { MainScheduler() }
Copy the code

Click on Driver(source) to see if Driver is an alias to SharedSequence. .whileconnected), is it the same as the code we wrote when we first optimized it? Share returns a new sequence of events, listens for events in the underlying sequence, and notifies all subscribers. The representation above is multiple subscriptions and only one call to the network request.

public typealias Driver<Element> = SharedSequence<DriverSharingStrategy, Element>

  public static func share<Element>(_ source: Observable<Element>) -> Observable<Element> {
        return source.share(replay: 1, scope: .whileConnected)
    }
Copy the code

Continue exploring share, and with makeSubject: {replaySubject.create (bufferSize: replay), we see that it creates a ReplaySubject

     case .whileConnected:
            switch replay {
            caseZero:return ShareWhileConnected(source: self.asObservable())
            case 1: return ShareReplay1WhileConnected(source: self.asObservable())
            default: return self.multicast(makeSubject: { ReplaySubject.create(bufferSize: replay) }).refCount()
            }
Copy the code

Then we find along the multicast method, found that it is the initial method in the ConnectableObservableAdapter calls, found here to save the subject, and then the subject is a lazySubject.

init(source: Observable<Subject.Observer.Element>, makeSubject: @escaping () -> Subject) {
        self._source = source
        self._makeSubject = makeSubject
        self._subject = nil
        self._connection = nil
    }
    
  fileprivate var lazySubject: Subject {
        if let subject = self._subject {
            return subject
        }

        let subject = self._makeSubject()
        self._subject = subject
        return subject
    }    

Copy the code

When you look at lazy loading objects, it suddenly becomes clear that this is why you subscribe multiple times and only send a network request once. Here, a new Subject is actually created to listen for sequences. This Subject will only be created once, so it can listen for multiple things, but only execute once.

conclusion

A Driver is a carefully prepared sequence of features. It is mainly to simplify the UI layer of the code, so in the development can be more commonly used, I hope we can master and skilled use.