the basic of RXSwift

本文深入探讨了RXSwift中各种特性的使用方式与应用场景,包括Single、Completable、Maybe及RxCocoa特性Driver,并通过实际案例展示了如何利用这些特性增强应用程序的稳定性和直观性。
摘要由CSDN通过智能技术生成

I wanto to learn the RXSwift,so writing a document about RXSwift,refreing to the link:https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Traits.md

This document will try to describe what traits are ,why they are a useful concept,and how to used and create them.
.General
Why & How they work
.RXSwift traits
Single(creating a single) & completable (creating a completable) & Maybe (creating a Maybe) & RXCocoa traits(Driver-why is is named Driver & Practical usage example & ControlProperty / ControlEvent)

1、Why 。Swift has a powerful type system that can be used to improve the correctness and stability of applications and make using Rx a more intuitive and straightforward experience. Traits help communicate and ensure observable sequence properties across interface boundaries,as well as provide contextual meaning,syntactical sugar and target more specific use-cases when compared to a raw Observable,which could be used in any context.For that reason,Traits are entirely optional.You are free to use raw Observable sequences everywhere in your program as all core RxSwift/RxCocoa APIs support them.
Note:Some of the Traits described in this document(such as Driver) are specific only to the RxCocoa project,while some are part of the general RxSwift project.However,the same principles could easily be implemented Rx implementations,if necessary.There is no private API magic needed.
2、How they work.Traits are simply a wrapper struct with a single read-only Observable sequence property.
struct Single {
let source: Observable
}
struct Driver {
let source: Observable
}
You can think of them as a kind of builder pattern implementation for Observable sequences.When a Trait is built,calling .asObservable() will transform it back into a vanilla observable sequence.

