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
try_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 ... more
generate_helper
...
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

