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
- Reduce takes an initial value and an operation symbol.
- 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
- Concat merges (concatenates) multiple Observables into one Observable.
- 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
publish
Method converts a normal sequence into a concatenable sequence. Also, the sequence does not send events immediately, only when calledconnect
It 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
replay
Ditto belowpublish
The 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 calledconnect
It will start later.replay
与publish
The 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
multicast
The method is also to convert a normal sequence into a concatenable sequence.- At the same time
multicast
The method can also pass in oneSubject
This is emitted whenever the sequence sends an eventSubject
To 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
refCount
Operators can be concatenableObservable
Convert to ordinaryObservable
- That is, the operator can automatically connect and disconnect the connectable
Observable
. When the first observer is connected to theObservable
When subscribing, then the underlyingObservable
Will be automatically connected. When the last observer leaves, then the bottomObservable
Will 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
- This operator causes observers to share the source
Observable
And cache the latestn
Element, which is sent directly to the new observer.- In simple terms
shareReplay
isreplay
和refCount
The 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 set
Observable
All 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 right
Observable
Subscribe.
- 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
- This operator converts events generated by a sequence into elements.
Usually a limited numberObservable
Zero or more will be generatedonNext
The event,- And you end up with a
onCompleted
oronError
Events. whilematerialize
The operator will setObservable
These 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 and
materialize
On the contrary, it canmaterialize
Element 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 source
Observable
A timeout occurs when no element is emitted within the specified timeerror
Events.
- 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
- use
using
Operator creationObservable
At the same time a resource will be created that can be purged onceObservable
Terminate, 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 with
error
Event, 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
- This method catches errors and processes them.
- 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
- Using this method, the sequence is re-subscribed when an error is encountered. For example, if a network request fails, you can reconnect.
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 put
debug
The 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 the
RxSwift.Resources.total
Print it out and we can view the currentRxSwift
The 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
- Single is another version of Observable. Unlike an Observable, which emits multiple elements, it either emits one element or an error event.
- Raises an element, or an error event
- State changes are not shared
- 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 calling
Observable
The 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:
Completable
是Observable
Another version of “. Don’t likeObservable
Multiple elements can be emitted, or it can only produce onecompleted
Event, or produce oneerror
Events.- No elements are emitted
- Only one Completed event or one error event is emitted
- 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:
- .completed: Used to generate a completion event
- .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
Maybe
The same isObservable
Another version of “. It is betweenSingle
andCompletable
In between, it can either emit one element or produce onecompleted
Event, or produce oneerror
Events.
- 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:
- if
fetchAutoCompleteItems
The 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.- if
fetchAutoCompleteItems
If the sequence is returned in the background, the page refresh will also occur in the background, causing an exception crash.- The returned result is bound to two
UI
Element. 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:
- First we use
asDriver
Method will beControlProperty
convertDriver
.- And then we can use
.asDriver(onErrorJustReturn: [])
Method takes anyObservable
The sequence is all transformed intoDriver
Because we know that the sequence transforms toDriver
He was asked to meet three conditions:
A. Noerror
The 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- At the same time
Driver
The framework has been added for us by defaultshareReplayLatestWhileConnected
So we don’t have to add”replay
“Related statement.- Finally remember to use
drive
Rather thanbindTo
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
- 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
- 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: \
- will
viewDidLoad
,viewDidAppear
,viewDidLayoutSubviews
Wait for all sorts ofViewController
Life cycle method intoControlEvent
Convenient inRxSwift
Used in the project.- increase
isVisible
Sequence property, which makes it easy to subscribe to the display state of a view.- increase
isDismissing
Sequence 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
) isRxSwift
The core module of multithreading, which is mainly used to control which thread or queue the task runs in. - (2)
RxSwift
The following are built inScheduler
:
CurrentThreadScheduler
: Indicates the current threadScheduler
. (This is used by default)MainScheduler
: indicates the main thread. If we need to perform some UI-related tasks, we need to switch to this Scheduler.SerialDispatchQueueScheduler
: encapsulates the serial queue of the GCD. If we need to perform some serial tasks, we can switch to this Scheduler.ConcurrentDispatchQueueScheduler
: encapsulates the parallel queue of GCD. If we need to perform some concurrent tasks, we can switch to thisScheduler
Run.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()
- This method determines where the constructor of the data sequence is located
Scheduler
To run on.- For example, in the above example, it takes some time to obtain and parse the data, so it passes
subscribeOn
Switch it to the backgroundScheduler
To execute. This prevents the main thread from being blocked.
- 2).
observeOn()
- The method determines which
Scheduler
Listen for this data sequence.- In the example above, we fetch and parse the data and then pass
observeOn
Method switches to the main thread to listen and process the results.
Since the | address