Understanding RxSwift Observable Chain - swift

This is my a simplified version of my code:
var myObservable: Observable<MyEnum>
var modelObservable: Observable<Model?>
myObservable = myButton.rx.tap.asSignal()
.asObservable()
.flatMapLatest {
getModel()
}.map { model in
print("this is called")
return model.prop == true ? MyEnum.first : MyEnum.second
}
func getModel() -> Observable<Model?> {
if let model = self.model.value {
return Observable.just(model)
}
createModel()
return modelObservable
}
myObservable.subscribe(onNext: { (enum) in
switch enum {
case .first:
self.presentFirst()
case .second:
self.presentSecond()
}
}).disposed(by: bag)
I was expecting this code to mean that whenever myButton is tapped, this code would run and print "this is called", however, "this is called" is printed also when myOtherObservable is triggered, even when myButton is not tapped. Why does this happen? This makes me think I don't understand Rx. Also, how would I make it behave so that it only runs when the myButton is tapped?

Related

How can i get controlEvent type in RxSwift?

I added textfield two controlEven with RxSwift I want to different things in a method.
textField.rx.controlEvent([.editingDidBegin, .editingDidEnd])
.asObservable()
.subscribe(onNext: { event in
// setFocusing(withType: ControlEvent)
})
private func setFocusing(type: ControlEvent) {
if type == .editingDidBegin {
//....
} else if type ==.editingDidEnd {
//....
}
}
I want to execute everything one method but how can I get controlEvent type? Is it possible?
Try this:
Observable.merge(
[.editingDidBegin, .editingDidEnd]
.map { event in
textField.rx.controlEvent(event).map { event }
}
)
.subscribe(onNext: setFocusing)
func setFocusing(type: UIControl.Event) {
if type == .editingDidBegin {
//....
} else if type == .editingDidEnd {
//....
}
}

Prevent self retain cycle in combine flatMap

I'm having a hard time figuring out how to create a Swift Combine pipeline that contains a .flatMap which has a reference to self. In order to prevent a retain cycle, this should be a [weak self] reference, but this does not work with a .flatMap.
Here is a simplified example showing my problem:
import Foundation
import Combine
class SomeService {
func someOperation(label: String) -> Future<String, Never> {
Future { promise in
print("Starting work for", label)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print("Finished work for", label)
promise(.success(label))
}
}
}
}
class SomeDataSource {
let someService = SomeService()
var cancellables = Set<AnyCancellable>()
deinit {
print("Deinit SomeDataSource")
}
func complexOperation() {
// First part 'defined' is inside the complexOperation method:
someService.someOperation(label: "First part")
// Second part is 'defined' in another method (it is shared with other tasks)
.flatMap { _ in self.partOfComplexOperation(label: "Second part") } // <--- This creates a retain cycle
.sink { label in
print("Received value in sink", label)
}
.store(in: &cancellables)
}
func partOfComplexOperation(label: String) -> Future<String, Never> {
someService.someOperation(label: label)
}
}
var someDataSource: SomeDataSource? = SomeDataSource()
someDataSource?.complexOperation()
someDataSource = nil
Output:
Starting work for First part
Finished work for First part
Starting work for Second part
Finished work for Second part
Received value in sink Second part
Deinit SomeDataSource
The problem here is that I want my SomeDataSource to be deinitialised right after starting the "First part" and not even starting the second part. So the output I'm looking for is:
Starting work for First part
Deinit SomeDataSource
Finished work for First part
I'm looking for some kind of combination of .flatMap and .compactMap. Does this exist? If I first .compactMap { [weak self] _ in self } I get the expected result, but maybe there is a better way?
import Foundation
import Combine
class SomeService {
func someOperation(label: String) -> Future<String, Never> {
Future { promise in
print("Starting work for", label)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print("Finished work for", label)
promise(.success(label))
}
}
}
}
class SomeDataSource {
let someService = SomeService()
var cancellables = Set<AnyCancellable>()
deinit {
print("Deinit SomeDataSource")
}
func complexOperation() {
// First part 'defined' is inside the complexOperation method:
someService.someOperation(label: "First part")
.compactMap { [weak self] _ in self }
// Second part is 'defined' in another method (it is shared with other tasks)
.flatMap { dataSource in dataSource.partOfComplexOperation(label: "Second part") }
.sink { label in
print("Received value in sink", label)
}
.store(in: &cancellables)
}
func partOfComplexOperation(label: String) -> Future<String, Never> {
someService.someOperation(label: label)
}
}
var someDataSource: SomeDataSource? = SomeDataSource()
someDataSource?.complexOperation()
someDataSource = nil
Output:
Starting work for First part
Deinit SomeDataSource
Finished work for First part
The solution here is to not retain self. You don't even want self in the flatMap so why retain it...
let label = someService.someOperation(label: "First part")
.flatMap { [someService] _ in
someService.someOperation(label: label)
}
Of course seeing all this work on someService implies that the service is missing some functionality. Seeing that the result of someOperation is being ignored might also be a red flag.
If you were truly in a situation where you had to use self, then the solution would look like this:
let foo = someOperation()
.flatMap { [weak self] in
self?.otherOperation() ?? Empty(completeImmediately: true)
}
Or you might consider something like:
someOperation()
.compactMap { [weak someService] _ in
someService?.otherOperation()
}
.switchToLatest()
Which will cancel otherOperation() if a new event comes through from someOperation().

