🧑‍🔧 Implementing Error Handling 🦺

Building a Newsletter App
Error Handling
April 7, 2025
Sponsored

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.