Perhaps most people may be familiar with API implementation using closure as a callback, today I would like to share with you how to use RxSwift to implement the API. In this article, I use Moya to build API structure, Moya as a built-in network abstraction layer with base Alamofire, which helps us simplify and clear more API structure.
About RxSwift
RxSwift is not new but it is not old, I have dev iOS for a relatively time, but only a few months ago I have been exposed to RxSwift and apply it to my projects. RxSwift basically, it simplifies asynchronous programming by allowing you to interact and process with data from incoming events in a sequential manner. To be honest, Rx has a wide application in many situations, but we only need to remember one thing, that Rx helps us programmatically asynchronously. If any code is asynchronous there you can apply Rx.
Code
API Structure
Using Moya, we can write very easy API structure and clear parts of it, I take an example with a simple API as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | import Moya enum API { case getBook(Int) // ... more APIs } extension API: TargetType { static let baseUrl = "http://localhost:3000" static let apiVersion = "/api/v1" var baseURL: URL { return URL(string: API.baseUrl + API.apiVersion)! } var path: String { switch self { case .getBook(let id): return "/books/(id)" } } var method: Moya.Method { switch self { case .getBook: return .get } } var sampleData: Data { return "{}".data(using: .utf8)! } var task: Task { switch self { case .getBook: return Task.requestPlain } } var headers: [String : String]? { return nil } } |
and an APIProvider class, often written to MoyaProvider config.
1 2 3 4 5 6 7 8 | import Moya class APIProvider { static let shared = MoyaProvider() // trong lớp này vốn có rất nhiều thứ nhng mình xóa đi để đơn giản hóa ví dụ } |
Base Service
So we have a simple API that has been structured above, the next thing is to write the BaseService class, in both ways: normally (using closures as callbacks) and using RxSwift:
import RxSwift class ResponseError { static let invalidJSONFormat = NSError(domain: "", code: 600, userInfo: [NSLocalizedDescriptionKey: "Invalid JSON Format"]) } class BaseService { // Cách thông thường static func requestJson(api: API, completion: @escaping ([String: Any]?, Error?) -> Void) { APIProvider.shared.request(api) { result in do { switch result { case .success(let response): let json = try response.mapJSON() if let jsonDict = json as? [String: Any] { completion(jsonDict, nil) } else { throw ResponseError.invalidJSONFormat } case .failure(let error): throw error } } catch { completion(nil, error) } } } // Sử dụng RxSwift static func requestJsonRx(api: API) -> Observable<[String: Any]> { return Observable.create({ observer -> Disposable in let request = APIProvider.shared.request(api, completion: { result in do { switch result { case .success(let response): let json = try response.mapJSON() if let jsonDict = json as? [String: Any] { observer.onNext(jsonDict) observer.onComplete() } else { throw ResponseError.invalidJSONFormat } case .failure(let error): throw error } } catch let error { observer.onError(error) observer.onComplete() } }) return Disposables.create { request.cancel() } }) } }
Child services
Let’s say we have a Book model that can be initialized from json thanks to ObjectMapper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import RxSwift class BookService: BaseService { // Theo cách thông thường static func getBook(id: Int, completion: @escaping (Book?, Error?) -> Void) { requestJson(.getBook(id), completion: { (json, error) in if let error = error { completion(nil, error) } else if let book = Book(JSON: json) { completion(Book(JSON: json), nil) } else { completion(nil, ResponseError.invalidJSONFormat) } }) } // Sử dụng RxSwift static func getBookRx(id: Int) -> Observable&lt;Book> { return requestJsonRx(.getBook(id)).map({ json in if let book = Book(JSON: json) { return book } else { throw ResponseError.invalidJSONFormat } }) } } |
Call API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import RxSwift class ViewController: UIViewController { var disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() // Cách thông thường BookService.getBook(id: 100, completion: { (data, error) in // Thao tác với data và error }) // Reactive Style BookService.getBookRx(id: 100) // Làm gì đó hay ho với các operator của RxSwift // ... // Sau đó: .subscribe(onNext: { book in // Xử lý book object }, onError: { error in // Xử lý nếu gặp lỗi }, onCompleted: { // Xử lý khi complete }, onDisposed: { // Xử lý khi dispose }.disposed(by: disposeBag) } |
Conclusion
I have written the usual code attached for you to have a better view, but to say that using reactive is better or the traditional way is better, I did not identify. Basically reactive is just a code style, but the problem can still be solved in many ways, those who like it hope the article will be useful for you, and those who do not like or have never used it. Can bookmark reference. Have a nice day.