Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxSwift - Misundestanding some concept

Tags:

I'm a very beginner with RxSwift and I'm trying to begin with a simple login screen. So I have 2 text fields and a login button, which is bind to a PublishSubject so every time I tap the button, I'll send a network request to perform authentication.

Since authentication may fails, I went with a Driver so I could replay my request each time I click the button.

I have 2 version of what I think is the same code but one works and one doesn't. I'm trying to understand what happens behind the scene.

Here the first version which works (request every time I touch the button) :

let credentials = Driver.combineLatest(email.asDriver(), password.asDriver()) { ($0, $1) }
self.signIn = signInTaps
    .asDriver(onErrorJustReturn: ())
    .withLatestFrom(credentials)
    .flatMapLatest { email, password in // returns Driver<Result<AuthenticateResponse, APIError>>
        return provider.request(.Authenticate(email: email, password: password))
            .filterSuccessfulStatusCodes()
            .mapObject(AuthenticateResponse)
            .map { element -> Result<AuthenticateResponse, APIError> in
                return .Success(element)
            }
            .asDriver { error in
                let e = APIError.fromError(error)
                return Driver<Result<AuthenticateResponse, APIError>>.just(.Failure(e))
            }
            .debug()
    }

And here's the one which doesn't work (request fires only on first click) :

let credentials = Observable.combineLatest(email.asObservable(), password.asObservable()) { ($0, $1) }
self.signIn = signInTaps.asObservable()
    .withLatestFrom(c)
    .flatMapLatest { email, password in // returns Observable<AuthenticateResponse>
        return provider.request(.Authenticate(email: email, password: password))
            .filterSuccessfulStatusCodes()
            .mapObject(AuthenticateResponse)
    }
    .map { element -> Result<AuthenticateResponse, APIError> in // returns Observable<Result<AuthenticateResponse, APIError>>
        return .Success(element)
    }
    .asDriver { error in // returns Driver<Result<AuthenticateResponse, APIError>>
        let e = APIError.fromError(error)
        return Driver<Result<AuthenticateResponse, APIError>>.just(.Failure(e))
    }
    .debug()

For information, here's my properties declaration :

let email = Variable("")
let password = Variable("")
let signInTaps = PublishSubject<Void>()    
let signIn: Driver<Result<AuthenticateResponse, APIError>>
like image 492
Yaman Avatar asked May 02 '16 19:05

Yaman


People also ask

What is RxSwift used for?

RxSwift is a library for composing asynchronous and event-based code by using observable sequences and functional style operators, allowing for parameterized execution via schedulers.

What are RxSwift traits?

Traits are observables with a narrow set of behaviors. Traits are simply a wrapper struct with a single read-only Observable sequence property.

What is RxSwift subject?

There are four subject types in RxSwift: PublishSubject : Starts empty and only emits new elements to subscribers. BehaviorSubject : Starts with an initial value and replays it or the latest element to new subscribers.

What is RxSwift and combine?

RxSwift and Combine are reactive programming solutions whose purpose is to handle asynchronous events. RxSwift is the most popular framework whereas Combine is Apple's recently introduced built-in solution.


1 Answers

Let's break down what's happening in the first one (since they're mostly the same):

// 1.
let credentials = Driver.combineLatest(email.asDriver(), password.asDriver()) { ($0, $1) }
// 2.
self.signIn = signInTaps
    .asDriver(onErrorJustReturn: ())
    // 3.
    .withLatestFrom(credentials)
    // 4.
    .flatMapLatest { email, password in // returns Driver<Result<AuthenticateResponse, APIError>>
    return provider.request(.Authenticate(email: email, password: password))
        .filterSuccessfulStatusCodes()
        .mapObject(AuthenticateResponse)
        .map { element -> Result<AuthenticateResponse, APIError> in
            return .Success(element)
        }
        .asDriver { error in
            let e = APIError.fromError(error)
            return Driver<Result<AuthenticateResponse, APIError>>.just(.Failure(e))
        }
        .debug()
}
  1. You're combining the latest signals from email and password and combining them into a tuple of Strings.
  2. This is your sign in signal, the composite of all the following signals.
  3. You're combining the latest results from credentials with a tap of a button.
  4. Every time the button is tapped or the email/password emit, you're cancelling the current signal and creating a new one which makes a call (using Moya), filters by successful status codes, mapping the object, and handling success and error.

The second example is mostly the same except you're using observables instead of drivers. Check on signInTaps and see if you're receiving events every time you tap the button. It could be that somewhere down the line the signal is deallocating and really the only difference between the two versions is the use of drivers and observables.

Also keep in mind that using a driver is just syntactic sugar over an observable.

let intDriver = sequenceOf(1, 2, 3, 4, 5, 6)
.asDriver(onErrorJustReturn: 1)
.map { $0 + 1 }
.filter { $0 < 5 }

is the same as

let intObservable = sequenceOf(1, 2, 3, 4, 5, 6)
.observeOn(MainScheduler.sharedInstance)
.catchErrorJustReturn(1)
.map { $0 + 1 }
.filter { $0 < 5 }
.shareReplay(1)

So when you use observables over drivers, you're losing .observeOn and shareReplay. It could be that with drivers you're just seeing the replay and cached values.

like image 195
barndog Avatar answered Sep 29 '22 23:09

barndog