let a = Just("a")
let b = Just("b")
_ = Publishers.CombineLatest(a, b).map { a, b in
print(a, b)
}
This is my simple CombineLatest test. I am calling this method in onAppear function.
However, my print(a,b) is not called. How should I fix my code to run print(a, b) ?
A Publisher doesn't produce values until it is given a Subscriber. The map operator isn't a Subscriber.
The easiest solution (as mentioned by Sajjon) is to use sink:
let a = Just("a")
let b = Just("b")
_ = Publishers.CombineLatest(a, b).sink { a, b in
print(a, b)
}
Please note that sink returns an AnyCancellable, and when that AnyCancellable is destroyed, it cancels the subscription. In this example, the _ = means I'm not saving that AnyCancellable, so it is destroyed immediately. That doesn't matter here, because all of the Publishers in this example (the two Justs and the CombineLatest) operate synchronously. They have published everything they will ever publish before the AnyCancellable is destroyed. But in general, you need to save the AnyCancellable if you're subscribing to a Publisher that can publish values asynchronously.
Related
I am learning about RxSwift and trying to understand the main difference between Observable and PublishSubject aka Sequence.
As far as I understood, Sequences / PublishesSubject act as Observable and Observer. You can subscribe to these and they emit notifications if the value changes. PublishesSubject can be changed.
My question is, what is the purpose of Observables then? I can only create Observable with a fix value with just() or from(). But I can not change these values or append an item, right? So my Observable will only emit the value I assigned to it in the init. Why do I need Observable then, when the actual value is immutable?
LetΒ΄s think of a function which returns an Observable<UIImage>. Instead of returning that Observable and then subscribe on next, I can just return an UIImage. Why do I need a Observable?
If I create following Observable:
let observable = Observable.of(1,2,3)
I would have a static Marble diagram.. my Observable will emit 1,2 and 3. And afterwards..? It is ended?
I am just not getting the reason why I should use Observable. Any help is appreciated.
To help you understand, look at Observable.create. You will see that it takes a closure. This closure executes every time an observer subscribes to it.
So if you wanted to implement just using create, you would do something like:
func just(_ value: Int) -> Observable {
return Observable.create { observer in
observer.onNext(value)
observer.onCompleted()
return Disposables.create()
}
}
Sure, that will just emit the static value very time, but you could do other things...
let random = Observable<Int>.create { observer in
observer.onNext(Int.random(in: 0..<10))
observer.onCompleted()
return Disposables.create()
}
The above will emit a random value and then complete. You could also make a network request in the closure, or you could call onNext every time a button is tapped. RxCocoa has a bunch of them already... So for example:
let result = loginButton.rx.tap
.withLatestFrom(Observable.combineLatest(usernameTextField.rx.text, passwordTextField.rx.text))
.map { URLRequest.login(credentials: $0) }
.flatMapLatest { URLSession.shared.rx.data(request: $0) }
.subscribe(onNext: { print("data:", $0) }
loginButton.rx.tap emits a next event every time the button is tapped.
usernameTextField.rx.text & passwordTextField.rx.text emit next events every time the user enters data into one of the text fields.
URLSession.shared.rx.data(request:) emits a value when the server responds with data. All of these are (forms of) Observables.
If you really don't like the mutable state of Subjects, you can use normal Observables. This comes in handy when you want to convert your ViewModel to a function instead of a class.
To answer this specific question of yours,
LetΒ΄s think of a function which returns an Observable<UIImage>. Instead of returning that Observable and then subscribe on next, I can just return an UIImage. Why do I need a Observable?
Answer is, imagine a situation where you can't immediately return a UIImage. This could be a result of a network operation or, expensive drawing operation that could take time. So it has to be asynchronous. In order to achieve this, you can use Observable<UIImage> instead of UIImage. That way you can immediately return Observable<UIImage> but your subscriber won't receive the UIImage until it's actually finished processing.
Without Observables, you have to pass a block to achieve the same result. This is just one example. There are many use cases.
I have an AnyPublisher property that emits values. When someone subscribes to it, is there a way to emit the last value immediately and let it listen for future values from there?
You should use a CurrentValueSubject for this and erase that to AnyPublisher
let publisher = CurrentValueSubject<Int, Never>(1).eraseToAnyPublisher()
let c = publisher.sink(receiveValue: { int in
print(int)
})
The code above would immediately print 1 since that's the current value of the CurrentValueSubject.
Say I have 2 functions with 2 different Observable return types :
func getWatchedMovies() -> Observable<[TraktMovie]>
func getDetails(id: Int, language: String) - > Observable<TMDbMovie>
I'd like to flatMap each value in my getWatchedMovies() request to be able to request the details of each movie like this (I'm not sure it's the best way to do it though..)
traktDataManager?
.getWatchedMovies()
.flatMap({ (traktMovies) -> Observable<[TraktMovie]> in
let moviesObs = Observable.from(traktMovies)
let movieDetails = moviesObs.flatMap {
self.tmdbDataManager!.getMovieDetails(id: $0.ids.tmdb, language: Device.lang)
}
})
The thing is, I need to add each TraktMovie to Realm AND update a TraktMovie property, named tmdbMovie, with the nested request value of type TMDbMovie in Realm too.
What I mean is :
first, I need to loop in my [TraktMovie] array to save each value of it in Realm (say an object named traktMovie)
for traktMovie in traktMovies {
let realm = try! Realm()
realm.write {
realm.add(traktMovie)
}
}
second, I need to retrieve the details of each TraktMovie object with the second request (e.g. getDetails(_ , _)) : with something like flatMap ?
third, I need to update each traktMovie object property as follow with the value retrieved with the getDetails request (say tmdbMovie for the retrieved value):
traktMovie.setValue(tmdbMovie, forKeyPath: "tmdbMovie")
Here I have an object retrieved from the first request(getWatchedMovies()) named traktMovie and I update one of its property named tmdbMovie with the object retrieved from the second request (getDetails(_, _)) also named tmdbMovie
The thing is my first request returns an array and the second only a single object.
If I return the TMDbMovie object, I got only one object with onNext event and I loose my [TraktMovie] array.
Hope I'm clear enough.
Help is really appreciated ! π
You can try to use Observable.zip for this as in example below:
getWatchedMovies()
.flatMap({ [unowned self] (traktMovies) -> Observable<[TraktMovie]> in
let movieDetails = traktMovies.flatMap { movie in
// you can save in realm here
return Observable.just(movie)
.withLatestFrom(self.getMovieDetails(id: 0, language: "")) { movie, details in
// here you have both movie & movieDetails
return movie
}
}
return Observable.zip(movieDetails, { return $0 })
})
It may be a bit risky, if one of getMovieDetails will fail it will fail whole stream, as well it will require all getMovieDetails to emit onNext event in order that zipped Observable to emit a value.
I'm looking for something like this:
let observable = PublishSubject<String>()
observable.onNext("1")
observable.onCompleted()
_ = observable.subscribeNext { s in
print(s)
}
So I want to subscribe to the Observable after it has already been completed and still get the values (or just the last value).
You're using the wrong Subject to get what you want. Take a look at the descriptions of the Subjects:
PublishSubject: Broadcasts new events to all observers as of their time of the subscription.
ReplaySubject: Broadcasts new events to all subscribers, and the specified bufferSize number of previous events to new subscribers.
BehaviorSubject: Broadcasts new events to all subscribers, and the most recent (or initial) value to new subscribers.
Variable: Wraps a BehaviorSubject, so it will emit the most recent (or initial) value to new subscribers. And Variable also maintains current value state. Variable will never emit an Error event. However, it will automatically emit a Completed event and terminate on deinit.
So, don't use PublishSubject, since it only broadcasts new events upon subscription.
You can use ReplaySubject to get all previous events, or you can use ReplaySubject, BehaviorSubject, or Variable to get the most recent value.
let disposeBag = DisposeBag()
// ALL previous events
let subject = ReplaySubject<String>.createUnbounded()
// or use this to get just the last, say, 4:
// let subject = ReplaySubject<String>.create(bufferSize: 4)
// or use one of these to get the most recent:
// let subject = ReplaySubject<String>.create(bufferSize: 1)
// let subject = BehaviorSubject(value: "blah")
// let subject = Variable("blah")
subject.onNext("blah")
subject.onNext("foo")
subject.onCompleted()
subject
.asObservable()
.subscribeNext { print($0) }
.addDisposableTo(disposeBag)
Outputs:
blah
foo
I learn the sample code in RxSwift. In the file GithubSignupViewModel1.swift, the definition of validatedUsername is:
validatedUsername = input.username //the username is a textfiled.rx_text
.flatMapLatest { username -> Observable<ValidationResult> in
print("-------->1:")
return validationService.validateUsername(username)
.observeOn(MainScheduler.instance)
.catchErrorJustReturn(.Failed(message: "Error contacting server"))
}
.shareReplay(1)
the validateUsername method is finally called the following method:
func usernameAvailable(username: String) -> Observable<Bool> {
// this is ofc just mock, but good enough
print("-------->2:")
let URL = NSURL(string: "https://github.com/\(username.URLEscaped)")!
let request = NSURLRequest(URL: URL)
return self.URLSession.rx_response(request)
.map { (maybeData, response) in
print("-------->3:")
return response.statusCode == 404
}
.catchErrorJustReturn(false)
}
Here is my confusion:
whenever I input a character quickly in the username textfield, message -------->1:, -------->2: showed, and a little later message -------->3: showed, but only showed one -------->3: message.
When I input characters slower, message -------->1:, -------->2:, -------->3: showed successively.
But when I change the flatMapLatest to flatMap, how many characters I input, I will get the same number of -------->3: message.
So how did the flatMapLatest work here?
How the flatMapLatest filter the early response from NSURLResponse ?
I read some about the flatMapLatest, but none of them will explain my confusion.
What I saw is something like:
let a = Variable(XX)
a.asObservable().flatMapLatest(...)
When changed a.value to another Variable, the Variable(XX) will not influence the subscriber of a.
But the input.username isn't changed, it is always a testfield.rx_text! So how the flatMapLatest work?
TheDroidsOnDroid's answer is clear for me:
FlatMapLatest diagram
Well, flatMap() gets one value, then performs long task, and when it
gets the next value, previous task will still finish even when the new
value arrives in the middle of the current task. It isnβt really what
we need because when we get a new text in the search bar, we want to
cancel the previous request and start another. Thatβs what
flatMapLatest() does.
http://www.thedroidsonroids.com/blog/ios/rxswift-examples-3-networking/
You can use RxMarbles app on Appstore to play around with operators.
It's not clear what your confusion is about. Are you questioning the difference between flatMap and flatMapLatest? flatMap will map to a new Observable, and if it needs to flatMap again, it will in essence merge the two mapped Observables into one. If it needs to flatMap again, it will merge it again, etc.
With flatMapLatest, when a new Observable is mapped, it overwrites the last Observable if there was one. There is no merge.
EDIT:
In response to your comment, the reason you aren't seeing any "------>3:" print is because those rx_request Observables were disposed before they could compete, because flatMapLatest received a new element, and this mapped to a new Observable. Upon disposal, rx_request probably cancels the request and will not run the callback where you're printing. The old Observable is disposed because it no longer belongs to anyone when the new one takes its place.
I find this https://github.com/ReactiveX/RxSwift/blob/master/Rx.playground/Pages/Transforming_Operators.xcplaygroundpage/Contents.swift to be useful
Transforms the elements emitted by an Observable sequence into Observable sequences, and merges the emissions from both Observable sequences into a single Observable sequence. This is also useful when, for example, when you have an Observable sequence that itself emits Observable sequences, and you want to be able to react to new emissions from either Observable sequence. The difference between flatMap and flatMapLatest is, flatMapLatest will only emit elements from the most recent inner Observable sequence.
let disposeBag = DisposeBag()
struct Player {
var score: Variable<Int>
}
let π¦π» = Player(score: Variable(80))
let π§πΌ = Player(score: Variable(90))
let player = Variable(π¦π»)
player.asObservable()
.flatMap { $0.score.asObservable() } // Change flatMap to flatMapLatest and observe change in printed output
.subscribe(onNext: { print($0) })
.disposed(by: disposeBag)
π¦π».score.value = 85
player.value = π§πΌ
π¦π».score.value = 95 // Will be printed when using flatMap, but will not be printed when using flatMapLatest
π§πΌ.score.value = 100
With flatMap, we get
80
85
90
95
100
With flatMapLatest, we get
80
85
90
100
In this example, using flatMap may have unintended consequences. After
assigning π§πΌ to player.value, π§πΌ.score will begin to emit
elements, but the previous inner Observable sequence (π¦π».score) will
also still emit elements. By changing flatMap to flatMapLatest, only
the most recent inner Observable sequence (π§πΌ.score) will emit
elements, i.e., setting π¦π».score.value to 95 has no effect.
flatMapLatest is actually a combination of the map and switchLatest
operators.
Also, I find https://www.raywenderlich.com/158205/rxswift-transforming-operators this to be useful
flatMap
keeps up with each and every observable it creates, one for each element added onto the source observable
flatMapLatest
What makes flatMapLatest different is that it will automatically switch to the latest observable and unsubscribe from the the previous one.
I think this diagram from Ray Wenderlich tutorial can help.
So if we think about an event being emitted from a search box as the user types each time an event is received flatMap would fire off a separate request even if a current request is in flight. flatMapLatest in contrast disposes of the first stream. In the wrapper around URLSession this calls cancel on the request. So if you type really quickly you should see fewer requests returning. There's a brilliant video explaining just this here: https://youtu.be/z8ukiv5flcw . Here's the source to the wrapper around URLSession (notice task.cancel on dispose):
public func response(request: URLRequest) -> Observable<(response: HTTPURLResponse, data: Data)> {
return Observable.create { observer in
// smart compiler should be able to optimize this out
let d: Date?
if URLSession.rx.shouldLogRequest(request) {
d = Date()
}
else {
d = nil
}
let task = self.base.dataTask(with: request) { data, response, error in
if URLSession.rx.shouldLogRequest(request) {
let interval = Date().timeIntervalSince(d ?? Date())
print(convertURLRequestToCurlCommand(request))
#if os(Linux)
print(convertResponseToString(response, error.flatMap { $0 as NSError }, interval))
#else
print(convertResponseToString(response, error.map { $0 as NSError }, interval))
#endif
}
guard let response = response, let data = data else {
observer.on(.error(error ?? RxCocoaURLError.unknown))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
observer.on(.error(RxCocoaURLError.nonHTTPResponse(response: response)))
return
}
observer.on(.next((httpResponse, data)))
observer.on(.completed)
}
task.resume()
return Disposables.create(with: task.cancel)
}
}