RxSwift Safety Manual 📚
RxSwift gives you a lot of useful tools to make your coding more pleasurable, but it also can bring you a lot of headaches and… bugs 😱 After three months of using it actively I think I can give you some tips to avoid the problems I encountered.
Side Effect in computer science may be hard to understand, because it’s a very broad term. I think this Stackoverflow thread does a good job of explaining it.
So basically, a function/closure/… is said to have a side effect if it changes the state of the app somewhere.
In the context of RxSwift:
Why is that important in RxSwift? Because a cold❄️ signal (like the one we just created) starts new work every time it’s observed !
Let’s observe on
We would expect it to print
2, right? Wrong. It prints
3, because each subscription creates a separate execution, so the code inside the closure is executed twice and the side effect(counter incrementation) happens twice.
It means that if you put a network request inside, it would execute two times!
How do we fix this? By turning the cold observable into a hot one 💡! We do this using
refCount operators. Here’s a whole tutorial explaining this in detail.
Most of time time it’s enough if you just use the more high level
shareReplay operator. It uses the
refCount operator and
refCount is like
connect, but it’s managed automatically - it stars when there’s at least one observer.
replay is useful to emit some elements to observers “late to the party”.
When observing in the view controller and updating the UI you never know on which thread/queue does the subscription happen. You always have to make sure it’s happening on the main queue, if you’re updating UI.
If you have a stream of observables bound together and there’s an error event on the far end all the observables will stop observing! If on the first end there’s UI, it will just stop responding. You have to carefully plan your API and think of what’s going to happen when
error events are passed in your observables.
importantText sends an
error event for some reason, the binding/subscription will be disposed.
If you want to prevent that from happening you use
Driver is an
shareReplay operators already applied. If you want to expose a secure API in your view model it’s a good idea to always use a
Preventing memory leaks caused by reference cycles takes a lot of patience. When using any variable in the observe closure it gets captured as a strong reference.
The view controller has a strong reference to the view model. And now, the view model has a strong reference to the view controller because we’re using
self in the closure. Pretty much basic Swift stuff.
Here’s a small tip:
[unowned self] without worrying it might be dangerous when adding disposable to
self.disposeBag. If self is
nil, then the dispose bag is
nil, so the closure will never be called when
self is nil - app will never crash and you don’t have to deal with optionals 🤗
self is not the only case you have to watch out for! You have to think of every variable that gets captured in the closure.
It might get very complex, that’s why it’s a very good idea to keep closures short! If a closure is longer than 3-4 lines consider moving the logic to a separate method - you’ll see all the “dependencies” clearly and you’ll be able to decide what you want to capture weakly/strongly.
Managing your subscriptions
Remember to always clear the subscriptions you don’t need anymore. I had an issue where I didn’t clear my subscriptions, the cells were reused, new subscriptions were created each time causing some very spectacular bugs.
RxSwift is very complex, but if you set your own rules in the project and adhere to them, you should be fine 😇 It’s very important to be consistent in the RxSwift API you’re exposing for each layer - it helps with catching bugs.
A passionate iOS dev always trying to get to the bottom of stuff.