ios - 将 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"
},
{
...
}
]
解决方案
这里的解决方案是两阶段方法。首先,您应该为两个网络调用定义两个不同的结构,并为组合结果定义第三个结构。假设您选择:
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) } }
上面假设轨道元数据的顺序与请求的顺序相同。如果不是这种情况,那么最后一张地图必须更复杂。
推荐阅读
- cassandra - Cassanra-Medusa 错误:备份期间发生此错误:[Errno 2] 没有这样的文件或目录:'nodetool'
- python - 如何使用 python 从 memcache 中获取所有密钥
- c# - 填充列表的方法
具有类常量值。C# - ios - 如何构建 iOS SDK?
- java - 如果春季sql查询失败,如何回滚事务
- php - FPDF 多单元格对齐
- embedded - 如何在 gsm 调制解调器通信中使用 nanopb,其中特定字符(如 CTRL+Z)用作 AT 命令中的 msg 终止?
- javascript - 在 Angular 8 的表单数组中使用表单数组
- android - 在 RecyclerView firestore 中发布喜欢的用户
- android - 如何解决:无法解决:com.google.firebase:firebase-messaging:20.1.0