如何解析来自两个 API 的数据

How to parse data from two APIs

我正在制作一个应用程序,我需要在其中解析来自两个 API 的数据 (first and second)。我想出了如何从第一个 API 获取数据,但是第二个有问题。我使用第一个 API 如下。

对于第一个 API,我使用此模型,然后在视图中创建 @State 属性 并在 Body() 中通过点语法调用我需要的变量。

// MARK: - API
class InfoApi {
    func getRockets(completion: @escaping ([RocketInfo]) -> ()) {
        guard let url = URL(string: "https://api.spacexdata.com/v4/rockets") else {
            return
        }

        URLSession.shared.dataTask(with: url) { (data, response, error) in
            do {
                let rocketsInfo = try JSONDecoder().decode([RocketInfo].self, from: data!)
                DispatchQueue.main.async {
                    completion(rocketsInfo)
                }
            } catch {
                print(error.localizedDescription)
            }
        }
        .resume()
    }
}

// MARK: - ROCKET MODEL
struct RocketInfo: Codable, Identifiable {
    let id = UUID()
    let name: String
    let country: String
    let first_flight: String
    let cost_per_launch: Int
}

// MARK: - CONTENT VIEW
struct ContentView: View {
    @State var rockets: [RocketInfo] = []

    var body: some View {
        NavigationView {
            if rockets.isEmpty {
                ProgressView()
            } else {
                TabView {
                    ForEach(rockets) { rocket in
                        ScrollView(.vertical, showsIndicators: false) {
                            VStack {
                                //MARK: - HEADER IMAGE
                                Image("Image2")
                                    .renderingMode(.original)
                                    .resizable()
                                    .scaledToFill()
                                    .frame(height: 190, alignment: .center)
                                    .padding(.bottom, 32)
                                
                                //MARK: - INFO
                                VStack(spacing: 40) {
                                    HStack {
                                        Text(rocket.name)
                                            .font(.title)
                                        Spacer()
                                    }
                                    HStack {
                                        Text("First flight")
                                        Spacer()
                                        Text(rocket.first_flight)
                                    }
                                    HStack {
                                        Text("Country")
                                        Spacer()
                                        Text(rocket.country)
                                    }
                                    HStack {
                                        Text("Cost per launch")
                                        Spacer()
                                        Text("$\(rocket.cost_per_launch / 1000000)M")
                                    }
                                } //: VSTACK
                                .padding(.horizontal, 32)
                                
                                //MARK: - LAUNCHES BUTTON
                                NavigationLink {
                                    LaunchDetailView()
                                } label: {
                                    Text("Launches".uppercased())
                                        .font(.headline)
                                        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 56, maxHeight: 56, alignment: .center)
                                        .background(
                                            Color(UIColor.secondarySystemFill)
                                                .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
                                        )
                                        .foregroundColor(Color.green)
                                        .padding(32)
                                }
                            } //: VSTACK
                        } //: SCROLL
                    } //: LOOP
                } //: TAB
                .tabViewStyle(.page)
                .navigationBarTitleDisplayMode(.inline)
                .navigationBarHidden(true)
                .edgesIgnoringSafeArea(.vertical)
            }
        } //: NAVIGATION
        .onAppear {
            InfoApi().getRockets { rockets in
                self.rockets = rockets
            }
        }
        .edgesIgnoringSafeArea(.vertical)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .preferredColorScheme(.dark)
    }
}

第二个 API 的问题在于它包含第一个 API 中所有四枚火箭的发射列表,并且这些发射中的每一个都有一个与第一个 API.

中的四枚火箭之一的 ID

简而言之第一个 API 包含 4 枚火箭的列表 和它们每个的参数,秒 API 包含这些火箭的 所有发射 的列表。 注意:我翻了一下第二API,发现四枚火箭其中一枚根本没有发射,另一枚有160枚之多。说实话,我什至不能只解析第二个 API 的整个启动列表,使用与第一个 API 相同的模型,而是将 link 更改为这个 API本身。

每次发射都有一个 ID,与第一次 API 中的四枚火箭之一的 ID 相匹配。在我的应用程序中,有四个选项卡,其中包含有关每枚火箭的信息和一个指向另一个视图的按钮,该视图应显示有关该火箭所有发射的信息(名称、日期以及发射是否成功)。

  1. 我不知道如何更新我的模型以便我可以同时解析来自两个 API 的信息。
  2. 我也不知道如何在单独的视图上显示有关特定火箭的所有发射的信息,首先检查它们的 ID 是否与第一个 API 中的特定火箭 ID 匹配。

编辑-1:

最好的办法是重构代码并使用 ObservableObject class 进行所有的抓取、处理和发布。

