Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/swift/18.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 将获取相同对象不同属性的两个API调用的结果与RxSwift结合起来_Ios_Swift_Rx Swift_Moya - Fatal编程技术网

Ios 将获取相同对象不同属性的两个API调用的结果与RxSwift结合起来

Ios 将获取相同对象不同属性的两个API调用的结果与RxSwift结合起来,ios,swift,rx-swift,moya,Ios,Swift,Rx Swift,Moya,我有一个名为轨道的模型。它有一组基本属性和一组扩展属性。曲目列表及其基本属性是通过搜索API调用获取的,然后我需要使用那些曲目ID进行另一个API调用来获取它们的扩展属性 问题是如何最好地组合两个API调用的结果,并将扩展属性填充到已创建的跟踪对象中,当然还要按ID匹配它们(不幸的是,这两个调用的结果中的属性名称不同)。请注意,在实际结果集中返回的键要多得多——两个调用中的每个调用大约有20-30个属性 Track.swift struct Track: Decodable { // MARK

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

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

Track.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) ?? ""
}
}
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: [])
}
ViewModel.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) ?? ""
}
}
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: [])
}
.getTracksMetadata API调用的JSON结果的结构如下:

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

以上假设跟踪元数据的顺序与请求的顺序相同。如果不是这样,那么最后一个映射就必须更加复杂。

这可能是您需要的,两个调用的JSON是什么样子的?原始问题中添加了JSON结果。结果集非常简化,结果通常包含每个对象的20-30个属性。这里的关键是使用
flatMap
combinelatetest
,和
just
将旧数据与新数据一起携带。这里可以找到这种和其他有趣的组合观测值的方法:问题是我不能假设两个API调用的顺序是相同的。另外,我需要通过不同的属性比较每个结果数组中的单个结果
(如果obj.id==otherobj.trackid)
-请参阅原始问题中新添加的JSON结果集。第三,在实际的生产结果集中有许多属性,因此在Track()初始值设定项中描述每个属性会很麻烦。这很好,只需在传递到zip
.map{zip($0.sorted(by:{$0.id<$1.id}),$1.sorted(by:{$0.trackID<$1.trackID}))之前对它们进行排序即可
并通过仅为Track结构提供两个属性使其更简单
struct Track{let basic:TrackBasic;let meta:TrackMetadata}
。基本建议仍然相同,使用flatMap、CombineTest和just。