Story about us migrating from RxSwift to Swift Concurrency
Once upon a time there’s a team who changed the supported iOS version to iOS 13 as the minimum version. So they could upgrade their RxSwift library to RxSwift version 6.5.0 but some people from the team want to try to migrate to Swift Concurrency.
That makes us want to change a little bit of our code to Swift Concurrency to modernize our code. Because the project uses RxSwift and looks like Apple could change the game with Combine + Swift Concurrency to handle reactive & asynchronous tasks, so it will be good if we could remove RxSwift faster & easier and use Apple/Swift native framework in the future.
And the journey started
Where to start ??
The thought process on this matter is quite simple. They don’t want to break lots of things so they decided to find this criteria:
Simple class with minimum dependencies
How to do it ??
What they found is a class with a simple RxSwift extension for URLSession with some logic in it.
For this article I ignore the logic & focus on the step by step how we approach to change the code to Swift Concurrency.
1. Here’s an example method to get Data using RxSwift that we’re going to change.
2. Make async/await version for the target method
The async/await version for our URLSession.shared.data(for:) method is needed so they could change the RxSwift implementation to async/await version later.
Here, we are mapping the error to RxCocoaURLError because mostly, when you use URLSession from RxSwift, some of the code that uses the data(for:) method still using RxCocoaURLError.
From this step, we actually could remove the old RxSwift version method and change it with our async/await method. But, it’s risky because the code that uses data(for:) will break because the return type is changed from Observable to Data.
How do they solve this ? Just make a wrapper for async/await to ObservableType then.
3. Make extension of ObservableType to wrap async/await using Observable.
Remember we are not going to change all of our code to async/await ? Let’s wrap our async/await to Observable !
So, we could implement the code using async/await, but we are still returning Observable. Sounds strange ? I’ll explain later why we need this. From now on, we call this function Observable.task.
4. Change RxSwift implementation using Observable.task
On this step, they already achieve their goals to use async/await. But the problem is the return type is still Observable.
If you ( the reader ) really committed to change the code using async/await, try adding deprecated message the method so everyone who still uses RxSwift implementation (in this case Observable) on this method, will get this warning & they need to fix it (please do this guys).
5. Change caller of the target method to use the async/await version.
Eventually, when no one calls the RxSwift version, they need to delete the method & Observable.task when it’s not needed ( It’s a long run. Brace yourself ).
How could they know that the implementation is not breaking anything ?
- Try to run the test without changing anything on the test file after wrapping it inside Observable.task and it should be a success. That indicates the Observable.task and async/await implementation is working as expected.
What to do from now on ?
- Make a wrapper for another ObservableType like Single/Maybe.
- Rinse & repeat with all of the methods with DEPRECATED warning. (let’s this guys)
Any issues ?
SWIFT TASK CONTINUATION MISUSE: value leaked its continuation!
The issue is already solved by this MR & will be included on the next release.