Rxswift traits
Single(A Single is a variation of Observable that, instead of emitting a series of elements, is always guaranteed to emit either a single element or an error.)
Emits exactly one element,or an error
Doesn’t share side effects
func getRepo(_ repo: String) -> Single<[string: Any]> {
return Single<[string: Any]>.create { single in
let task = URLSession.shared.dataTask(with: URL(string: “https://api.github.com/repos/(repo)!”) { data,

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 Disposable.create{ task.cancel()}
}
}
After which you could use it in the following way:
getRepo(“ReactiveX/RxSwift”)
.subscribe { event in
switch event {
case .success(let json):
print(“JSON: “,json)
case .error(let error):
print(“Error: “,error)
}
}
.disposed(by: disposeBag)
Or by using subscribe(onSuccess:onError:) as follows:
getRepo(“ReactiveX/RxSwift”)
.subscribe(onSuccess: {json in
print(“JSON: “,json)
},
onError: {error in
print(“Error: “,error)
})
.disposed(by:disposedBag)
The subscription provides a SingleEvent enumeration which could be either .success containing a element of the Single’s type,or .error.No further events would be emitted beyond the first one.
It’s also possible using .asSingle() on a raw Observable sequence to transform it into a single.
Completable(A Completable is a variation of Observable that can only complete or emit an error. It is guaranteed to not emit any elements.)
.Emits zero elements
.Emits a completion event,or an error
.Does’t share side effects
A useful use case for Completable would be to model any case where we only care for the fact an operation has completed,but don’t care about a element resulted by that completion.You could compare it to using an Observablethat can’t emit elements.
Creating a completable(Creating a completable is similar to creating an Observable.A simple example would look like this:)
func cacheLocally() -> Completable {

return Completable.create { completable in
//store some data locally
guard success else {
completable(.error(CacheError.failedCaching))
return Disposables.create{}
}
completable(.completed)
return Disposables.create{}
}
}
After which you could use it in the following way:
cacheLocally()
.subscribe { completable in
switch completable {
case .completed:
print(“Completed with no error”)
case .error(let error):
print(“Completed with an error:(error.localizedDescription)”)
}
}
.disposed(by:disposedBag)
Or by using subscribe(onCompleted:onError:)as follows:
cacheLocally()
.subscribe(onCompleted:{
print(“Completed with no error”)
},
onError:{ error in
print(“Completed with an error: (error.localizedDescription)”)
})
.disposed(by: disposeBag)
The subscription provides a completableEvent enumeration which could be either .completed-indicating the operation completed with no errors,or .errors,or.error.No further events would be emitted beyond the first one.
Maybe(A Maybe is a variation of Observable that is right in between on a single and a Completable.It can either emit a single element,complete withuot emitting an element,or emit an error.)
Note:(Any of these three events would terminate the Maybe,meaning-a Maybe that completed cna’t also emit an elment,and a Maybe that emitted an element can’t also send a Completion event.)
Emits either a completed event,a single element or an error
Doesn’t share side effects
you could use maybe to model any operation that could emit an element,but doesn’t necessarily have to emit an element.
Creating a Maybe(Creating a Maybe is similar to creating an Observable.A simple example would look like this:)
func generatingString() -> Maybe{
return Maybe.create {maybe in
maybe(.success(“RxSwift”))
maybe(.completed)
maybe(.error(error))
return Disposables.create{}
}
}
After which you could use it in the following way:
generatingString()
.subscribe{ maybe in
switch maybe {
case .success(let element):
print(“Completed with element (element)”)
case .completed:
print(“Completed with no element”)
case .error(let error):
print(“completed with an error (error.localizedDescription)”)
}
}
.disposed(by: dispseBag)
Or by using subscribe(onSuccess:onError:onCompleted:) as follows:
generatingString()
.subscribe(onSuccess: {element in
print(“Completed with element (element)”)
},
onError:{error in
print(“Completed with an error (error.localizedDescription)”)
},
onCompleted: {
print(“Completed with no element”)
})
.disposed(by: disposeBag)
RxCocoa traits
Driver(This is the most elaborate trait.Its intention is to provide an intuitive way to write reactive code in the UI layer,or for any case where you want to model a stream of data Driving your application.)
.Can’t error out
.Observe occurs on main scheduler
.Shares side effects(shareReplayLatestWhileConnected)
why is it named Driver
Its intended use case was to model sequences that drive your application.
E.g.
.Drive UI from CoreData model
.Drive UI using values from other UI elements(bindings)…
Like normal operating system drivers,in case a sequence errors out,your application will stop responding to user input.It is also extremely important that those elements are observed on the main thread because UI elements and application logic are usually not thread safe.
Also,m a Driver builds an observable sequence that shares side effects.
E.g.
Drive UI from CoreData model
Drive UI using values from other UI elemnts(bindings)…
Like normal operating system drivers,in case a sequence errors out,your apllication will stop responding to user input.It is also extremely important that those elements are observed on the main thread because UI elements and application logic are usually not thread safe.Also,a Driver builds an observable sequence that shares side effects.
Practical usage example
This is a typical beginner example.
let results = query.rx.text
.throttle(0.3, scheduler: MainScheduler.instance)
.flatMapLatest { query in
fetchAutoCompleteItems(query)
}
results
.map{ “($0.count)”}
.bind(to: resultCount.rx.text)
.disposed(by: disposeBag)

results
.bind(to: resultsTableView.rx.items(cellIdentifier:”Cell”)){ (_, result, cell) in
cell.textLabel?.text = “(result)”
}
.disposed(by: disposedBag)

results
.bind(to: resultsTableView.rx.items(cellIdentifier: “Cell”)){ (_, result, cell) in
cell.textLabel?.text = “(result)”
}
.disposed(by: disposeBag)

The intended behavior of this code was to :
.Throttle user input
.Contact server and fetch a list of user results(once per query)
.Bind the results to two UI elements: results table view and a label that displays the number of results
So, what are the problems with this code?:
.If the fetchAutoCompleteItems observable sequence errors out(connection failed or parsing error),this error would unbind everything and the UI wouldn’t respond any more to new queries.
.If fetchAutoCompleteItems return results on some background thread,results would be bound to UI elements from a background thread which could cause non deterministic crashes
.Results are bound to two UI elements, which means that for each user query, two Http requests would be made,one for each UI element,which is not the intended behavior.
A more appropriate version of the code would like this:
let results = query.rx.text
.throttle(0.3, scheduler: MainScheduler.instance)
.flatMapLatest { query in
fetchAutoCompleteItems(query)
.observeOn(MainScheduler.instance)
.catchErrorJustResult([])
}
.shareReplay(1)

results
.map{ “($0.count)”}
.bind(to: resultCount.rx.text)
.disposed(by: disposeBag)

results
.bind(to: resultsTableView.rx.items(cellIdentifier: “Cell”)){ (_, result, cell) in
cell.textLabel?.text = “(result)”
}
.disposed(by: disposedBag)

Making sure all of these requirements are properly handled in large systems can be challenging,but there is a simpler way of using the compiler and traits to prove these requirements are met.
The following code looks almost the same:
let reslults = query.rx.text.asDriver()//this converts a normal sequence into a “Driver” sequence.
.throttle(0.3, scheduler: MainScheduler.instance)
.flatMapLatest { query in
fetchAutoCompleteItems(query)
.asDriver(onErrorJustReturn:[])//builder just needs info about what to return in case of error.
}

results
.map{ “($0.count)”}
.drive(resultCount.rx.text)//if there is a ‘drive’method available instead of ‘bindTo’,//that means that the compiler has proven that all prroperties
.disposed(by: disposeBag)//are satisfied.

results
.drive(resultsTableView.rx.items(cellIdentifier: “Cell”)){ (_, result, cell) in
cell.textLabel?.text = “(result)”
}
.disposed(by: disposeBag)

So what is happening here?
This first asDriver method ocnverts the ControlProperty trait to a Driver trait.
query.rx.text.asDriver()
Notice that there wasn’t anying special that needed to be done.Driver has all of the properties of the ControlProperty trait,plus some more. The underlying observable sequence is just wrapped as a Driver trait,and that’s it.
The second change is :
.asDriver(onErrorJustRetrun:[])
Any observable sequence can be converted to Driver trait, as long as it satisfies 3 properties:
.Can’t error out
.Observe on main scheduler
.Sharing side effects(shareReplayLatestWhileConnected)
So how do you make sure those properties are satisfied?Just use normal Rx operators.asDriver(onErrorJustReturn:[])is equivalent to following code.
let safeSequence = xs
.observeOn(MainScheduler.instance)//observe events on main scheduler
.catchErrorJustReturn(onErrorJustReturn)//can’t error out
.shareReplayLatestWhileConnected()//side effects sharing
return Driver(raw: safeSequence)//wrap it up
The final piece is using drive instead of using bindTo.
drive is defined only on the Driver trait.This means that if you see drive somewhere in code ,that observable sequence can never error out and it observes on the main thread,which is safe for binding to a UI element.
Not however that,theoretically,someone could still deine a drive method to work on observableType or some other interface ,so to be extra safe,creating a temporary definition with let results: Driver<[Results]> = … before binding to UI elements would be necessary for complete proof.However, we’ll leave it up to the reader to decide whether this is a realistic scenario or not.
ControlProperty/ControlEvent(Trait for observable/ObservableType that represents property of UI element.Sequence of values only represents initial control value and user initiated value changes.Programatic value changes won’t be reported.)
It’s properties are :
.it never fails
.shareReplay(1) behavior(it’s stateful,upon subscription(calling subscribe)last element is immediately replayed if it was produced)
.it will complete sequence on control being deallocated
.it never errors out
.it delivery events on MainScheduler.instance
The impleementation of ControlProperty will ensure that sequence of events is being subscribed on main scheduler (subscribeOn(ConcurrentMainScheduler.instance)behavior)
Practical usage example
we can found very good pratical examples in the UISearchBar+Rx and in the UISegmentedControl+Rx:
Practical usage example
We can found very good practical examples in the UISearchBar+Rx and in the UISegmentedControl+Rx:
extension Reactive where Base: UISearchBar {
public var value: ControlProperty

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值