Creating a Simple Network Manager/Client in SwiftUI Using Protocols, async/await, and URLCache
When developing apps in SwiftUI, handling network requests efficiently is crucial for providing a seamless user experience. In this post…
Creating a Simple Network Manager/Client in SwiftUI Using Protocols, async/await, and URLCache
When developing apps in SwiftUI, handling network requests efficiently is crucial for providing a seamless user experience. In this post, we will create a simple network manager/client using SwiftUI, protocols, async/await, and URLCache to handle RESTful network calls. This approach will ensure clean code, separation of concerns, and easy testing.
Step 1: Define the API Protocol
First, define a protocol that outlines the basic functions your network manager will support. This will ensure that any network manager class conforms to a standard interface.
protocol APIClient {
func get<T: Decodable>(url: URL) async throws -> T
func post<T: Decodable, U: Encodable>(url: URL, body: U) async throws -> T
}
Step 2: Create a Network Manager
Next, create a class that implements this protocol. This class will handle the actual network requests using URLSession, async/await, and URLCache.
import Foundation
class NetworkManager: APIClient {
private let urlCache: URLCache
init(urlCache: URLCache = .shared) {
self.urlCache = urlCache
}
func get<T: Decodable>(url: URL) async throws -> T {
if let cachedResponse = urlCache.cachedResponse(for: URLRequest(url: url)) {
let decodedData = try JSONDecoder().decode(T.self, from: cachedResponse.data)
return decodedData
}
let (data, response) = try await URLSession.shared.data(from: url)
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
let cachedResponse = CachedURLResponse(response: response, data: data)
urlCache.storeCachedResponse(cachedResponse, for: URLRequest(url: url))
}
let decodedData = try JSONDecoder().decode(T.self, from: data)
return decodedData
}
func post<T: Decodable, U: Encodable>(url: URL, body: U) async throws -> T {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(body)
let (data, _) = try await URLSession.shared.data(for: request)
let decodedData = try JSONDecoder().decode(T.self, from: data)
return decodedData
}
}
Step 3: Create a ViewModel
Now, create a ViewModel that will use the network manager to fetch data and update the view. This ViewModel will conform to ObservableObject to enable SwiftUI to react to data changes.
import SwiftUI
import Combine
class ExampleViewModel: ObservableObject {
@Published var data: [SampleData] = []
private let apiClient: APIClient
init(apiClient: APIClient = NetworkManager()) {
self.apiClient = apiClient
}
func fetchData() async {
guard let url = URL(string: "https://api.example.com/data") else { return }
do {
let fetchedData: [SampleData] = try await apiClient.get(url: url)
DispatchQueue.main.async {
self.data = fetchedData
}
} catch {
print("Error fetching data: \(error.localizedDescription)")
}
}
}
// MARK: - Sample Data
struct SampleData: Decodable, Identifiable {
var id = UUID()
let name: String
}
Step 4: Create the SwiftUI View
Finally, create a SwiftUI view that uses the ViewModel to display data. This view will use the @StateObject property wrapper to create and manage the ViewModel’s lifecycle.
import SwiftUI
struct ExampleView: View {
@StateObject private var viewModel = ExampleViewModel()
var body: some View {
List(viewModel.data) { item in
Text(item.name) // Replace with appropriate property
}
.task {
await viewModel.fetchData()
}
}
}
Conclusion
This approach ensures a clean separation of concerns and makes the network manager easily testable and reusable. You can further extend this setup by adding more HTTP methods (PUT, DELETE), handling different types of errors, and implementing retry mechanisms.
Additional Tips
• Error Handling: Consider creating a custom error type to better handle different error scenarios.
• Dependency Injection: Pass different implementations of APIClient to your ViewModel for testing purposes.
• EnvironmentObject: Use EnvironmentObject to pass the network manager throughout your SwiftUI views if needed.
By following these steps, you can create a robust and reusable network manager in SwiftUI using protocols, async/await, and URLCache. This setup provides a solid foundation for handling network requests in a clean and maintainable way.
Happy coding! 🚀
By Wesley Matlock on May 28, 2024.
Exported from Medium on May 10, 2025.