In this article It has explanation how to use async/await with two network calls
We can use an array of Task
instances to manage multiple concurrent tasks in Swift. This approach is useful for running multiple network requests in parallel and handling their results collectively. Here’s how you can incorporate an array of Task
instances into your ViewModel for fetching data concurrently.
Step-by-Step Implementation with Task Array
- Create a Model for Your Data
- Create a Network Service
- Create a ViewModel with Task Array
- Create a SwiftUI View
1. Create a Model for Your Data
Create a new Swift file named Hotel.swift
and define your model:
2. Create a Network Service
Create a new Swift file named NetworkService.swift
for your network calls:
3. Create a ViewModel with Task Array
Create a new Swift file named HotelViewModel.swift
for your view model:
4. Create a SwiftUI View
Finally, create your main SwiftUI view in ContentView.swift
:
Explanation
- Model:
Hotel
andAdditionalInfo
are defined to match the structure of the JSON data. - NetworkService: Contains methods to fetch data from the provided URLs.
- ViewModel:
HotelViewModel
manages the state, including loading and error states, and performs data fetching using an array ofTask
instances. It includes functionality to cancel ongoing tasks. - ContentView: Displays a loader, handles errors, and shows the list of hotels and additional info when available. The
onDisappear
modifier is used to cancel the fetch tasks if the view disappears.
This implementation ensures that multiple tasks can be managed and canceled effectively, making the app responsive to user interactions and preventing unnecessary network requests.
import SwiftUI
import Foundation
// MARK: - Model Definitions
struct Hotel: Codable, Identifiable {
let id: Int
let name: String
let cuisine: String
}
struct AdditionalInfo: Codable {
let title: String
}
// MARK: - Network Service
class NetworkService {
static let shared = NetworkService()
private init() {}
// Asynchronously fetch hotels from a JSON endpoint
func fetchHotels() async throws -> [Hotel] {
let url = URL(string: "https://raw.githubusercontent.com/janeshsutharios/REST_GET_API/main/HotelsList.json")!
let (data, _) = try await URLSession.shared.data(from: url)
do {
let results = try JSONDecoder().decode([Hotel].self, from: data)
return results
} catch {
print("Error in fetching hotels:", error)
return []
}
}
// Asynchronously fetch additional information from another JSON endpoint
func fetchAdditionalInfo() async throws -> [AdditionalInfo] {
let url = URL(string: "https://raw.githubusercontent.com/janeshsutharios/REST_GET_API/main/get.json")!
let (data, _) = try await URLSession.shared.data(from: url)
do {
return try JSONDecoder().decode([AdditionalInfo].self, from: data)
} catch {
print("Error in fetching additional info:", error)
return []
}
}
}
// MARK: - View Model
@MainActor
class HotelViewModel: ObservableObject {
@Published var hotels: [Hotel] = []
@Published var additionalInfo: [AdditionalInfo] = []
@Published var isLoading: Bool = false
@Published var errorMessage: String?
private var tasks: [Task<Void, Never>] = []
// Fetch data from NetworkService
func fetchData() {
guard #available(iOS 15.0, *) else { return }
isLoading = true
cancelTasks() // Cancel any existing tasks
// Task to fetch hotels
let fetchHotelsTask = Task {
do {
let hotels = try await NetworkService.shared.fetchHotels()
self.hotels = hotels
} catch {
if !Task.isCancelled {
self.errorMessage = "Failed to fetch hotels: \(error.localizedDescription)"
}
}
}
// Task to fetch additional info
let fetchAdditionalInfoTask = Task {
do {
let additionalInfo = try await NetworkService.shared.fetchAdditionalInfo()
self.additionalInfo = additionalInfo
} catch {
if !Task.isCancelled {
self.errorMessage = "Failed to fetch additional info: \(error.localizedDescription)"
}
}
}
// Store tasks for cancellation and completion handling
tasks = [fetchHotelsTask, fetchAdditionalInfoTask]
// Wait for both tasks to complete before setting isLoading to false
Task {
await fetchHotelsTask.value
await fetchAdditionalInfoTask.value
self.isLoading = false
}
}
// Cancel all ongoing tasks
func cancelTasks() {
tasks.forEach { $0.cancel() }
tasks.removeAll()
}
}
// MARK: - Content View
struct ContentView: View {
@StateObject private var viewModel = HotelViewModel()
var body: some View {
NavigationView {
VStack {
// Show loading indicator while fetching data
if viewModel.isLoading {
ProgressView("Loading...")
.progressViewStyle(CircularProgressViewStyle())
.onDisappear {
viewModel.cancelTasks() // Cancel the tasks if the view disappears
}
}
// Show error message if data fetching fails
else if let errorMessage = viewModel.errorMessage {
Text(errorMessage)
.foregroundColor(.red)
}
// Display fetched additional info and hotels list
else {
Text(viewModel.additionalInfo.first?.title ?? "No additional info")
.font(.headline)
.padding()
List(viewModel.hotels) { hotel in
VStack(alignment: .leading) {
Text(hotel.name)
.font(.headline)
Text(hotel.cuisine)
.font(.subheadline)
}
}
.listStyle(InsetGroupedListStyle())
}
}
.navigationTitle("Hotels")
.onAppear {
viewModel.fetchData() // Fetch data when view appears
}
.onDisappear {
viewModel.cancelTasks() // Cancel tasks when view disappears
}
}
}
}
// MARK: - App Entry Point
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}