RxSwiftでクロージャを減らす: APIリクエストのcompletionHandlerをObservableにしてみる


RxSwiftを適用する箇所として、クロージャをObservableに切り替えるのが最近良いなと思ったので掲載。

APIクライアントからレスポンスを受けてUIを更新する操作はクロージャを使いますがこれをObservableを使って書き換えてみました。

Before

import UIKit

class ViewController: UIViewController {
    private var label: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        label = UILabel(frame: view.frame)
        label.numberOfLines = 10

        fetch(completion: { [weak self] data in
            guard let strongSelf = self else { return }
            strongSelf.label.text = String(data: data, encoding: .ascii)
            strongSelf.view.addSubview(strongSelf.label)
        })
    }

    private func fetch(completion: @escaping ((Data) -> Void)) {
        let request = URLRequest(url: URL(string: "https://www.google.com")!)
        URLSession.shared.dataTask(with: request, completionHandler: { data, _, error in
            guard
                error == nil,
                let data = data
            else { return }
            completion(data)
        }).resume()
    }
}

After

import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {
    private var label: UILabel!
    private let disposeBag = DisposeBag()

    enum MyError: Error {
        case APIClientFailed
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        label = UILabel(frame: view.frame)
        label.numberOfLines = 10

        fetch().asObservable().subscribe(onNext: { [weak self] data in
            guard let strongSelf = self else { return }
            strongSelf.label.text = String(data: data, encoding: .ascii)
            strongSelf.view.addSubview(strongSelf.label)
        }).disposed(by: disposeBag)
    }

    private func fetch() -> Observable<Data> {
        return Observable<Data>.create { observer in
            let request = URLRequest(url: URL(string: "https://www.google.com")!)
            URLSession.shared.dataTask(with: request, completionHandler: { data, _, error in
                guard
                    error == nil,
                    let data = data
                else { return observer.on(.error(MyError.APIClientFailed)) }
                observer.on(.next(data))
                observer.on(.completed)
            }).resume()

            return Disposables.create()
        }
    }
}

こういう風に作っておくと、一度に2つ以上のエンドポイントにリクエストを送ってその内容をマージしたくなったり、リクエスト前後にバリデーションが必要になったりしたときにRxSwiftの能力を発揮して複雑性を増やさずにコードを書ける気がしています。