Wait multiple observable requests to finish using RXSwift

I have a list of observables that are requests for google distance and duration info from an specific point. I'm trying to load my screen only when all this information is fetched, but my subscribe on next for those observables are never called (the line "observer.onNext(viewModel)" is called and has the information already fetched, only the subscribe(onNext) is not being called). How can I wait til those observables complete?
func stationInfoObservable(userLocation: CLLocationCoordinate2D, stations: [Station]) -> [Observable<GasStationTableCellViewModel>] {
var observables: [Observable<GasStationTableCellViewModel>] = []
for station in stations {
observables.append(Observable.create({ observer in
guard let toCoordinate = station.coordinate() else { return Disposables.create() }
self.mapDirections.routes(from: userLocation.asPlace(), to: toCoordinate.asPlace()) { routes, error in
if let error = error {
logger.error(error)
} else {
guard let leg = routes.first?.legs?.first else {
return
}
guard let distance = leg.distance?.text, let duration = leg.duration?.text else { return }
station.distanceInKMFromUserLocation = distance
station.distanceInMinutesFromUserLocation = duration
let viewModel = GasStationTableCellViewModel(station: station)
observer.onNext(viewModel)
observer.onCompleted()
}
}
return Disposables.create()
}))
}
return observables
}
I'm trying to subscribe this way (EDIT: I'm now trying to use zip, but the the drive / subscribe continues not being called):
Observable.zip(observables)
.asDriver(onErrorJustReturn: [])
.drive(onNext: { test in
print(test)
}, onCompleted: {
print("aa")
}).disposed(by: DisposeBag())
Based on your subscription code, it looks like you're not retaining the DisposeBag. You must retain this object because when it gets deallocated, all disposables it owns get immediately disposed. Try making it a property and use the property:
class MyClass {
let disposeBag = DisposeBag()
func setupSubscription() {
Observable.zip(observables)
.asDriver(onErrorJustReturn: [])
.drive(onNext: { test in
print(test)
}, onCompleted: {
print("aa")
}).disposed(by: disposeBag)
}
}

Observable<Bool> If else in RxSwift

