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
tags
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