首页 > 解决方案 > 将 2 个 API 调用的结果与 RxSwift 结合起来,为相同的对象获取不同的属性

问题描述

我有一个名为Track的模型。它具有一组基本属性和一组扩展属性。通过搜索API 调用获取曲目列表及其基本属性,然后我需要使用这些曲目 ID进行另一个 API 调用以获取其扩展属性。

问题是如何最好地结合两个 API 调用的结果并将扩展属性填充到已经创建的Track对象中,当然还可以通过 ID 匹配它们(不幸的是,这两个调用结果中的属性名称不同)。请注意,在实际结果集中返回了更多的键 - 两个调用中的每一个都有大约 20-30 个属性。

追踪.swift

struct Track: Decodable {

// MARK: - Basic properties

let id: Int
let title: String

// MARK: - Extended properties

let playbackURL: String

enum CodingKeys: String, CodingKey {
    case id = "id"

    case title = "title"

    case playbackUrl = "playbackUrl"
}

init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)

    let idString = try container.decode(String.self, forKey: CodingKeys.id)
    id = idString.int ?? 0

    title = try container.decode(String.self, forKey: CodingKeys.title)

    playbackURL = try container.decodeIfPresent(String.self, forKey: CodingKeys.playbackUrl) ?? ""
}
}

ViewModel.swift

let disposeBag = DisposeBag()
var searchText = BehaviorRelay(value: "")
private let provider = MoyaProvider<MyAPI>()
let jsonResponseKeyPath = "results"

public lazy var data: Driver<[Track]> = getData()

private func searchTracks(query: String) -> Observable<[Track]> {
    let decoder = JSONDecoder()
    return provider.rx.request(.search(query: query))
        .filterSuccessfulStatusCodes()
        .map([Track].self, atKeyPath: jsonResponseKeyPath, using: decoder, failsOnEmptyData: false)
        .asObservable()
}

private func getTracksMetadata(tracks: Array<Track>) -> Observable<[Track]> {
    let trackIds: String = tracks.map( { $0.id.description } ).joined(separator: ",")
    let decoder = JSONDecoder()
    return provider.rx.request(.getTracksMetadata(trackIds: trackIds))
        .filterSuccessfulStatusCodes()
        .map({ result -> [Track] in

        })
        .asObservable()
}

private func getData() -> Driver<[Track]> {
    return self.searchText.asObservable()
        .throttle(0.3, scheduler: MainScheduler.instance)
        .distinctUntilChanged()
        .flatMapLatest(searchTracks)
        .flatMapLatest(getTracksMetadata)
        .asDriver(onErrorJustReturn: [])
}

.search API 调用的 JSON 结果结构如下:

{
  "results": [
    {
      "id": "4912",
      "trackid": 4912,
      "artistid": 1,
      "title": "Hello babe",
      "artistname": "Some artist name",
      "albumtitle": "The Best Of 1990-2000",
      "duration": 113
    },
    { 
      ...
    }
  ]
}

.getTracksMetadata API 调用的 JSON 结果结构如下:

[
  {
    "TrackID": "4912",
    "Title": "Hello babe",
    "Album": "The Best Of 1990-2000",
    "Artists": [
      {
        "ArtistID": "1",
        "ArtistName": "Some artist name"
      }
    ],
    "SomeOtherImportantMetadata1": "Something something 1",
    "SomeOtherImportantMetadata2": "Something something 2",
    "SomeOtherImportantMetadata3": "Something something 3"
  },
  { 
    ...
  }
]

标签: iosswiftrx-swiftmoya

解决方案


这里的解决方案是两阶段方法。首先,您应该为两个网络调用定义两个不同的结构,并为组合结果定义第三个结构。假设您选择:

struct TrackBasic {
    let id: Int 
    let title: String 
}

struct TrackMetadata {
    let id: Int // or whatever it's called.
    let playbackURL: String
}

struct Track {
    let id: Int 
    let title: String 
    let playbackURL: String
}

并像这样定义你的函数:

func searchTracks(query: String) -> Observable<[TrackBasic]>
func getTracksMetadata(tracks: [Int]) -> Observable<[TrackMetadata]>

现在您可以进行两次调用并将来自两个单独端点的数据包装到组合结构中:

searchText
    .flatMapLatest { searchTracks(query: $0) }
    .flatMapLatest { basicTracks in
        Observable.combineLatest(Observable.just(basicTracks), getTracksMetadata(tracks: basicTracks.map { $0.id }))
    }
    .map { zip($0.0, $0.1) }
    .map { $0.map { Track(id: $0.0.id, title: $0.0.title, playbackURL: $0.1.playbackURL) } }

上面假设轨道元数据的顺序与请求的顺序相同。如果不是这种情况,那么最后一张地图必须更复杂。


推荐阅读