I made Observable of Bool type as below
let allValid: Observable<Bool>
//All valid is combination of two more Observable<Bool>
allValid = Observable.combineLatest(checkBoxValid, reasonValid) { $0 && $1 }
Now I want to check when Done button is pressed, call respective method based on value of AllValid.
public func doneButtonPressed() {
//Here I have two methods, and to be called, when AllValid is true and false
//self.method1()
//self.method2()
}
Now how to make it. I cannot bind directly as it will trigger, and I want to trigger when Done is pressed.
The Rx way to do this would be to put this in your viewDidLoad
let isValid = doneButton.rx.tap.withLatestFrom(allValid)
isValid
.filter { $0 }
.subscribe(onNext: { _ in
// The button was tapped while the last value from allValid was true.
}
.disposed(by: bag)
isValid
.filter { !$0 }
.subscribe(onNext: { _ in
// The button was tapped while the last value from allValid was false.
}
.disposed(by: bag)

Implement a condition which is based on calls from different external IBActions?

I have these two IBActions in WorkoutsController.swift.
#IBAction func startWalkingButton() {
print("Walking start button pressed")
presentControllerWithName("Dashboard", context: sessionContext)
wSM!.startWorkout()
}
#IBAction func startCyclingButton() {
print("Cycling start button pressed")
presentControllerWithName("Dashboard", context: sessionContext)
wSM!.startWorkout()
}
They are calling the startWorkout() function in WorkoutSessionManager.swift
func startWorkout() {
self.healthStore.startWorkoutSession(self.workoutSession)
if ... {
print("startWorkout() called from startWalkingButton")
} else if ... {
print("startWorkout() called from startCyclingButton")
}
}
How do I create a condition to print out different print statements depending on which button function called the method? Should I use an if statement or switch statement?
I know there is already a print statement for the separate IBActions but I want to know if it's possible to do it in the reverse for redundancy.
Simply add one Bool parameter with your method startWorkout
func startWorkout(isFromWalking: Bool) {
if (isFromWalking) {
print("startWorkout() called from startWalkingButton")
}
else {
print("startWorkout() called from startCyclingButton")
}
}
Now call this function from startWalkingButton method with passing true
startWorkout(true)
and from startCyclingButton method with passing false
startWorkout(false)
Edit:
You haven't told that you have multiple option, then best option is to used enum in this case, create one enum like this and use that with the method
enum Workout {
case Walking
case Cycling
//Add case that you have
}
Now change the function like this
func startWorkout(workout: Workout) {
switch(workout) {
case .Walking :
print("Walking")
case .Cycling:
print("Cycling")
}
}
And call the function like this
self.startWorkout(.Walking)
self.startWorkout(.Cycling)
Simply add some sort of 'sender' parameter to your startWorkout() method.
Example:
// Hold a reference to your buttons, connected from IB
#IBOutlet var startWalkingButton: UIButton!
#IBOutlet var startCyclingButton: UIButton!
// here are your .TouchUpInside actions
// UIControl action methods receive the sender of the event as the first parameter (sender)
#IBAction func startWalkingButtonTouched(sender: AnyObject) {
...
startWorkout(sender)
}
#IBAction func startCyclingButtonTouched(sender: AnyObject) {
...
startWorkout(sender)
}
func startWorkout(sender: AnyObject) {
self.healthStore.startWorkoutSession(self.workoutSession)
switch sender {
case startWalkingButton:
print("startWorkout() called from startWalkingButton")
break
case startCyclingButton:
print("startWorkout() called from startCyclingButton")
break
default: ()
}
}
Hope this helps.
I feel you should probably use a block here. startWorkout method should accept an optional block. This approach avoids passing arguments and also avoids having if and case statements.
class Walking {
let workout = Workout()
func startWalkingButton() {
print("startWalkingButton")
workout.startWorkout() {
print("Walking Over")
}
}
}
class Cycling {
let workout = Workout()
func startCyclingButton() {
print("startCyclingButton")
workout.startWorkout() {
print("Cycling Over")
}
}
}
class Workout {
func startWorkout( afterWorkout: () -> Void ){
print("startWorkout")
afterWorkout()
}
}
let w = Walking()
w.startWalkingButton()