I am trying to achieve a subscription of 3 different BehaviorRelay using RxSwift. The idea is following:
let disposeBag = DisposeBag()
class ClassA{
var br1 = BehaviorRelay(value: "BR1_1")
var br2 = BehaviorRelay(value: "BR2_1")
var br3 = BehaviorRelay(value: "BR3_1")
/* Some other variables */
}
let classA = ClassA()
class ClassB:CustomStringConvertible{
let classA:ClassA
var description: String{
return "\(br1.value), \(br2.value), \(br3.value)"
}
var br1:BehaviorRelay<String>{
return classA.br1
}
var br2:BehaviorRelay<String>{
return classA.br2
}
var br3:BehaviorRelay<String>{
return classA.br3
}
init(classA:ClassA) {
self.classA = classA
}
}
let classB = ClassB(classA: classA)
classB.br1.asObservable().subscribe { (value) in
print(value)
}
classA.br1.accept("BR1_2")
It prints:
next(BR1_1)
next(BR1_2)
By doing that I am "populating" my ClassB with objects from ClassA and by setting a subscription to the objects in ClassB i can react to the next events.
However, I would like to create a function such as:
func reactingFunction(br1:BehaviorRelay<String>, br2:BehaviorRelay<String>, br3:BehaviorRelay<String>){
/**/
}
to be called every time any of the br produces onNext event. Can I create a mix subscription for all of them?
Thank you
I have found out a solution by implementing CombineLast:
Observable.combineLatest(classB.br1, classB.br2, classB.br3).subscribe(onNext: { (br1, br2, br3) in
reactingFunction(br1: br1, br2: br2, br3: br3)
}).disposed(by: disposeBag)
Then I have changed the reactingFunction to accept only the values:
func reactingFunction(br1:String, br2:String, br3:String){
print("Reacting Function -> \(br1) & \(br2) \(br3)")
}
From the example:
classA.br1.accept("BR1_2")
classA.br1.accept("BR1_3")
classA.br1.accept("BR1_4")
classA.br1.accept("BR1_5")
classA.br2.accept("BR2_2")
classA.br3.accept("BR3_2")
classA.br2.accept("BR2_3")
It prints:
Reacting Function -> BR1_1 & BR2_1 BR3_1
Reacting Function -> BR1_2 & BR2_1 BR3_1
Reacting Function -> BR1_3 & BR2_1 BR3_1
Reacting Function -> BR1_4 & BR2_1 BR3_1
Reacting Function -> BR1_5 & BR2_1 BR3_1
Reacting Function -> BR1_5 & BR2_2 BR3_1
Reacting Function -> BR1_5 & BR2_2 BR3_2
Reacting Function -> BR1_5 & BR2_3 BR3_2
Related
async function showSampleText(context: vscode.ExtensionContext): Promise<void> {
let sampleTextEncoded = await vscode.workspace.fs.readFile(vscode.Uri.file(context.asAbsolutePath('sample.txt')));
let sampleText = new TextDecoder('utf-8').decode(sampleTextEncoded);
let doc = await vscode.workspace.openTextDocument({ language: 'plaintext', content: sampleText });
vscode.window.showTextDocument(doc);
}
I translated the above Typecript snippet to the following Fable code.
type ITextDecoder =
abstract member decode: ?input: Uint8Array * ?options: {| stream : bool |} -> string
[<Emit("new TextDecoder($0)")>]
let TextDecoder label : ITextDecoder = jsNative
let show_sample_text (ctx : Vscode.ExtensionContext) =
async {
let! sampleTextEncoded = Vscode.workspace.fs.readFile(statics.Uri.file(ctx.asAbsolutePath("sample.txt"))) |> Async.AwaitPromise
let sampleText = TextDecoder("utf-8").decode(sampleTextEncoded);
let! doc = workspace.openTextDocument(options= !!{| language="plaintext"; content=sampleText |}) |> Async.AwaitPromise
let! _ = window.showTextDocument(document=doc,options=undefined) |> Async.AwaitPromise
return ()
} |> Async.StartAsPromise
It bothers me how I am explicitly converting between Async and Promises. Is there builder specifically for Promises somewhere?
Fable does support promise via Fable.Promise package.
If you add Fable.Promise package to your project then you can do things like that:
let private getRandomUser () = promise {
// Do something here
// ...
return ()
}
I am trying to bind a delegate this is what was done
/** Delegate Proxy **/
func castOrThrow<T>(_ resultType: T.Type, _ object:Any) throws -> T {
guard let returnValue = object as? T else {
throw RxCocoaError.castingError(object: object, targetType: resultType)
}
return returnValue
}
#objc
public protocol TestEventGeneratorDelegate:class {
#objc optional func asyncEventResult(int:Int)
}
open class TestEventGenerator {
public var delegate: TestEventGeneratorDelegate?
func asyncCall() {
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(1)) { [weak self] in
guard let self = self else { return }
self.delegate?.asyncEventResult!(int: 0)
}
}
}
extension TestEventGenerator: HasDelegate {
public typealias Delegate = TestEventGeneratorDelegate
}
open class RxTestDelegateProxy : DelegateProxy<TestEventGenerator,TestEventGeneratorDelegate>,
DelegateProxyType,
TestEventGeneratorDelegate {
public weak private(set) var testGenerator: TestEventGenerator?
public init(testEventGenerator: ParentObject) {
self.testGenerator = testEventGenerator
super.init(parentObject: testEventGenerator, delegateProxy: RxTestDelegateProxy.self)
}
public static func registerKnownImplementations() {
self.register { RxTestDelegateProxy(testEventGenerator: $0) }
}
}
extension Reactive where Base: TestEventGenerator {
public var delegate: DelegateProxy<TestEventGenerator, TestEventGeneratorDelegate> {
return RxTestDelegateProxy.proxy(for: base)
}
public var asyncEventResult: Observable<Int> {
let source = delegate.methodInvoked(#selector(TestEventGeneratorDelegate.asyncEventResult(int:)))
.map { (a) in
return try castOrThrow(Int.self,a[0])
}
return source
}
}
/** Delegate Proxy **/
Then when I use it
let testEventGenerator = TestEventGenerator()
textEventGenerator.rx.asyncEventResult.subscribe.... // subscribe here no rx found?
testEventGenerator.asyncCall() // call and wait for the observable to return to the delegate
It doesn't compile and says there is no such rx
I have to bind it because the person who wrote the API didn't use a callback to return the value but rather a delegate.
This is example of how he wrote the code and how I want to wrap it.
Is there a way to bind a delegate that requires a kick off from asyncCall()?
so I can chain it using flatmapLatest in a promiseLike way?
Thanks let me know!
I followed these tutorial:
How to convert Delegate to Observable RxSwift?
and
https://blog.ippon.fr/2018/11/13/rxswift-how-to-make-your-favorite-delegate-based-apis-reactive/
have conform to TestEventGenerator:ReactiveCompatible and that will fix it.
So I have a button inside my ViewController which is connected to ViewModel and than whenever the button is tapped, in my coordinator I navigate to another screen. The code is like this:
VC
btnShowShopsMap.rx.tap
.bind(to: viewModel.selectShowMap)
VM
let selectShowMap: AnyObserver<Void>
let showShopMap: Observable<Void>
//Inside init
let _selectShowMap = PublishSubject<Void>()
selectShowMap = _selectShowMap.asObserver()
showShopMap = _selectShowMap.asObservable()
Coordinator
viewModel.showShopMap
.subscribe(onNext: { _ in self.showShopMap()})
.disposed(by: userShopVC.disposeBag)
Is it possible to refactor above code? rather than using PublishSubject is there any other way to do what i am doing using PublishSubject
My VC, VM & Coordinator Flow
Coordinator
func showLoginScreen(logout: Bool = false) {
guard let viewController = LoginViewController.instantiate(storyboard: .main) else { return }
viewController.viewModelFactory = { inputs in
let viewModel = LoginViewModel(inputs: inputs)
viewModel.showHome
.subscribe(onNext: { isLogged in
if isLogged {
self.showHomeScreen()
}
})
.disposed(by: viewController.disposeBag)
inputs.showOnboarding
.subscribe(onNext: { _ in
self.showOnboardingScreen()
})
.disposed(by: viewController.disposeBag)
return viewModel
}
navController.pushViewController(viewController, animated: true)
VC
var viewModelFactory: (LoginViewModel.UIInputs) -> LoginViewModel = { _ in fatalError("factory not set")}
let inputs = LoginViewModel.UIInputs(userNumber: txtUserNumber.rx.text.orEmpty.asDriver(),
password: txtPassword.rx.text.orEmpty.asDriver(),
loginTapped: btnLogin.rx.tap.asSignal(),
userNumberLostFocus: txtUserNumber.rx.controlEvent(.editingDidEnd).asSignal(),
passwordLostFocus: txtPassword.rx.controlEvent(.editingDidEnd).asSignal(),
indicator: indicator,
showOnboarding: btnShowOnboarding.rx.tap.asObservable())
VM
struct UIInputs {
let userNumber: Driver<String>
let password: Driver<String>
let loginTapped: Signal<Void>
let userNumberLostFocus: Signal<Void>
let passwordLostFocus: Signal<Void>
let indicator: ActivityIndicator
let showOnboarding: Observable<Void>
}
init(inputs: UIInputs) {}
Assuming the view controller owns and instantiates the view model, you could pass the tap control event as an observable to the view model initializer, which then exposes it as an observable for the coordinator to subscribe to:
// VC:
let viewModel = ViewModel(..., showShopMap: btnShowShopMap.rx.tap.asObservable())
// VM:
let showShopMap: Observable<Void>
init(..., showShopMap: Observable<Void>) {
self.showShopMap = showShopMap
}
I try not to use subjects whenever possible and instead just expose transformed observables that were passed in.
I found very easy and simple way to solve my issue and avoid using Subject, As there was no logic related to my button in VM, I don't need pass my Button tap to my VM either by using Observable or using Subject. Instead I directly accessed my button in my Coordinator like this:
viewController.btnShowOnboarding.rx.tap
.subscribe(onNext: { _ in
self.showOnboardingScreen()
})
.disposed(by: viewController.disposeBag)
I have 2 questions:
What difference between 'bind(onNext:' and 'subscribe(onNext:'?
struct Info {
var index: Int?
var data: String?
}
let infoData: BehaviorRelay<Info> = BehaviorRelay<Info>(value: Info())
var osInfo: Observable<String> { return self.infoData.map({ return $0.data }).distinctUntilChanged() }
osInfo.bind { (target) in
print("bind!")
}.disposed(by: self.disposeBag)
osInfo.subscribe { (target) in
print("subscribe!")
}
.disposed(by: self.disposeBag)
a has no asObservable(), but well executable. What is difference a and b?
a. var osInfo: Observable<String> { return self.infoData.map({ return $0.data }).distinctUntilChanged() }
b. var osInfo: Observable<String> { return self.infoData.asObservable().map({ return $0.data }).distinctUntilChanged() }
What difference between 'bind(onNext:' and 'subscribe(onNext:'?
If we check out implementation of bind(...) we found that it does nothing else but just uses subscribe(...) underhood and crashes in Debug with error:
/**
Subscribes an element handler to an observable sequence.
In case error occurs in debug mode, `fatalError` will be raised.
In case error occurs in release mode, `error` will be logged.
- parameter onNext: Action to invoke for each element in the observable sequence.
- returns: Subscription object used to unsubscribe from the observable sequence.
*/
public func bind(onNext: #escaping (E) -> Void) -> Disposable {
return subscribe(onNext: onNext, onError: { error in
rxFatalErrorInDebug("Binding error: \(error)")
})
}
By using bind(onNext) you can express that stream should never emit error and you interested only in item events.
So you should use subscribe(onNext:...) when you interested in error / complete / disposed events and bind(onNext...) otherwise. But since it is part of RxCocoa and not RxSwift I usually use subscribe everywhere.
a has no asObservable(), but well executable. What is difference a and b?
map(...) is function declared on ObservableType and returning new Observable
Let's start from ObservableType.
ObservableType is protocol that require only one method: subscribe(...), this allow him to create default implementation of func asObservable().
For you it means that you can create Observable from any type that conform to ObservableType.
/// Represents a push style sequence.
public protocol ObservableType : ObservableConvertibleType {
func subscribe<O: ObserverType>(_ observer: O) -> Disposable where O.E == E
}
extension ObservableType {
/// Default implementation of converting `ObservableType` to `Observable`.
public func asObservable() -> Observable<E> {
// temporary workaround
//return Observable.create(subscribe: self.subscribe)
return Observable.create { o in
return self.subscribe(o)
}
}
}
So each time you call asObservable() underhood RxSwift just creates new Observable wrapper around your stream.
And if you check source of BehaviourRelay you will find that it conform to ObservableType as well. So you can create Observable from it anytime:
public final class BehaviorRelay<Element>: ObservableType { ... }
Now lets check map function:
extension ObservableType {
/**
Projects each element of an observable sequence into a new form.
- seealso: [map operator on reactivex.io](http://reactivex.io/documentation/operators/map.html)
- parameter transform: A transform function to apply to each source element.
- returns: An observable sequence whose elements are the result of invoking the transform function on each element of source.
*/
public func map<R>(_ transform: #escaping (E) throws -> R)
-> Observable<R> {
return self.asObservable().composeMap(transform)
}
}
As expected map just call asObservable() inside and operate on new Observable.
If we "unwrap" map call we will get:
var osInfoA: Observable<String> {
return infoData
.asObservable()
.composeMap { $0.data }
.distinctUntilChanged()
}
var osInfoB: Observable<String> {
return infoData
.asObservable()
.asObservable()
.composeMap { $0.data }
.distinctUntilChanged()
}
Sure it will not compile since composeMap is internal function but you got main idea.
Calling asObservable before other operators is redundant (most operators defined on ObservableType) and just add small overhead.
I'm trying to convert my app from Swift 1.2 to Swift 2.0 and I'm encountering the following error:
class B {
func test() -> Promise<A> {
return Promise<A> { (fulfill, reject) -> Void in
anotherPromise.then { _ -> Void in
return fulfill(A()) // Class declaration cannot close over value 'fulfill' defined in outer scope
}
}
}
}
How can I make B (or the then closure) capture fulfill properly?
PS: Promise comes from PromiseKit, and I'm running Xcode 7 beta 1.
You should be able to workaround this by assigning fulfill to a local that you capture instead.
class B {
func test() -> Promise<A> {
return Promise<A> { (fulfill, reject) -> Void in
let innerFulfill = fulfill // close on this instead
anotherPromise.then { _ -> Void in
return innerFulfill(A()) // Class declaration cannot close over value 'fulfill' defined in outer scope
}
}
}
}