20

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>>
manniL
  • 7,157
  • 7
  • 46
  • 72
Yaman
  • 3,949
  • 4
  • 38
  • 60

1 Answers1

13

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.

barndog
  • 6,975
  • 8
  • 53
  • 105
  • But all UIKit signal aren't what they call "hot observable" ? I mean, should they be emitting all the time and never getting deallocated ? – Yaman May 07 '16 at 14:02
  • Moreover, in my second example I cast the all thing to a `Driver` so theoretically, shouldn't the signal never be deallocated as well ? – Yaman May 07 '16 at 14:10
  • Your signin tap observable may not be hot that's why it only might fire once. I'd use ReactiveCocoa over RxSwift, much easier to understand. – barndog May 11 '16 at 01:31
  • 3
    Hey guys, I would recommend, to read this [article](http://davesexton.com/blog/post/Hot-and-Cold-Observables.aspx). To better understand what "cold" and "hot" means – Serg Dort May 12 '16 at 12:26