Swift 3
Error Handling
Custom Error Codes
Swift Development
iOS Programming

Generate your own Error code in swift 3

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Custom error codes are useful when you need stable failure identifiers across UI, logs, analytics, or backend contracts. In Swift 3, the clean approach is to define a typed Error enum and keep the numeric code mapping in one place instead of scattering integers across the codebase.

Define a Typed Error Enum

A central enum gives you type safety in Swift while still letting you expose stable numeric codes.

swift
1import Foundation
2
3enum AppError: Error {
4    case invalidInput
5    case networkTimeout
6    case unauthorized
7    case serverFailure(status: Int)
8
9    var code: Int {
10        switch self {
11        case .invalidInput: return 1001
12        case .networkTimeout: return 2001
13        case .unauthorized: return 3001
14        case .serverFailure: return 2002
15        }
16    }
17}

This keeps the mapping centralized and prevents duplicate codes from appearing in random controllers or services.

Add Human-Readable Messages

Codes are useful for machines, but users and support teams still need readable text.

swift
1extension AppError: LocalizedError {
2    var errorDescription: String? {
3        switch self {
4        case .invalidInput:
5            return "Input is invalid."
6        case .networkTimeout:
7            return "Request timed out. Try again."
8        case .unauthorized:
9            return "Session expired. Please sign in again."
10        case .serverFailure(let status):
11            return "Server error with status code \(status)."
12        }
13    }
14
15    var recoverySuggestion: String? {
16        switch self {
17        case .invalidInput:
18            return "Check the form fields and retry."
19        case .networkTimeout:
20            return "Verify the connection and retry."
21        case .unauthorized:
22            return "Sign in again to continue."
23        case .serverFailure:
24            return "Try again later or contact support."
25        }
26    }
27}

That keeps human-facing text close to the error definition without losing the value of stable numeric codes.

Throw and Catch Errors With Type Safety

Once the enum exists, you can throw and catch it directly.

swift
1func performLogin(token: String?) throws {
2    guard let token = token, !token.isEmpty else {
3        throw AppError.invalidInput
4    }
5
6    if token == "expired" {
7        throw AppError.unauthorized
8    }
9}
10
11do {
12    try performLogin(token: "")
13} catch let err as AppError {
14    print("code=\(err.code) message=\(err.localizedDescription)")
15} catch {
16    print("unexpected error: \(error)")
17}

This preserves strong typing while still giving you access to the numeric code for logging or analytics.

Bridge to NSError When Needed

Many Apple frameworks and Objective-C boundaries still expect NSError. A small bridge keeps one source of truth while supporting those APIs.

swift
1extension AppError {
2    var asNSError: NSError {
3        return NSError(
4            domain: "com.example.app",
5            code: self.code,
6            userInfo: [
7                NSLocalizedDescriptionKey: self.localizedDescription,
8                NSLocalizedRecoverySuggestionErrorKey: self.recoverySuggestion ?? ""
9            ]
10        )
11    }
12}
13
14let ns = AppError.networkTimeout.asNSError
15print(ns.domain, ns.code)

This is often the cleanest way to integrate Swift-native errors with Cocoa APIs.

Use a Code-Range Policy

As the app grows, code collisions become likely unless you define ranges by subsystem.

Example policy:

  • '1000 range for validation,'
  • '2000 range for networking,'
  • '3000 range for authentication.'

That makes the codes easier to scan in logs and easier to document across teams.

Test the Contract

If other systems depend on these codes, add tests so refactors do not silently change them.

swift
1import XCTest
2
3final class AppErrorTests: XCTestCase {
4    func testCodeValues() {
5        XCTAssertEqual(AppError.invalidInput.code, 1001)
6        XCTAssertEqual(AppError.unauthorized.code, 3001)
7    }
8
9    func testNSErrorBridge() {
10        let err = AppError.networkTimeout.asNSError
11        XCTAssertEqual(err.code, 2001)
12        XCTAssertEqual(err.domain, "com.example.app")
13    }
14}

Stable codes are a contract. Contracts should be tested.

Common Pitfalls

A common mistake is throwing generic errors everywhere and then trying to recover meaning later from message text alone.

Another issue is duplicating numeric codes or messages in multiple files instead of centralizing the mapping.

Developers also sometimes forget Objective-C interoperability requirements and only discover the need for NSError bridging late in the integration process.

Summary

  • Define a typed Swift error enum with explicit code mapping.
  • Keep machine-readable codes and user-facing messages together but distinct in purpose.
  • Throw and catch the typed errors directly in Swift.
  • Bridge to NSError when frameworks or Objective-C APIs require it.
  • Test public error-code mappings so they remain stable over time.

Course illustration
Course illustration

All Rights Reserved.