Error Handling in Swift (with version cues)

Practical patterns for throwing, typed throws, and interop.

Version Quick Reference

  • Swift 5.x: untyped throws, rethrows, Result, try?, try!.
  • Swift 6+: typed throws (throws(SomeError)) available by default; strict concurrency checking may surface error-sendability issues.

Basics

  • Use throws for recoverable issues; keep errors small and domain-specific.
  • Prefer enums conforming to Error; include minimal context (payloads or userInfo when bridging).
1
2
3
4
5
6
enum NetworkError: Error { case offline, timeout(seconds: Int) }

func fetch() throws -> Data {
    guard isOnline else { throw NetworkError.offline }
    return Data()
}

Typed Throws (Swift 6+)

  • Specify the exact error type to enable exhaustive handling at call sites.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
enum LoginError: Error { case offline, invalidCredentials }

func login(email: String, password: String) throws(LoginError) -> Session { ... }

do {
    _ = try login(email: "a@b.com", password: "pw")
} catch LoginError.offline {
    // precise recovery
} catch {
    // optional fallback if bridging from ObjC or unexpected errors arise
}

Rethrows

  • Use rethrows when a function only throws if its function parameter throws—helpful for higher-order functions.
1
2
3
func mapOrThrow<T>(_ values: [String], transform: (String) throws -> T) rethrows -> [T] {
    try values.map(transform)
}

Result vs throws

  • throws is ergonomic inside async/await and linear code.
  • Result is useful for storing or combining errors, or when API shape must be synchronous but errorful.
1
let outcome: Result<Data, NetworkError> = Result { try fetch() }

Async Error Propagation

  • async throws pairs naturally; prefer this over callback-based completion handlers.
  • For cancellation, treat CancellationError distinctly to avoid masking user intent.
1
2
3
4
5
6
7
func loadProfile() async throws -> Profile { ... }

do {
    _ = try await loadProfile()
} catch is CancellationError {
    // respect user cancellation
}

Bridging and Interop

  • Objective-C NSError maps to Swift Error; annotate ObjC with NS_SWIFT_NAME and NSError ** patterns.
  • When exposing Swift APIs to ObjC, typed throws are not visible; design ObjC-friendly overloads if needed.

Testing Errors

  • Use XCTAssertThrowsError with pattern matching to assert specific cases.
  • For typed throws, tests become clearer and more exhaustive.
1
2
3
XCTAssertThrowsError(try login(email: "", password: "")) { error in
    XCTAssertEqual(error as? LoginError, .invalidCredentials)
}