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 deinit
ed. 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
What Is Darwinboolean Type in Swift
Mfmailcomposeviewcontroller Error [Mc] Filtering Mail Sheet Accounts for Bundle Id
Swift Firestore Check If Documents Exists
Showing Notification Banner on MAC with Swift
Classes in Swift Files Inside Folder References Not Seen by Xcode 10's Compiler
Generic and (Early) Binding in Swift 1.2
Cannot Load Module Coredata as Coredata
Using Nil-Coalescing Operator with Try? for Function That Throws and Returns Optional
How to Pass a Class and Method to Create an Instance of a Class
Loading Multiple Google Interstitial Ads Makes App Crash
In Swift, How to Extend a Typealias
API Violation - Multiple Calls Made to -[Xctestexpectation Fulfill]
Ambigious Reference to Member Request() Issues with Alamofire After Migration to Swift 3
Swiftui Overlay Blocking List Scroll Events
How to Print Escape Sequence Characters in Swift
Swift Combine: What Are Those Multicast Functions for and How to Use Them
How to Set Alignment for Wkinterface Label Using Setattributedtext