这是一些使用 ObservableObject class.

的示例代码
struct ContentView: View {
    @StateObject var spacex = SpacexModel() // <-- here
    
    var body: some View {
        NavigationView {
            if spacex.loadingRockets || spacex.loadingLaunches {  // <-- here
                ProgressView()
            } else {
                TabView {
                    ForEach(spacex.rockets) { rocket in  // <-- here
                        ScrollView(.vertical, showsIndicators: false) {
                            VStack {
                                //MARK: - HEADER IMAGE
                                Image(systemName: "globe") // <-- for testing
                                    .renderingMode(.original)
                                    .resizable()
                                    .scaledToFill()
                                    .frame(width: 190, height: 190, alignment: .center)
                                    .padding(.bottom, 32)
                                
                                //MARK: - INFO
                                VStack(spacing: 40) {
                                    HStack {
                                        Text(rocket.name).font(.title)
                                        Spacer()
                                    }
                                    HStack {
                                        Text("First flight")
                                        Spacer()
                                        Text(rocket.first_flight)
                                    }
                                    HStack {
                                        Text("Country")
                                        Spacer()
                                        Text(rocket.country)
                                    }
                                    HStack {
                                        Text("Cost per launch")
                                        Spacer()
                                        Text("$\(rocket.cost_per_launch / 1000000)M")
                                    }
                                } //: VSTACK
                                .padding(.horizontal, 32)
                                
                                //MARK: - LAUNCHES BUTTON
                                NavigationLink {
                                    LaunchDetailView(rocket: rocket)  // <-- here
                                } label: {
                                    Text("Launches".uppercased())
                                        .font(.headline)
                                        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 56, maxHeight: 56, alignment: .center)
                                        .background(
                                            Color(UIColor.secondarySystemFill)
                                                .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
                                        )
                                        .foregroundColor(Color.green)
                                        .padding(32)
                                }
                            } //: VSTACK
                        } //: SCROLL
                    } //: LOOP
                } //: TAB
                .tabViewStyle(.page)
                .navigationBarTitleDisplayMode(.inline)
                .navigationBarHidden(true)
                .edgesIgnoringSafeArea(.vertical)
            }
        } //: NAVIGATION
        .navigationViewStyle(.stack)
        .environmentObject(spacex) // <-- here
        .edgesIgnoringSafeArea(.vertical)
    }
}

struct LaunchDetailView: View {
    @EnvironmentObject var spacex: SpacexModel  // <-- here
    let rocket: RocketInfo    // <-- here
    
    var body: some View {
        VStack {
            Text("launches for \(rocket.name)").foregroundColor(.blue)
            List {
                ForEach(spacex.launchesFor(rocket)) { launch in
                    VStack {
                        Text(launch.name).foregroundColor(.green)
                        Text("launch \(launch.details ?? "no details")")
                    }
                }
            }
        }
    }
}

class SpacexModel: ObservableObject {
    
    @Published var rockets = [RocketInfo]()
    @Published var launches = [LaunchInfo]()
    
    @Published var loadingRockets = false
    @Published var loadingLaunches = false
    
    init() {
        getRockets()
        getLaunches()
    }
    
    func getRockets() {
        self.loadingRockets = true
        guard let url = URL(string: "https://api.spacexdata.com/v4/rockets") else {
            return
        }
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            // todo deal with errors
            guard let data = data, error == nil else { return }
            DispatchQueue.main.async {
                do {
                    self.rockets = try JSONDecoder().decode([RocketInfo].self, from: data)
                    self.loadingRockets = false
                } catch {
                    print(error)
                }
            }
        }.resume()
    }
    
    func getLaunches() {
        self.loadingLaunches = true
        guard let url = URL(string: "https://api.spacexdata.com/v4/launches") else {
            return
        }
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            // todo deal with errors
            guard let data = data, error == nil else { return }
            DispatchQueue.main.async {
                do {
                    self.launches = try JSONDecoder().decode([LaunchInfo].self, from: data)
                    self.loadingLaunches = false
                } catch {
                    print(error)
                }
            }
        }.resume()
    }
    
    func launchesFor(_ rocket: RocketInfo) -> [LaunchInfo] {
        return launches.filter{ [=10=].rocket == rocket.id }
    }
    
}




struct RocketInfo: Codable, Identifiable {
    let id: String  // <-- here
    let name: String
    let country: String
    let first_flight: String
    let cost_per_launch: Int
    // ...
}

struct LaunchInfo: Identifiable, Codable {
    let id: String
    let rocket: String  // <-- here
    let details: String?
    let crew, ships, capsules, payloads: [String]
    let name: String
    // ...
}