Lesson 84Error Handling
Try? and Try!
Simplified Error Handling
Sometimes you don't need full do-try-catch. Swift provides try? and try! for simpler scenarios.
try? vs try!
try? (Optional Try)
Returns optional - nil if error
let x = try? riskyFunc()Safe, loses error info
try! (Force Try)
Crashes if error thrown
let x = try! riskyFunc()Dangerous, use sparingly
When to Use try?
- When you don't care about the specific error
- When you have a default/fallback value
- When checking if something succeeds (bool-like)
- Combined with
if let,guard let,??
When to Use try!
Only when failure is impossible:
- Loading bundled resources that must exist
- In unit tests where you want to fail fast
- Programmer errors (assertions)
Never use try! with user input or network data!
Comparison Summary
| Syntax | On Success | On Error |
|---|---|---|
| try | Returns value | Jumps to catch |
| try? | Returns optional value | Returns nil |
| try! | Returns value | CRASH! |
main.swift
// TRY? AND TRY!
// Alternative ways to handle throwing functions
// SETUP
enum FileError: Error {
case notFound
case corrupted
case permissionDenied
}
func readFile(name: String) throws -> String {
if name.isEmpty {
throw FileError.notFound
}
if name == "corrupted.txt" {
throw FileError.corrupted
}
return "Contents of \(name)"
}
// ===== TRY? (Optional Try) =====
// Converts result to optional - nil if error thrown
let content1 = try? readFile(name: "data.txt")
// content1 is String? = "Contents of data.txt"
let content2 = try? readFile(name: "")
// content2 is String? = nil (error was thrown)
// Using with optional binding
if let content = try? readFile(name: "config.txt") {
print("Got content: \(content)")
} else {
print("Failed to read file")
}
// Using with nil coalescing
let data = try? readFile(name: "missing.txt") ?? "Default content"
print(data) // "Default content"
// Using with guard
func processFile(name: String) {
guard let content = try? readFile(name: name) else {
print("Cannot process - file not readable")
return
}
print("Processing: \(content)")
}
// Chaining multiple try?
let result = try? readFile(name: "a.txt")
.uppercased()
.trimmingCharacters(in: .whitespaces)
// ===== TRY! (Force Try) =====
// Crashes if error is thrown - use only when certain no error will occur
// SAFE: We know this won't throw
let knownGoodContent = try! readFile(name: "safe.txt")
// DANGEROUS: This will crash!
// let crash = try! readFile(name: "") // Fatal error!
// When to use try!
// 1. When failure is truly impossible
// 2. In tests where you want to fail fast
// 3. When loading bundled resources that must exist
// Example: Loading bundled JSON
func loadBundledConfig() -> [String: Any] {
let url = Bundle.main.url(forResource: "config", withExtension: "json")!
let data = try! Data(contentsOf: url)
return try! JSONSerialization.jsonObject(with: data) as! [String: Any]
}
// ===== COMPARISON =====
// try - Full error handling
do {
let a = try readFile(name: "file.txt")
print(a)
} catch {
print("Error: \(error)")
}
// try? - Optional result, ignore specific error
let b: String? = try? readFile(name: "file.txt")
// try! - Force unwrap, crash on error
let c: String = try! readFile(name: "known-good.txt")
// ===== PRACTICAL PATTERNS =====
// Pattern 1: Default value with try?
func getConfig(key: String) throws -> String {
throw FileError.notFound
}
let config = (try? getConfig(key: "theme")) ?? "default"
// Pattern 2: Multiple attempts with try?
func fetchFromPrimary() throws -> Data { throw FileError.notFound }
func fetchFromBackup() throws -> Data { return Data() }
let data2 = try? fetchFromPrimary() ?? (try? fetchFromBackup())
// Pattern 3: Converting try? to Bool
func validate(input: String) throws {
if input.isEmpty { throw FileError.notFound }
}
let isValid = (try? validate(input: "test")) != nil
// Pattern 4: Using try? in conditions
if (try? readFile(name: "lock.txt")) != nil {
print("File is locked")
}
// Pattern 5: Async try?
func fetchUser() async throws -> String {
return "User"
}
// In async context:
// let user = try? await fetchUser()Try It Yourself!
Create a JSON parsing function and use try? with nil coalescing to provide a default object when parsing fails!