slogan3

Ontwikkel een geavanceerde Swift-functie voor aangepaste iOS-applicatie-integraties

description

Met deze prompt kan de gebruiker een geavanceerde en betrouwbare integratiefunctie ontwikkelen voor iOS-apps die gebruikmaakt van moderne Swift-frameworks. Dit helpt om complexe externe communicatie efficiënt te implementeren, fouten te voorkomen en prestaties te optimaliseren, wat beter is dan algemene codevoorbeelden zonder context of diepgaande uitleg.

prompt

Help mij met het ontwikkelen van een geavanceerde Swift-functie die zorgt voor integratie met <beschrijf de externe API of service> binnen mijn iOS-app. Geef duidelijke codevoorbeelden en leg uit hoe ik Swift Concurrency, Comb ...

try_prompt

generate_helper
disclaimerOnPageApi

ai_answers

provider: openai
model: gpt-4.1-nano
Natuurlijk! Hieronder geef ik een uitgebreide uitleg en codevoorbeeld voor een geavanceerde Swift-functie die REST API-integratie met OAuth2-authenticatie mogelijk maakt, gebruikmakend van Swift Concurrency en URLSession. Ik behandel ook best practices, valkuilen en optimalisaties. --- ## 1. Overzicht - **Doel**: Ophalen van gebruikersprofielen via een REST API met OAuth2 authenticatie. - **Technieken**: - **Swift Concurrency (async/await)** voor eenvoudige, leesbare asynchrone code. - **URLSession** voor netwerkcommunicatie. - Mogelijk gebruik van **Combine** als alternatief voor reactieve programmering, maar hier focus ik op `async/await`. --- ## 2. Opzetten OAuth2 Authenticatie Voor OAuth2 heb je meestal: - **Access Token**: voor authenticatie. - **Refresh Token**: om een verlopen token te vernieuwen. Hieronder een voorbeeld van het ophalen en beheren van tokens. --- ## 3. Codevoorbeeld ### a. Tokenbeheer ```swift import Foundation class OAuth2TokenManager { static let shared = OAuth2TokenManager() private init() {} private var accessToken: String? private var refreshToken: String? private var tokenExpirationDate: Date? private let lock = NSLock() // Vul dit met je OAuth2 gegevens private let clientID = "JOUW_CLIENT_ID" private let clientSecret = "JOUW_CLIENT_SECRET" private let tokenURL = URL(string: "https://api.example.com/oauth/token")! // Ophalen van de toegangstoken func getAccessToken() async throws -> String { lock.lock() defer { lock.unlock() } if let token = accessToken, let expiry = tokenExpirationDate, expiry > Date() { // Token is nog geldig return token } // Token vernieuwen of ophalen try await refreshAccessToken() guard let token = accessToken else { throw NSError(domain: "OAuth2", code: 1, userInfo: [NSLocalizedDescriptionKey: "Geen toegangstoken beschikbaar"]) } return token } private func refreshAccessToken() async throws { // Als je nog een refresh token hebt, probeer die te gebruiken guard let refreshToken = self.refreshToken else { // Hier zou je ook een initiële 'login' flow moeten starten throw NSError(domain: "OAuth2", code: 2, userInfo: [NSLocalizedDescriptionKey: "Geen refresh token beschikbaar"]) } var request = URLRequest(url: tokenURL) request.httpMethod = "POST" let params = [ "grant_type": "refresh_token", "refresh_token": refreshToken, "client_id": clientID, "client_secret": clientSecret ] request.httpBody = params .map { "\($0)=\($1)" } .joined(separator: "&") .data(using: .utf8) request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw NSError(domain: "OAuth2", code: 3, userInfo: [NSLocalizedDescriptionKey: "Tokenvernieuwing mislukt"]) } let tokenResponse = try JSONDecoder().decode(TokenResponse.self, from: data) self.accessToken = tokenResponse.access_token self.refreshToken = tokenResponse.refresh_token self.tokenExpirationDate = Date().addingTimeInterval(TimeInterval(tokenResponse.expires_in)) } } struct TokenResponse: Decodable { let access_token: String let refresh_token: String let expires_in: Int } ``` --- ### b. Haal gebruikersprofiel op ```swift struct UserProfile: Decodable { let id: String let naam: String let email: String // Voeg extra velden toe afhankelijk van API } class UserProfileService { private let apiBaseURL = URL(string: "https://api.example.com/users/me")! func fetchUserProfile() async throws -> UserProfile { let token = try await OAuth2TokenManager.shared.getAccessToken() var request = URLRequest(url: apiBaseURL) request.httpMethod = "GET" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") let (data, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw NSError(domain: "UserProfile", code: 1, userInfo: [NSLocalizedDescriptionKey: "Fout bij ophalen profiel"]) } let profile = try JSONDecoder().decode(UserProfile.self, from: data) return profile } } ``` --- ## 4. Gebruik in je app ```swift @MainActor func laadGebruikersprofiel() async { do { let profiel = try await UserProfileService().fetchUserProfile() print("Gebruikersnaam: \(profiel.naam)") // Update je UI hier } catch { print("Fout bij ophalen profiel: \(error.localizedDescription)") // Foutafhandeling } } ``` --- ## 5. Mogelijke valkuilen en optimalisaties ### Valkuilen: - **Token vervaldatum niet goed beheren**: Zorg dat je `tokenExpirationDate` correct instelt; anders kan je met verlopen tokens werken. - **Gelijktijdige tokenvernieuwing**: Bij meerdere gelijktijdige API-aanroepen kan het gebeuren dat tokenvernieuwing meerdere keren wordt geprobeerd. Gebruik bijvoorbeeld een `DispatchSemaphore` of `Task`-referentie om dit te voorkomen. - **Security**: Bewaar tokens veilig, bijvoorbeeld in de Keychain. - **API-fouten afhandelen**: Controleer response codes en pas je herhaal- of foutafhandelingsstrategie aan. ### Optimalisaties: - **Caching**: Cache gebruikersprofielen indien mogelijk. - **Retry-logica**: Bij tijdelijke fouten (bijv. 503) herhaal de request met exponential backoff. - **Gebruik van URLSessionConfiguration**: Stel bijvoorbeeld timeout in, of gebruik een `URLSession` met aangepaste configuratie voor betere controle. - **Gebruik van Combine**: Voor reactieve programmeerstijl, met `Future` of `Publisher`, afhankelijk van je voorkeur. --- ## Samenvatting - Gebruik `async/await` voor overzichtelijke, schaalbare asynchrone code. - Beheer tokens zorgvuldig met een singleton `OAuth2TokenManager`. - Maak API-aanroepen via `URLSession`. - Vergeet niet foutafhandeling en beveiliging. --- Als je nog verdere hulp nodig hebt, bijvoorbeeld met de UI-integratie of specifieke API-configuraties, laat het gerust weten!
provider: deepseek
model: deepseek-chat
Ik help je graag met het ontwikkelen van een geavanceerde Swift-functie voor REST API integratie met OAuth2 authenticatie. Hier is een complete implementatie met moderne Swift Concurrency: ## 1. Basis Modellen en Configuratie ```swift import Foundation // MARK: - Modellen struct UserProfile: Codable { let id: String let name: String let email: String let avatarUrl: String? } struct OAuthToken: Codable { let accessToken: String let refreshToken: String? let expiresIn: TimeInterval let tokenType: String } // MARK: - Fouten enum APIError: Error { case invalidURL case unauthorized case networkError(Error) case decodingError(Error) case serverError(Int) case tokenExpired case invalidResponse } // MARK: - Configuratie struct APIConfig { static let baseURL = "https://api.jouwservice.com" static let clientId = "jouw_client_id" static let clientSecret = "jouw_client_secret" } ``` ## 2. Authenticatie Manager ```swift import Security actor AuthenticationManager { static let shared = AuthenticationManager() private var currentToken: OAuthToken? private let tokenKey = "oauth_token" private init() { loadTokenFromKeychain() } // MARK: - Token Opslag (Keychain) private func saveTokenToKeychain(_ token: OAuthToken) throws { let encoder = JSONEncoder() let tokenData = try encoder.encode(token) let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: tokenKey, kSecValueData as String: tokenData ] SecItemDelete(query as CFDictionary) let status = SecItemAdd(query as CFDictionary, nil) guard status == errSecSuccess else { throw NSError(domain: "KeychainError", code: Int(status)) } } private func loadTokenFromKeychain() { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: tokenKey, kSecReturnData as String: true ] var result: AnyObject? let status = SecItemCopyMatching(query as CFDictionary, &result) guard status == errSecSuccess, let data = result as? Data, let token = try? JSONDecoder().decode(OAuthToken.self, from: data) else { return } currentToken = token } // MARK: - Token Beheer func getValidToken() async throws -> String { if let token = currentToken, !isTokenExpired(token) { return token.accessToken } // Token vernieuwen return try await refreshToken() } private func refreshToken() async throws -> String { guard let refreshToken = currentToken?.refreshToken else { throw APIError.unauthorized } let newToken = try await performTokenRefresh(refreshToken: refreshToken) currentToken = newToken try saveTokenToKeychain(newToken) return newToken.accessToken } private func isTokenExpired(_ token: OAuthToken) -> Bool { // Vereenvoudigde implementatie - in productie zou je de creatietijd moeten bijhouden return false } private func performTokenRefresh(refreshToken: String) async throws -> OAuthToken { // Implementeer token refresh logica fatalError("Implementeer token refresh logica") } } ``` ## 3. Geavanceerde API Client ```swift actor APIClient { static let shared = APIClient() private let session: URLSession private let authenticationManager = AuthenticationManager.shared private init() { let configuration = URLSessionConfiguration.default configuration.timeoutIntervalForRequest = 30 configuration.timeoutIntervalForResource = 60 configuration.waitsForConnectivity = true configuration.httpMaximumConnectionsPerHost = 4 self.session = URLSession(configuration: configuration) } // MARK: - Hoofd API Functie func fetchUserProfile(userId: String) async throws -> UserProfile { let endpoint = "/users/\(userId)" return try await performRequest(endpoint: endpoint) } // MARK: - Generieke Request Handler private func performRequest<T: Decodable>( endpoint: String, method: String = "GET", body: Data? = nil ) async throws -> T { // 1. Bouw URLRequest let request = try await buildRequest( endpoint: endpoint, method: method, body: body ) // 2. Voer request uit met retry logica let (data, response) = try await executeRequestWithRetry(request) // 3. Verwerk response return try processResponse(data: data, response: response) } // MARK: - Request Bouwer private func buildRequest( endpoint: String, method: String, body: Data? ) async throws -> URLRequest { guard let url = URL(string: APIConfig.baseURL + endpoint) else { throw APIError.invalidURL } let token = try await authenticationManager.getValidToken() var request = URLRequest(url: url) request.httpMethod = method request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.httpBody = body return request } // MARK: - Request Uitvoering met Retry private func executeRequestWithRetry(_ request: URLRequest) async throws -> (Data, URLResponse) { let maxRetries = 3 for attempt in 0..<maxRetries { do { let (data, response) = try await session.data(for: request) if let httpResponse = response as? HTTPURLResponse { switch httpResponse.statusCode { case 200...299: return (data, response) case 401: // Token is verlopen, probeer te vernieuwen _ = try await authenticationManager.getValidToken() continue case 429: // Rate limiting - wacht en probeer opnieuw if attempt < maxRetries - 1 { try await Task.sleep(nanoseconds: UInt64(pow(2.0, Double(attempt))) * 1_000_000_000) continue } default: throw APIError.serverError(httpResponse.statusCode) } } return (data, response) } catch { // Alleen opnieuw proberen bij netwerkfouten if let urlError = error as? URLError, urlError.code == .notConnectedToInternet || urlError.code == .timedOut { if attempt < maxRetries - 1 { try await Task.sleep(nanoseconds: UInt64(pow(2.0, Double(attempt))) * 1_000_000_000) continue } } throw APIError.networkError(error) } } throw APIError.networkError(NSError(domain: "MaxRetriesExceeded", code: -1)) } // MARK: - Response Verwerking private func processResponse<T: Decodable>(data: Data, response: URLResponse) throws -> T { guard let httpResponse = response as? HTTPURLResponse else { throw APIError.invalidResponse } guard (200...299).contains(httpResponse.statusCode) else { throw APIError.serverError(httpResponse.statusCode) } do { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase return try decoder.decode(T.self, from: data) } catch { throw APIError.decodingError(error) } } } ``` ## 4. Gebruik in ViewModel ```swift import SwiftUI @MainActor class UserProfileViewModel: ObservableObject { @Published var userProfile: UserProfile? @Published var isLoading = false @Published var error: APIError? private let apiClient = APIClient.shared func loadUserProfile(userId: String) async { isLoading = true error = nil do { let profile = try await apiClient.fetchUserProfile(userId: userId) await MainActor.run { self.userProfile = profile self.isLoading = false } } catch let apiError as APIError { await MainActor.run { self.error = apiError self.isLoading = false } } catch { await MainActor.run { self.error = APIError.networkError(error) self.isLoading = false } } } } ``` ## 5. Gebruik in SwiftUI View ```swift struct UserProfileView: View { @StateObject private var viewModel = UserProfileViewModel() let userId: String var body: some View { VStack { if viewModel.isLoading { ProgressView("Profiel laden...") } else if let error = viewModel.error { ErrorView(error: error, retryAction: { Task { await viewModel.loadUserProfile(userId: userId) } }) } else if let profile = viewModel.userProfile { ProfileContentView(profile: profile) } } .task { await viewModel.loadUserProfile(userId: userId) } } } ``` ## Valkuilen en Optimalisaties ### Valkuilen: 1. **Token Management**: Vergeet niet token expiration te controleren 2. **Netwerk Status**: Altijd netwerkconnectiviteit controleren 3. **Memory Leaks**: Gebruik `[weak self]` in completion handlers (bij Combine) 4. **Thread Safety**: Gebruik actors voor shared state ### Optimalisaties: 1. **Caching**: ```swift private let cache = NSCache<NSString, NSData>() func cachedRequest<T: Codable>(endpoint: String) async throws -> T { let cacheKey = endpoint as NSString if let cachedData = cache.object(forKey: cacheKey) as Data? { return try JSONDecoder().decode(T.self, from: cachedData) } // ... voer normale request uit en cache resultaat } ``` 2. **Request Batching**: Combineer meerdere requests 3. **Background Refresh**: Vernieuw data op de achtergrond 4. **Request Deduplication**: Voorkom dubbele requests ### Combine Alternatief: ```swift import Combine class CombineAPIClient { func fetchUserProfile(userId: String) -> AnyPublisher<UserProfile, APIError> { // Implementeer met URLSession.dataTaskPublisher // Voeg retry logica toe met .retry() operator // Gebruik .share() voor gedeelde subscriptions } } ``` Deze implementatie biedt een robuuste, schaalbare en onderhoudbare oplossing met moderne Swift Concurrency, goede foutafhandeling en optimale performance.
error: Generation failed [deepseek] prompt_id=5013 locale=en err=DeepSeek connection/timeout