Manually Disposing a Disposebag in Rxswift

Manually disposing a DisposeBag in RxSwift

You have to just change reference to your disposeBag object.
Make it nil or assign new object to disposeBag.

All request will be cancelled.

should I manually dispose a UIButton instance in RxSwift?

UIButton's tap will complete when the button is deinited and therefore doesn't need to be explicitly disposed (unless you want to cancel it before the button deinits.) There's certainly nothing wrong with adding the subscription to a dispose bag, but it's not necessary.

_ = btn.rx.tap
.subscribe(onNext: {
// do something when the button is tapped.
})

The above is how you handle it if you don't want the disposeBag.

RxSwift Disposing one subscription invokes dispose of another subscription

When your FutureSpendingsHeaderView is deinitialized, whatever view that is the source of infoMessageAction is also being deinitialized, and that view emits a completed event at that time. That completed event is passed on to infoData which then emits its own completed event.

Once an Observable has emitted a completed event, it is done. It can't emit any more events. So the subscription to it is disposed.

Your answer @Alex changes the equation by changing the order that things inside your view get deinitialized. The disposeBag is getting deinitialized first now, which breaks the observable chain before the view sends the completed event.

A better solution would be to use a PublishRelay rather than a PublishSubject. Relays don't emit completed events.

Even better than that would be to get rid of the subject entirely and do something like:

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = FutureSpendingsHeaderView(frame: frame)
view.infoMessageAction
.compactMap { $0 }
.subscribe(onNext: { [weak self] (title, message) in
self?.presentInfoSheetController(with: title, message: message)
})
.disposed(by: view.disposeBag)
return view
}

How to dispose RxSwift observable in viewmodel

When you move forward, the view controller is not removed from the stack. It remains so that when the user taps the back button, it is ready and still in the same state as the last time the user saw it. That is why nothing is disposed.

Also, since you said you are still learning Rx, what you have is not anywhere near best practices. I would expect to see something more like this:

class LoginViewModel {

let areValidFields: Observable<[Bool]>

init(username: Observable<String>, password: Observable<String>, confirm: Observable<String>) {

let usernameValid = username.map { $0.count > 6 }
let passValid = password.map { $0.count > 6 }
let confirmValid = Observable.combineLatest(password, confirm)
.map { $0 == $1 }

areValidFields = Observable.combineLatest([usernameValid, passValid, confirmValid])
}
}

In your view model, prefer to accept inputs in the init function. If you can't do that, for e.g. if some of the inputs don't exist yet, then use a Subject property and bind to it. But in either case, your view model should basically consist only of an init function and some properties for output. The DisposeBag does not go in the view model.

Your view controller only needs to create a view model and connect to it:

class LoginViewController: UIViewController {

@IBOutlet weak var textFieldUserName: UITextField!
@IBOutlet weak var textFieldPassword: UITextField!
@IBOutlet weak var textFieldConfirmPassword: UITextField!

override func viewDidLoad() {
super.viewDidLoad()

let viewModel = LoginViewModel(
username: textFieldUserName.rx.text.orEmpty.asObservable(),
password: textFieldPassword.rx.text.orEmpty.asObservable(),
confirm: textFieldConfirmPassword.rx.text.orEmpty.asObservable()
)

let textFieldArray = [textFieldUserName!, textFieldPassword!, textFieldConfirmPassword!]

viewModel.areValidFields.subscribe(
onNext: { validArray in
for (field, valid) in zip(textFieldArray, validArray) {
if valid {
field.layer.borderColor = UIColor.clear.cgColor
}
else {
field.layer.borderColor = UIColor.red.cgColor
field.layer.borderWidth = 2.0
}
}
})
.disposed(by: bag)

}

private let bag = DisposeBag()
}

Notice that all of the code ends up in the viewDidLoad function. That's the ideal so you don't have to deal with [weak self]. In this particular case, I would likely put the onNext closure in a curried global function, in which case it would look like this:

class LoginViewController: UIViewController {

@IBOutlet weak var textFieldUserName: UITextField!
@IBOutlet weak var textFieldPassword: UITextField!
@IBOutlet weak var textFieldConfirmPassword: UITextField!

override func viewDidLoad() {
super.viewDidLoad()

let viewModel = LoginViewModel(
username: textFieldUserName.rx.text.orEmpty.asObservable(),
password: textFieldPassword.rx.text.orEmpty.asObservable(),
confirm: textFieldConfirmPassword.rx.text.orEmpty.asObservable()
)

let textFieldArray = [textFieldUserName!, textFieldPassword!, textFieldConfirmPassword!]

viewModel.areValidFields.subscribe(
onNext:update(fields: textFieldArray))
.disposed(by: bag)

}

private let bag = DisposeBag()
}

func update(fields: [UITextField]) -> ([Bool]) -> Void {
return { validArray in
for (field, valid) in zip(fields, validArray) {
if valid {
field.layer.borderColor = UIColor.clear.cgColor
}
else {
field.layer.borderColor = UIColor.red.cgColor
field.layer.borderWidth = 2.0
}
}
}
}

Notice here that the update(fields:) function is not in the class. That way we aren't capturing self and so don't have to worry about weak self. Also, this update function may very well be useful for other form inputs in the app.

Are rx relays never disposed?

A subscription will dispose when its source emits a stop event (either completed or error) or its disposable's dispose() is called. You are correct that Relays never emit stop events, so the only way to release the subscriptions sourced from a relay is to dispose them.

To dispose such a subscription, you can do it manually, or you can insert the disposable into a dispose bag. A DisposeBag calls dispose() on all the disposables it contains when it is deinited. So:

final class Example: UIViewController { 
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.myRelay
.subscribe(onNext: { /* do stuff */ })
.disposed(by: disposeBag)
}
}

Is sufficient, and best practice.

RxSwift: What is the best practice to use DisposeBag?

Defining DisposeBag in ViewController would help manage lifecycle of related Disposable

Here is a simple example, in a ViewController, subscription against an API Request (for UI related data) is held by a Dispoable in DisposeBag. When the ViewController dealloced, associated DisposeBag would dispose all its Disposables.

At that time, If the API Request is still pending, URLSessionTask.cancel would be called to avoid unnecessary forwarding (suppose the observable is well constructed).

This behavior is very complicated to achieve using traditional callback closures.

How can i remove “addDisposableTo” when i used RxSwift

As pointed out by Nimble, you have to do it; otherwise you will most certainly leak memory. The subscribe/unsubscribe couple at work relies on the same principle as native Swift Notification Center/KVO observers: if you open a "channel", you have to "close" it.

The middle ground here is to use NSObject-Rx (by Ash Furrow) that prevents you from creating a dispose bag everywhere you import RxSwift in an NSObject subclass: simply call .disposed(by: rx.disposeBag).

RxSwift Disposable property isDisposed always false or Disposable closure newer called to cancel worker task

Your default of false is incorrect. It should be true. But if you don't make the disposable weak then you don't need a default.

Observable<Void>.create { observer -> Disposable in
let disposable = BooleanDisposable(isDisposed: false)

Thread(block: { [disposable] in
while (!disposable.isDisposed) {
observer.onNext(())
sleep(1)
}
})
.start()

return disposable
}
.debug("here")
.take(5)
.subscribe()
.disposed(by: disposeBag)


Related Topics



Leave a reply



Submit