This tutorial will use all pieces of information we gathered from previous posts to refactor existing code to RxSwift. I created a base project. Simple iOS application displaying images in the collection view. Our "API" is in a separate framework to mimic app modularization. The starter is done without any reactive code, old-school callbacks, and code blocks we used to love;)
Part 1 will make sure image API is reactive compatible. In the second part, we will refactor the collection view to use reactive code and we will simplify the view controller and our collection cell.
Build and run the application, you should see a grid of "images" from placeholder API.
Adding reactive extension
Before we start, we need RxSwift. Add swift package with RxSwift to ApiClient in Xcode. You can follow directions from GitHub. If you want, use CocoaPods or Carthage.
We will make our legacy API reactive ready! To do that add new swift file to the ApiClient project. We want to have an easy and familiar way to use the new Rx extension for API. The best is to have the .rx
property available. To do that we need to add one protocol conformance to ImageApi class:
import RxSwift
import SampleApi
extension ImageApi: ReactiveCompatible { }
This is it! Now every ImageApi
instance has .rx
property. But it is empty. Let's add downloading images to the reactive extension below:
// 1
extension Reactive where Base: ImageApi {
// 2
var getImages: Single<[Image]> {
// 3
Single.create { single in
// 4
base.getImages { images, error in
if let error = error {
single(.failure(error))
return
}
guard let images = images else {
single(.failure(APIError.noData))
return
}
single(.success(images))
}
// 5
return Disposables.create { }
}
}
}
- With the power of extension with generic where clause, I'm adding a new variable only to
ImageApi
class, we can do that becauseImageApi
was declaredReactiveCompatible
. - Now, instead of method, I'll use the computed variable. I'm using single here because I know this call should just fetch images and then complete. There is no need for more events. Single ensures this.
- We are using the
.create { }
factory method. It passes a function that acceptsResult
. Our code can use this function to report value or error. base
will return an instance ofself
- let's call the actual fetching method. Inside callback code will report errors or successes with an array of images.- We need to return disposable here - we do nothing on dispose, so I left it empty.
This is all code we need to add reactivity to our "legacy" images API.
Refactor
Now we can use it. In ViewController
change couple of lines of code:
import RxSwift // Add import
class ViewController: UIViewController {
let disposeBag = DisposeBag() // add dispose bag
override func viewDidLoad() {
super.viewDidLoad()
...
// replace .getImages {} with rx counterpart:
api.rx.getImages
.observe(on: MainScheduler.instance)
.subscribe(onSuccess: { [unowned self] in
self.images = $0
})
.disposed(by: disposeBag)
}
...
// rest of class
I'm adding RxSwift
import and instead of using a callback, we use the .rx
extension. Instead of nested blocks of code for dispatching images assignment we have it by adding simple .observe(on: MainScheduler.instance)
.
And this is it for this time. Next time, with the power of RxCocoa, I'll refactor UI handling to be reactive! Stay tuned!
All changes can be found here.
Here you can find all RxSwift course posts: