🧑🔧 Implementing Error Handling 🦺
The ultimate screen capturing tool for Mac
Upgrade your productivity with the most powerful screen capture tool for Mac. CleanShot will get you addicted to taking beautiful screenshots in no time.
Welcome to issue #40 of the iOS Coffee Break Newsletter 📬 and to the 4th issue of the "Building a Newsletter App" series!
Last week, I covered how to use mock data to implement views and introduced 3 new implementations of the IssuesLiveRepository
that can be used as the datasource of the IssuesView
.
This week, I am diving into error handling to my network client by creating a new enum that extends Swift's built-in Error type for better error management.
The Plan
Today, I am going to focus on effectively handling errors in Swift. I will cover:
- How to declare errors
- How to throw errors
- How to catch errors
- Common error handling patterns
Creating an APIError Enum
Before I can throw an error, I need to make a list of all the possible errors I want to throw. In my case, I am interested to handle network errors.
To do this, I need to create an APIError
enum that represents my type of error:
enum APIError: LocalizedError, Equatable {
[...]
case custom(error: Error)
case failedToDecode
case invalidStatusCode(statusCode: Int)
case invalidUrl
var errorDescription: String? {
switch self {
case .custom(let error):
return "Something went wrong: \(error.localizedDescription)."
case .failedToDecode:
return "Failed to decode."
case .invalidStatusCode(let statusCode):
return "Status code falls into the wrong range: \(statusCode)."
case .invalidUrl:
return "URL is not valid."
}
}
}
Updating the APIClient
Next, I need to use the APIError
and refactor the API request.
Here is how my getIssues()
from the APIClient
now looks like:
class APIClient {
[...]
func getIssues() async throws -> [Issue] {
guard let url = URL(string: "https://www.ioscoffeebreak.com/api/feed.json") else {
throw APIError.invalidUrl
}
let request = URLRequest(url: url)
let (data, res) = try await session.data(for: request)
guard let res = res as? HTTPURLResponse, (200...300) ~= res.statusCode else {
let statusCode = (res as? HTTPURLResponse)?.statusCode ?? 500
throw APIError.invalidStatusCode(statusCode: statusCode)
}
let issuesResponse = try decoder.decode(IssuesResponse.self, from: data)
return issuesResponse.issues
}
}
Refactoring the Issues View Model
To catch a thrown error in Swift I need to use the do-catch
statement.
Here is how my IssuesViewModel
now looks like:
@Observable
class IssuesViewModel {
[...]
@MainActor
func getIssues() async {
self.isLoading = true
defer { self.isLoading = false }
do {
self.issues = try await issuesRepository.getIssues()
self.hasError = false
self.error = nil
} catch let error as APIError {
self.issues = []
self.hasError = true
self.error = error
} catch {
self.issues = []
self.hasError = true
self.error = .custom(error: error)
}
}
}
🤝 Wrapping Up
That wraps up this guide on handling errors by leveraging an enum that conforms to Swift's Error type for improved error management.
Next week, I will focus on adding unit tests to my newsletter app — covering why they matter and how to implement them effectively. Stay tuned!
CURATED FROM THE COMMUNITY
📦 CI/CD for Swift Packages Webinar
Do you work with Swift Packages on a daily basis? Or do you happen to maintain a Swift library that is installable via SPM?
This week, Pol Piela hosted a free webinar packed with helpful tips and best practices to streamline your workflow and boost your confidence when releasing new versions of your Swift packages. If you couldn't catch it live, no worries — chances are Pol will share the recording or related resources in his newsletter. Keep an eye out!
✍️ Documenting your code with DocC
Writing clear documentation is more essential than ever, especially now that modular app architectures are becoming the norm.
Majid recently published a great article on this subject, where he walks through a simple example of how to document a function effectively.
⏳ Working with The task modifier In SwiftUI
It is often necessary to perform async tasks right when a view appears in SwiftUI.
One way to handle this is by creating a Task inside the onAppear
modifier's action closure — but there is a more elegant solution.
Gabriel's latest blog post dives into the task
modifier in SwiftUI.
It explains how to execute async code when a view appears, walks through its various configuration options, and highlights some helpful insights.