Help me bij het implementeren van een aangepaste Swift-netwerklaag voor mijn iOS-app die geavanceerde foutafhandeling en caching-strategieën ondersteunt.
description
Door het implementeren van een aangepaste netwerklaag verbeter je de gebruikerservaring door snellere laadtijden, verbeterd foutbeheer en verminderd netwerkgebruik, wat leidt tot een efficiëntere app overall.
prompt
try_prompt
Help me een aangepaste netwerklaag in Swift te ontwerpen voor mijn iOS-toepassing. Deze laag moet API-verzoeken efficiënt afhandelen, fouten beheren met duidelijke gebruikersfeedback en caching-strategieën implementeren om netwerkverzoeken te ... more
generate_helper
...
ai_answers
provider: openai
model: gpt-4.1-nano
Natuurlijk! Hieronder vind je een gestructureerde aanpak en codevoorbeeld voor een aangepaste netwerklaag in Swift, gericht op efficiëntie, foutafhandeling en caching, geschikt voor het ophalen van gebruikersprofiel en instellingen.
---
### 1. Structuur van de netwerklaag
- **APIClient**: Een centrale klasse voor het uitvoeren van netwerkverzoeken.
- **Endpoint**: Enum of struct voor API-eindpunten.
- **NetworkError**: Enum voor foutafhandeling.
- **CacheManager**: Voor eenvoudige cachingstrategieën.
---
### 2. Codevoorbeeld
```swift
import Foundation
// 1. Definieer API-eindpunten
enum APIEndpoint {
case userProfile
case userSettings
var url: URL {
switch self {
case .userProfile:
return URL(string: "https://api.example.com/user/profile")!
case .userSettings:
return URL(string: "https://api.example.com/user/settings")!
}
}
}
// 2. Foutenbeheer
enum NetworkError: Error {
case invalidResponse
case invalidData
case serverError(String)
case cacheMiss
case unknown
}
// 3. CacheManager voor eenvoudige caching
class CacheManager {
static let shared = CacheManager()
private var cache = NSCache<NSURL, NSData>()
func getCachedResponse(for url: URL) -> Data? {
return cache.object(forKey: url as NSURL) as Data?
}
func cacheResponse(_ data: Data, for url: URL) {
cache.setObject(data as NSData, forKey: url as NSURL)
}
}
// 4. APIClient
class APIClient {
static let shared = APIClient()
private let session: URLSession
private init() {
let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = .returnCacheDataElseLoad
self.session = URLSession(configuration: configuration)
}
// Algemene request functie
func request<T: Decodable>(_ endpoint: APIEndpoint, responseType: T.Type, completion: @escaping (Result<T, NetworkError>) -> Void) {
let url = endpoint.url
// Check cache eerst
if let cachedData = CacheManager.shared.getCachedResponse(for: url) {
do {
let decoded = try JSONDecoder().decode(T.self, from: cachedData as Data)
completion(.success(decoded))
return
} catch {
// Cache kan corrupt zijn; verder gaan met netwerk
}
}
// Maak URLRequest
var request = URLRequest(url: url)
request.httpMethod = "GET"
// Voer request uit
session.dataTask(with: request) { data, response, error in
// Foutcontrole
if let error = error {
DispatchQueue.main.async {
completion(.failure(.serverError(error.localizedDescription)))
}
return
}
guard let httpResponse = response as? HTTPURLResponse else {
DispatchQueue.main.async {
completion(.failure(.invalidResponse))
}
return
}
guard (200...299).contains(httpResponse.statusCode) else {
let message = HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode)
DispatchQueue.main.async {
completion(.failure(.serverError(message)))
}
return
}
guard let data = data else {
DispatchQueue.main.async {
completion(.failure(.invalidData))
}
return
}
// Decodeer data
do {
let decodedObject = try JSONDecoder().decode(T.self, from: data)
// Cacheer de response
CacheManager.shared.cacheResponse(data, for: url)
DispatchQueue.main.async {
completion(.success(decodedObject))
}
} catch {
DispatchQueue.main.async {
completion(.failure(.invalidData))
}
}
}.resume()
}
}
```
---
### 3. Gebruik in je app
```swift
// Voorbeeld: ophalen van gebruikersprofiel
APIClient.shared.request(UserProfile.self, responseType: UserProfile.self) { result in
switch result {
case .success(let profile):
// Update UI of data verwerken
print("Gebruikersnaam: \(profile.name)")
case .failure(let error):
// Foutmelding tonen
print("Fout bij ophalen profiel: \(error.localizedDescription)")
}
}
```
---
### 4. Best practices
- **Gebruik decodable structs** voor API-responsen.
- **Caché strategie**: Cache responses na eerste fetch om netwerkverzoeken te minimaliseren.
- **Foutafhandeling**: Geef duidelijke foutmeldingen en feedback aan de gebruiker.
- **URLSession configuratie**: Gebruik `URLSessionConfiguration` voor caching en timeouts.
- **Main thread**: Zorg dat UI-updates op de hoofdthread gebeuren.
---
### Samenvatting
Deze aanpak biedt een solide basis voor een efficiënte, foutbestendige en cache-ondersteunde netwerklaag in Swift. Pas de structuren en foutmeldingen aan op basis van je exacte API en app-vereisten.
provider: deepseek
model: deepseek-chat
Ik help je graag met het ontwerpen van een aangepaste netwerklaag in Swift. Hier is een complete implementatie met best practices:
## 1. Basis Netwerklaag Structuur
```swift
import Foundation
// MARK: - Netwerk Fouten
enum NetworkError: Error, LocalizedError {
case invalidURL
case invalidResponse
case serverError(Int)
case decodingError
case noInternetConnection
case timeout
case unauthorized
var errorDescription: String? {
switch self {
case .invalidURL:
return "Ongeldige URL"
case .invalidResponse:
return "Ongeldig server antwoord"
case .serverError(let code):
return "Server fout: \(code)"
case .decodingError:
return "Fout bij verwerken gegevens"
case .noInternetConnection:
return "Geen internetverbinding"
case .timeout:
return "Time-out bij verbinding"
case .unauthorized:
return "Niet geautoriseerd"
}
}
}
// MARK: - HTTP Methoden
enum HTTPMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case delete = "DELETE"
}
// MARK: - API Endpoints
enum APIEndpoint {
case userProfile
case userSettings
var path: String {
switch self {
case .userProfile:
return "/api/user/profile"
case .userSettings:
return "/api/user/settings"
}
}
var method: HTTPMethod {
switch self {
case .userProfile, .userSettings:
return .get
}
}
}
```
## 2. Request Builder
```swift
// MARK: - Network Request
struct NetworkRequest {
let endpoint: APIEndpoint
let parameters: [String: Any]?
let headers: [String: String]?
init(endpoint: APIEndpoint, parameters: [String: Any]? = nil, headers: [String: String]? = nil) {
self.endpoint = endpoint
self.parameters = parameters
self.headers = headers
}
func buildURLRequest(baseURL: String) throws -> URLRequest {
guard let url = URL(string: baseURL + endpoint.path) else {
throw NetworkError.invalidURL
}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = endpoint.method.rawValue
urlRequest.timeoutInterval = 30
// Headers toevoegen
headers?.forEach { key, value in
urlRequest.setValue(value, forHTTPHeaderField: key)
}
// Parameters verwerken
if let parameters = parameters, !parameters.isEmpty {
if endpoint.method == .get {
var components = URLComponents(url: url, resolvingAgainstBaseURL: false)
components?.queryItems = parameters.map { URLQueryItem(name: $0.key, value: "\($0.value)") }
urlRequest.url = components?.url
} else {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.httpBody = try JSONSerialization.data(withJSONObject: parameters)
}
}
return urlRequest
}
}
```
## 3. Cache Manager
```swift
// MARK: - Cache Manager
class CacheManager {
static let shared = CacheManager()
private let cache = NSCache<NSString, AnyObject>()
private let userDefaults = UserDefaults.standard
private init() {}
func cache<T: Codable>(_ object: T, for key: String) {
if let encoded = try? JSONEncoder().encode(object) {
userDefaults.set(encoded, forKey: key)
userDefaults.synchronize()
}
}
func getCached<T: Codable>(for key: String) -> T? {
guard let data = userDefaults.data(forKey: key) else { return nil }
return try? JSONDecoder().decode(T.self, from: data)
}
func removeCached(for key: String) {
userDefaults.removeObject(forKey: key)
}
func clearAllCache() {
let domain = Bundle.main.bundleIdentifier!
userDefaults.removePersistentDomain(forName: domain)
userDefaults.synchronize()
}
}
```
## 4. Hoofd Netwerk Manager
```swift
// MARK: - Network Manager
class NetworkManager {
static let shared = NetworkManager()
private let session: URLSession
private let cacheManager = CacheManager.shared
private let baseURL = "https://jouw-api-domein.com" // Vervang met jouw API URL
private init() {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 60
configuration.requestCachePolicy = .returnCacheDataElseLoad
self.session = URLSession(configuration: configuration)
}
// MARK: - Generieke Request Functie
func request<T: Codable>(
_ request: NetworkRequest,
cacheKey: String? = nil,
forceRefresh: Bool = false
) async throws -> T {
// Cache controleren (indien van toepassing)
if let cacheKey = cacheKey, !forceRefresh {
if let cached: T = cacheManager.getCached(for: cacheKey) {
print("📦 Gebruik gecachte data voor: \(cacheKey)")
return cached
}
}
// Netwerk request bouwen
let urlRequest = try request.buildURLRequest(baseURL: baseURL)
// Network connectivity controleren
guard await isConnectedToNetwork() else {
throw NetworkError.noInternetConnection
}
print("🌐 Verstuur request: \(urlRequest.url?.absoluteString ?? "")")
do {
let (data, response) = try await session.data(for: urlRequest)
guard let httpResponse = response as? HTTPURLResponse else {
throw NetworkError.invalidResponse
}
// HTTP status code verwerken
try handleStatusCode(httpResponse.statusCode)
// Data decoderen
let decodedData = try JSONDecoder().decode(T.self, from: data)
// Cache data (indien van toepassing)
if let cacheKey = cacheKey {
cacheManager.cache(decodedData, for: cacheKey)
}
return decodedData
} catch let error as NetworkError {
throw error
} catch let error as DecodingError {
print("❌ Decoding error: \(error)")
throw NetworkError.decodingError
} catch {
if let urlError = error as? URLError {
switch urlError.code {
case .timedOut:
throw NetworkError.timeout
case .notConnectedToInternet:
throw NetworkError.noInternetConnection
default:
throw NetworkError.invalidResponse
}
}
throw NetworkError.invalidResponse
}
}
// MARK: - Helper Methods
private func handleStatusCode(_ statusCode: Int) throws {
switch statusCode {
case 200...299:
return // Success
case 401:
throw NetworkError.unauthorized
case 400...499:
throw NetworkError.serverError(statusCode)
case 500...599:
throw NetworkError.serverError(statusCode)
default:
throw NetworkError.invalidResponse
}
}
private func isConnectedToNetwork() async -> Bool {
// Vereist Network framework import
// In praktijk: gebruik Network framework of Reachability
return true // Vereenvoudigd voor dit voorbeeld
}
}
```
## 5. Data Modellen
```swift
// MARK: - Data Modellen
struct UserProfile: Codable {
let id: String
let name: String
let email: String
let avatarURL: String?
enum CodingKeys: String, CodingKey {
case id, name, email
case avatarURL = "avatar_url"
}
}
struct UserSettings: Codable {
let notificationsEnabled: Bool
let theme: String
let language: String
enum CodingKeys: String, CodingKey {
case notificationsEnabled = "notifications_enabled"
case theme, language
}
}
```
## 6. Gebruiksvoorbeelden
```swift
// MARK: - Gebruik in ViewModel/Controller
class UserService {
private let networkManager = NetworkManager.shared
// Gebruikersprofiel ophalen
func fetchUserProfile() async throws -> UserProfile {
let request = NetworkRequest(endpoint: .userProfile)
do {
let profile: UserProfile = try await networkManager.request(
request,
cacheKey: "user_profile",
forceRefresh: false
)
return profile
} catch {
throw error
}
}
// Gebruikersinstellingen ophalen
func fetchUserSettings() async throws -> UserSettings {
let request = NetworkRequest(endpoint: .userSettings)
do {
let settings: UserSettings = try await networkManager.request(
request,
cacheKey: "user_settings",
forceRefresh: false
)
return settings
} catch {
throw error
}
}
// Cache wissen
func clearCache() {
CacheManager.shared.clearAllCache()
}
}
// MARK: - Gebruik in View Controller
class ProfileViewController: UIViewController {
private let userService = UserService()
func loadUserData() {
Task {
do {
// Laad beide requests parallel
async let profile = userService.fetchUserProfile()
async let settings = userService.fetchUserSettings()
let (userProfile, userSettings) = try await (profile, settings)
await MainActor.run {
self.updateUI(with: userProfile, settings: userSettings)
}
} catch let error as NetworkError {
await MainActor.run {
self.showError(error.errorDescription ?? "Onbekende fout")
}
} catch {
await MainActor.run {
self.showError("Er is een fout opgetreden")
}
}
}
}
private func updateUI(with profile: UserProfile, settings: UserSettings) {
// Update UI
}
private func showError(_ message: String) {
let alert = UIAlertController(
title: "Fout",
message: message,
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
}
```
## Best Practices:
1. **Error Handling**: Gebruik gedetailleerde foutmeldingen met lokalisatie
2. **Caching**: Implementeer strategie gebaseerd op data type en frequentie
3. **Modulariteit**: Houd netwerklaag gescheiden van business logic
4. **Testbaarheid**: Maak protocol-based voor eenvoudig testen
5. **Performance**: Gebruik async/await voor niet-blokkerende calls
6. **Security**: Voeg authenticatie headers toe waar nodig
Deze implementatie biedt een solide basis die je kunt uitbreiden met extra features zoals:
- Retry mechanisme
- Request interceptors
- Upload/download progress tracking
- Request batching
- Offline ondersteuning
Laat me weten als je specifieke onderdelen wilt uitbreiden!

