Help me een robuuste foutafhandelingsstrategie implementeren in mijn Swift iOS-app om de gebruikerservaring te verbeteren.
description
Door het implementeren van een robuuste foutafhandelingsstrategie kun je de gebruikerservaring aanzienlijk verbeteren door duidelijke feedback te geven, de responsiviteit van de app te behouden en het debuggen te vereenvoudigen.
prompt
try_prompt
Ik wil een uitgebreide foutafhandelingssysteem creëren in mijn Swift iOS-app dat netwerkfouten, gebruikersinvoerfouten en onverwachte uitzonderingen op een elegante manier beheert. Geef alstublieft begeleiding over het definiëren van aangepaste fouttypes, het weergeven van ... more
generate_helper
...
tags
ai_answers
provider: openai
model: gpt-4.1-nano
Uitstekend dat je een uitgebreide foutafhandelingssysteem wilt implementeren in je Swift iOS-app. Hieronder geef ik je stapsgewijze begeleiding over het definiëren van aangepaste fouttypes, gebruiksvriendelijke foutmeldingen, logging, retry-mechanismen en het behouden van responsiviteit.
1. Definieer Aangepaste Fouttypes
Gebruik enum met het Protocol Error om verschillende foutcategorieën te onderscheiden.
enum AppFout: Error {
case netwerkFout(onderliggendeFout: Error)
case serverFout(bericht: String)
case invoerFout(bericht: String)
case onverwachteFout
}
2. Foutafhandeling en Gebruikersvriendelijke Meldingen
Maak een functie die fouten afhandelt en een passende alert toont.
func handelFout(_ fout: Error, viewController: UIViewController) {
var bericht: String
if let appFout = fout as? AppFout {
switch appFout {
case .netwerkFout(let onderliggendeFout):
bericht = "Netwerkfout: \(onderliggendeFout.localizedDescription)"
case .serverFout(let serverbericht):
bericht = "Serverfout: \(serverbericht)"
case .invoerFout(let invoerbericht):
bericht = "Invoerfout: \(invoerbericht)"
case .onverwachteFout:
bericht = "Er is een onverwachte fout opgetreden."
}
} else {
bericht = "Onbekende fout: \(fout.localizedDescription)"
}
// Log de fout voor debugging
print("Fout: \(fout)")
// Toon een gebruikersvriendelijke alert
let alert = UIAlertController(title: "Fout", message: bericht, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
viewController.present(alert, animated: true)
}
3. Logging voor Debugging
Gebruik print() of een loggingframework (zoals OSLog) voor het vastleggen van fouten.
import os
let logger = OSLog(subsystem: "com.jouwapp", category: "fouten")
// Bij het afhandelen van fouten:
os_log("Fout: %{public}@", log: logger, type: .error, String(describing: fout))
4. Retry-mechanisme Implementeren
Gebruik bijvoorbeeld een timer of recursive functie om het opnieuw proberen te automatiseren.
func haalBerichtenOp(withRetryCount retries: Int = 3, viewController: UIViewController) {
fetchBerichten { resultaat in
switch resultaat {
case .success(let berichten):
// Verwerk berichten
print("Berichten succesvol opgehaald.")
case .failure(let fout):
handelFout(fout, viewController: viewController)
if retries > 0 {
// Wacht bijvoorbeeld 2 seconden voordat je opnieuw probeert
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.haalBerichtenOp(withRetryCount: retries - 1, viewController: viewController)
}
} else {
print("Maximal aantal retries bereikt.")
}
}
}
}
func fetchBerichten(completion: @escaping (Result<[Bericht], Error>) -> Void) {
// Implementatie van netverzoek
}
5. Responsiviteit Garanderen
Gebruik asynchrone calls en update UI op de hoofdthread.
- Gebruik URLSession dataTask met completion handler.
- Zorg dat UI-updates binnen DispatchQueue.main.async() plaatsvinden.
- Toon een laadindicator tijdens het ophalen.
Voorbeeld:
func fetchBerichten() {
let url = URL(string: "https://api.jouwserver.com/berichten")!
let taak = URLSession.shared.dataTask(with: url) { data, response, error in
DispatchQueue.main.async {
// Verberg laadindicator
}
if let error = error {
handelFout(AppFout.netwerkFout(onderliggendeFout: error), viewController: /* je viewcontroller */)
return
}
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
handelFout(AppFout.serverFout(bericht: "Onverwachte serverantwoord."), viewController: /* je viewcontroller */)
return
}
// Parse data en update UI
}
taak.resume()
}
6. Samenvatting
- Gebruik enums voor fouttypes.
- Toon gebruiksvriendelijke meldingen met UIAlertController.
- Log fouten voor debugging.
- Implementeer retry-mechanismen met vertragingen.
- Gebruik async/await of completion handlers voor responsiviteit.
- Update UI altijd op de hoofdthread.
Met deze aanpak zorg je voor een robuust, gebruikersvriendelijk en debugbaar foutafhandelingssysteem in je social media app.
provider: deepseek
model: deepseek-chat
Hier is een uitgebreide gids voor het implementeren van robuuste foutafhandeling in je Swift iOS social media app:
## 1. Aangepaste Fouttypes Definieren
```swift
enum NetworkError: Error, LocalizedError {
case invalidURL
case requestFailed(Int)
case decodingError
case noInternetConnection
case timeout
case serverError(String)
case unauthorized
var errorDescription: String? {
switch self {
case .invalidURL:
return "Ongeldige server URL"
case .requestFailed(let statusCode):
return "Serverfout: \(statusCode)"
case .decodingError:
return "Fout bij verwerken serverresponse"
case .noInternetConnection:
return "Geen internetverbinding"
case .timeout:
return "Time-out bij verbinden met server"
case .serverError(let message):
return "Serverfout: \(message)"
case .unauthorized:
return "Sessie verlopen, log opnieuw in"
}
}
}
enum UserInputError: Error, LocalizedError {
case emptyMessage
case messageTooLong(Int)
case invalidImage
case profanityDetected
var errorDescription: String? {
switch self {
case .emptyMessage:
return "Bericht mag niet leeg zijn"
case .messageTooLong(let maxLength):
return "Bericht is te lang (maximaal \(maxLength) tekens)"
case .invalidImage:
return "Ongeldige afbeelding"
case .profanityDetected:
return "Bericht bevat ongepaste inhoud"
}
}
}
enum AppError: Error, LocalizedError {
case unexpectedError
case databaseError
case cacheError
var errorDescription: String? {
switch self {
case .unexpectedError:
return "Er is een onverwachte fout opgetreden"
case .databaseError:
return "Fout bij opslaan gegevens"
case .cacheError:
return "Fout bij laden lokale gegevens"
}
}
}
```
## 2. Network Service met Retry Mechanism
```swift
class PostService {
private let maxRetryAttempts = 3
private let baseDelay: TimeInterval = 2.0
func fetchPosts(retryAttempt: Int = 0, completion: @escaping (Result<[Post], NetworkError>) -> Void) {
guard let url = URL(string: "https://api.jeapp.com/posts") else {
completion(.failure(.invalidURL))
return
}
var request = URLRequest(url: url)
request.timeoutInterval = 30
URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
if let error = error {
let networkError = self?.mapToNetworkError(error) ?? .unexpectedError
// Retry logica voor bepaalde fouten
if retryAttempt < self?.maxRetryAttempts ?? 0,
self?.shouldRetry(error: networkError) == true {
let delay = self?.calculateRetryDelay(attempt: retryAttempt) ?? 2.0
DispatchQueue.global().asyncAfter(deadline: .now() + delay) {
self?.fetchPosts(retryAttempt: retryAttempt + 1, completion: completion)
}
return
}
completion(.failure(networkError))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
completion(.failure(.unexpectedError))
return
}
switch httpResponse.statusCode {
case 200...299:
guard let data = data else {
completion(.failure(.decodingError))
return
}
do {
let posts = try JSONDecoder().decode([Post].self, from: data)
completion(.success(posts))
} catch {
completion(.failure(.decodingError))
}
case 401:
completion(.failure(.unauthorized))
case 500...599:
completion(.failure(.serverError("Server onderhoud")))
default:
completion(.failure(.requestFailed(httpResponse.statusCode)))
}
}.resume()
}
private func shouldRetry(error: NetworkError) -> Bool {
switch error {
case .timeout, .noInternetConnection, .serverError:
return true
default:
return false
}
}
private func calculateRetryDelay(attempt: Int) -> TimeInterval {
return baseDelay * pow(2.0, Double(attempt)) // Exponential backoff
}
private func mapToNetworkError(_ error: Error) -> NetworkError {
let nsError = error as NSError
switch nsError.code {
case NSURLErrorNotConnectedToInternet:
return .noInternetConnection
case NSURLErrorTimedOut:
return .timeout
case NSURLErrorCannotConnectToHost:
return .serverError("Kan niet verbinden met server")
default:
return .unexpectedError
}
}
}
```
## 3. Error Logger
```swift
class ErrorLogger {
static let shared = ErrorLogger()
private let logFileURL: URL
private init() {
let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
logFileURL = documentsPath.appendingPathComponent("error_log.txt")
}
func logError(_ error: Error, context: String = "", additionalInfo: [String: Any] = [:]) {
let timestamp = DateFormatter.localizedString(from: Date(), dateStyle: .medium, timeStyle: .medium)
var logMessage = """
[\(timestamp)] - \(context)
Fout: \(error.localizedDescription)
Type: \(type(of: error))
"""
if !additionalInfo.isEmpty {
logMessage += "\nExtra info: \(additionalInfo)"
}
if let localizedError = error as? LocalizedError {
logMessage += "\nRecovery: \(localizedError.recoverySuggestion ?? "Geen")"
}
logMessage += "\n" + String(repeating: "-", count: 50) + "\n"
// Schrijf naar console voor debugging
print(logMessage)
// Schrijf naar bestand voor persistente logs
writeToFile(logMessage)
// Stuur naar analytics service (optioneel)
sendToAnalytics(error: error, context: context)
}
private func writeToFile(_ message: String) {
if let data = message.data(using: .utf8) {
if FileManager.default.fileExists(atPath: logFileURL.path) {
if let fileHandle = try? FileHandle(forWritingTo: logFileURL) {
fileHandle.seekToEndOfFile()
fileHandle.write(data)
fileHandle.closeFile()
}
} else {
try? data.write(to: logFileURL)
}
}
}
private func sendToAnalytics(error: Error, context: String) {
// Integreer met je analytics service (Firebase, etc.)
let errorInfo: [String: Any] = [
"error_description": error.localizedDescription,
"error_type": "\(type(of: error))",
"context": context,
"timestamp": Date().timeIntervalSince1970
]
// Analytics.track("error_occurred", parameters: errorInfo)
}
}
```
## 4. Gebruikersvriendelijke Error Presentatie
```swift
class ErrorPresenter {
static func showError(_ error: Error, on viewController: UIViewController, retryHandler: (() -> Void)? = nil) {
let alertController = UIAlertController(
title: "Fout opgetreden",
message: error.localizedDescription,
preferredStyle: .alert
)
// Standaard actie
alertController.addAction(UIAlertAction(title: "OK", style: .default))
// Retry actie indien beschikbaar
if let retryHandler = retryHandler {
alertController.addAction(UIAlertAction(title: "Opnieuw proberen", style: .default) { _ in
retryHandler()
})
}
// Specifieke acties voor bepaalde fouten
if let networkError = error as? NetworkError, networkError == .unauthorized {
alertController.addAction(UIAlertAction(title: "Opnieuw inloggen", style: .destructive) { _ in
// Navigeer naar login scherm
self.navigateToLogin()
})
}
DispatchQueue.main.async {
viewController.present(alertController, animated: true)
}
}
private static func navigateToLogin() {
// Implementeer navigatie naar login scherm
}
}
```
## 5. ViewController Implementatie
```swift
class PostsViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var loadingIndicator: UIActivityIndicatorView!
@IBOutlet weak var errorView: UIView!
@IBOutlet weak var errorLabel: UILabel!
@IBOutlet weak var retryButton: UIButton!
private let postService = PostService()
private var posts: [Post] = []
override func viewDidLoad() {
super.viewDidLoad()
loadPosts()
}
private func loadPosts() {
setLoadingState(true)
hideError()
postService.fetchPosts { [weak self] result in
DispatchQueue.main.async {
self?.setLoadingState(false)
switch result {
case .success(let posts):
self?.posts = posts
self?.tableView.reloadData()
case .failure(let error):
self?.handleError(error)
ErrorLogger.shared.logError(
error,
context: "Posts laden",
additionalInfo: ["screen": "PostsViewController"]
)
}
}
}
}
private func handleError(_ error: Error) {
showErrorView(message: error.localizedDescription)
// Toon gebruikersvriendelijke melding
ErrorPresenter.showError(error, on: self) { [weak self] in
self?.loadPosts()
}
}
private func setLoadingState(_ loading: Bool) {
DispatchQueue.main.async {
self.loadingIndicator.isHidden = !loading
loading ? self.loadingIndicator.startAnimating() : self.loadingIndicator.stopAnimating()
self.tableView.isHidden = loading
}
}
private func showErrorView(message: String) {
DispatchQueue.main.async {
self.errorLabel.text = message
self.errorView.isHidden = false
self.tableView.isHidden = true
}
}
private func hideError() {
DispatchQueue.main.async {
self.errorView.isHidden = true
self.tableView.isHidden = false
}
}
@IBAction func retryButtonTapped(_ sender: UIButton) {
loadPosts()
}
}
```
## 6. Gebruikersinvoer Validatie
```swift
class PostValidator {
static func validatePost(content: String, image: UIImage?) -> Result<Void, UserInputError> {
// Controleer lege content
if content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
return .failure(.emptyMessage)
}
// Controleer maximale lengte
if content.count > 500 {
return .failure(.messageTooLong(500))
}
// Controleer op ongepaste taal (eenvoudige implementatie)
if containsProfanity(content) {
return .failure(.profanityDetected)
}
// Valideer afbeelding indien aanwezig
if let image = image, image.size.width > 4000 || image.size.height > 4000 {
return .failure(.invalidImage)
}
return .success(())
}
private static func containsProfanity(_ text: String) -> Bool {
let profanityWords = ["slechtwoord1", "slechtwoord2"] // Vervang met echte lijst
return profanityWords.contains { text.lowercased().contains($0) }
}
}
```
## Belangrijke Best Practices:
1. **Blijf responsief**: Gebruik altijd `DispatchQueue.main.async` voor UI updates
2. **Geef feedback**: Toon loading indicators tijdens netwerkrequests
3. **Log alles**: Houd gedetailleerde logs bij voor debugging
4. **Toon bruikbare meldingen**: Vermijd technische jargon voor eindgebruikers
5. **Implementeer retry strategie**: Gebruik exponential backoff voor netwerkrequests
6. **Valideer lokaal**: Controleer gebruikersinvoer voordat je naar server stuurt
Deze implementatie zorgt voor een robuuste foutafhandeling die zowel gebruikersvriendelijk is als developers helpt bij het debuggen van problemen.

