- Compartir el Observable
- Compartir el Observer
Repasemos un poco en qué consisten estos dos tipos de mecanismos y por qué podrían ser necesarios.
Compartir el Observable
let sourceObservable = Observable<Int>
.interval(.seconds(1), scheduler: serialScheduler)
.flatMap { id -> Observable<String> in
return Observable.just("Request to API with userId = \(id)")}
.do(onNext: { result in print(result) })
.take(1)
let subscriptionA = sourceObservable
.subscribe(onNext: { element in
// A Observing code
})
let subscriptionB = sourceObservable
.subscribe(onNext: { element in
// B Observing code
})
let subscriptionC = sourceObservable
.subscribe(onNext: { element in
// C Observing code
})
// To avoid immediately dispose of the
// emission and to see the share effect
Thread.sleep(forTimeInterval: 5.0)
subscriptionA.dispose()
subscriptionB.dispose()
subscriptionC.dispose()
Request to API with userId = 0
Request to API with userId = 0
Request to API with userId = 0
¿No sería más óptimo ejecutar la lógica contenida por el Observable una sola vez y compartir la operación con múltiples Observers?
let sourceObservable = Observable<Int>
.interval(.seconds(1), scheduler: serialScheduler)
.flatMap { id -> Observable<String> in
return Observable.just("Request to API with userId = \(id)")}
.do(onNext: { result in print(result) })
.take(1)
.share()
let subscriptionA = sourceObservable
.subscribe(onNext: { element in
// A Observing code
})
let subscriptionB = sourceObservable
.subscribe(onNext: { element in
// B Observing code
})
let subscriptionC = sourceObservable
.subscribe(onNext: { element in
// C Observing code
})
Thread.sleep(forTimeInterval: 5.0)
subscriptionA.dispose()
subscriptionB.dispose()
subscriptionC.dispose()
Con dicha modificación, el resultado es el siguiente:
Request to API with userId = 0
let sourceObservable = Observable<Int>
.interval(.seconds(1), scheduler: serialScheduler)
.flatMap { id -> Observable<String> in
return Observable.just("Request to API with userId = \(id)")}
.do(onNext: { result in print(result) })
.take(1)
.publish()
sourceObservable
.subscribe(onNext: { element in
// A Observing code
}).disposed(by: disposeBag)
sourceObservable
.subscribe(onNext: { element in
// B Observing code
}).disposed(by: disposeBag)
sourceObservable
.subscribe(onNext: { element in
// C Observing code
}).disposed(by: disposeBag)
//Apply Connect
sourceObservable.connect()
Compartir el Observer
let observableA = Observable<Int>
.interval(.seconds(1), scheduler: serialScheduler)
.flatMap { id -> Observable<String> in
return Observable.just("Request to API A with userId = \(id)")}
.take(1)
let observableB = Observable<Int>
.interval(.seconds(1), scheduler: serialScheduler)
.flatMap { id -> Observable<String> in
return Observable.just("Request to API B with userId = \(id)")}
.take(1)
let observableC = Observable<Int>
.interval(.seconds(1), scheduler: serialScheduler)
.flatMap { id -> Observable<String> in
return Observable.just("Request to API C with userId = \(id)")}
.take(1)
let subscription = Observable.merge(observableA, observableB, observableC)
.subscribe(onNext: { result in
print(result)
})
Thread.sleep(forTimeInterval: 5.0)
subscription.dispose()
Request to API A with userId = 0
Request to API B with userId = 0
Request to API C with userId = 0
Cuando se usan los operadores de combinación, se debe verificar la naturaleza del stream, considerar si es infinite o finite, ya que podría presentarse escenarios no deseados.
Compartir múltiples Observables a múltiples Observers
- PublishSubjects.
- BehaviorSubjects.
- ReplaySubjects.
¿Cómo se decide que tipo de Subject utilizar o definir?
Depende del tipo de memoria que necesita el componente:
- Si el componente necesita tener memoria de largo plazo, se utiliza ReplaySubject.
- Si el componente necesita tener memoria de corto plazo, se utiliza BehaviorSubject.
- Si el componente no necesita tener memoria, se utiliza PublishSubject.
Se muestra a continuación un ejemplo de aplicación de un componente basado en Subjects y que requiere actuar como proxy.
A nivel de la vista se podría tener una implementación como la siguiente:
import UIKit
import RxSwift
class PaginatorViewController: UIViewController {
private let disposeBag = DisposeBag()
private var paginator: Paginator?
@IBOutlet weak var resultLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
bindPaginator()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
private func bindPaginator() {
paginator = Paginator.instance
paginator?.getResults(query: "Query 1")
.map { result in "Updating the list with => \(result)" }
.subscribe(onNext: { result in
self.showResults(result)
})
.disposed(by: disposeBag)
paginator?.getResults(query: "Query 2")
.map { result in "Hiding the loader after getting => \(result)" }
.subscribe(onNext: { result in
self.showResults(result)
})
.disposed(by: disposeBag)
paginator?.getResults(query: "Query 3")
.map { result in "Doing any other update with => \(result)" }
.subscribe(onNext: { result in
self.showResults(result)
})
.disposed(by: disposeBag)
}
@IBAction func loadInitialPage(_ sender: Any) {
self.queryData(page: 1)
}
@IBAction func getNewPageByScrolling(_ sender: Any) {
self.queryData(page: 5)
}
@IBAction func getNewPageManually(_ sender: Any) {
self.queryData(page: 21)
}
private func queryData(page: Int) {
paginator?.pushPage(numberPage: page)
}
private func showResults(_ result: String) {
print(result)
resultLabel.text = result
}
}
El componente Paginator sería algo como:
import Foundation
import RxSwift
struct Paginator {
private let page: PublishSubject<Int>
static let instance = Paginator()
private init() {
page = PublishSubject<Int>()
}
func getResults(query: String) -> Observable<String> {
return getPage()
.flatMap { numberPage -> Observable<String> in
return Observable.just("page: \(numberPage) and query: \(query)")
}
.share()
}
func pushPage(numberPage: Int) {
page.onNext(numberPage)
}
func complete() {
page.onCompleted()
}
private func getPage() -> Observable<Int> {
return page.asObserver()
}
}
Cuando se ejecuta el evento loadInitialPage:
Updating the list with => page: 1 and query: Query 1
Hiding the loader after getting => page: 1 and query: Query 2
Doing any other update with => page: 1 and query: Query 3
Updating the list with => page: 5 and query: Query 1
Hiding the loader after getting => page: 5 and query: Query 2
Doing any other update with => page: 5 and query: Query 3
Updating the list with => page: 21 and query: Query 1
Hiding the loader after getting => page: 21 and query: Query 2
Doing any other update with => page: 21 and query: Query 3
El uso de Subjects también permite agregar cierta flexibilidad al diseño. En el ejemplo anterior el lector podrá apreciar cómo la subscripción se hace en el momento en el que se requiere, es decir en la ejecución del método. De igual forma en otros escenarios se podrá hacer a través de los operadores de combinación como ya se mencionaron.
ConversionConversion EmoticonEmoticon