Search code examples
rx-swift

Is there a way to simplify data trasforming?


There is a function that calls the API from the server to return Observable<DataFromServer>.

I want to transform it into necessary data in UI and make it Observable<[DataForUI>.
I made a sample as below, but I want to simplify the transforming part.

Could you tell me if there is a way?


// Models
struct DataFromServer {
  var name: String
  var score: Int
}

struct DataForUI {
  var displayName: String
  var displayScore: String
}

// API function
func fetch() -> Observable<[DataFromServer]> {
  // ...
}

// output
var resultData: PublishRelay<[DataForUI]> = PublishRelay()

// Sample Code
ActionSubject // Trigger for fetch()
  .flatMapLatest { fetch() }
  // I want to simplify the code below.
  .flatMap { data -> Observable<[DataForUI]> in
    return Observable.just(data.map {
      DataForUI(displayName: “convert \($0.name)”, displayScore: “convert \($0.score)”)
    })
  }
  .bind(to: resultData)
  .disposed(by: disposeBag)

Solution

  • You have a bunch of different options...

    First, I would make an extension to handle the actual conversion:

    extension DataForUI {
        init(serverData: DataFromServer) {
            displayName = "convert \(serverData.name)"
            displayScore = "convert \(serverData.score)"
        }
    }
    

    Then you could just map/map:

    func sampleCode(actionSubject: Observable<Void>, disposeBag: DisposeBag) {
        actionSubject
            .flatMapLatest { fetch() }
            .map { $0.map { DataForUI(serverData: $0) } }
            .bind(to: resultData)
            .disposed(by: disposeBag)
    }
    

    This is the same idea, but passing in the init method instead of a closure:

    func sample1Code(actionSubject: Observable<Void>, disposeBag: DisposeBag) {
        actionSubject
            .flatMapLatest { fetch() }
            .map { $0.map(DataForUI.init(serverData:)) }
            .bind(to: resultData)
            .disposed(by: disposeBag)
    }
    

    You could write an extension on the Observable to handle the map/map operation:

    extension ObservableType where Element: Sequence {
        func mapArray<Result>(_ transform: @escaping (Self.Element.Element) throws -> Result) -> RxSwift.Observable<[Result]> {
            map { try $0.map(transform) }
        }
    }
    

    Then use the extension like this:

    func sample2Code(actionSubject: Observable<Void>, disposeBag: DisposeBag) {
        actionSubject
            .flatMapLatest { fetch() }
            .mapArray { DataForUI(serverData: $0) }
            .bind(to: resultData)
            .disposed(by: disposeBag)
    }
    

    Or the point-free version:

    func sample3Code(actionSubject: Observable<Void>, disposeBag: DisposeBag) {
        actionSubject
            .flatMapLatest(fetch)
            .mapArray(DataForUI.init(serverData:))
            .bind(to: resultData)
            .disposed(by: disposeBag)
    }