Lesson 87Error Handling
Failable Initializers
When Initialization Can Fail
Sometimes creating an instance should fail if the input is invalid. Failable initializers (init?) return an optional - either an instance or nil.
Syntax
init?(parameters) {
if invalid { return nil }
// Initialize properties
}
if invalid { return nil }
// Initialize properties
}
init? vs init throws
init? (Failable)
Returns nil on failure
Simple pass/fail cases
init throws (Throwing)
Throws specific error
Need error details
Common Use Cases
- Input validation - Age, email, URL format
- Raw value enums - Auto-generated init?(rawValue:)
- Type conversion - String to Int, JSON to Model
- Resource loading - Files, images that may not exist
Important Rules
- All stored properties must be set before returning nil
- Can override failable with non-failable (not vice versa)
- Subclass failable init can delegate to superclass failable init
init! (Implicitly Unwrapped)
init! returns an implicitly unwrapped optional. Use sparingly - it will crash if nil is assigned to a non-optional!
main.swift
// FAILABLE INITIALIZERS
// Initializers that might fail and return nil
// BASIC FAILABLE INITIALIZER
struct Temperature {
var celsius: Double
init?(celsius: Double) {
// Absolute zero is -273.15°C
if celsius < -273.15 {
return nil
}
self.celsius = celsius
}
}
let valid = Temperature(celsius: 25) // Optional(Temperature)
let invalid = Temperature(celsius: -300) // nil
// Using with optional binding
if let temp = Temperature(celsius: 100) {
print("Temperature: \(temp.celsius)°C")
}
// FAILABLE INIT WITH ENUM
enum Direction: String {
case north, south, east, west
// Automatically generated failable init for raw values
}
let dir = Direction(rawValue: "north") // Optional(Direction.north)
let bad = Direction(rawValue: "up") // nil
// CUSTOM FAILABLE ENUM INIT
enum HTTPStatus {
case ok
case notFound
case serverError
case unknown(Int)
init?(code: Int) {
switch code {
case 200: self = .ok
case 404: self = .notFound
case 500: self = .serverError
case 100...599: self = .unknown(code)
default: return nil // Invalid HTTP code
}
}
}
let status = HTTPStatus(code: 200) // .ok
let invalid2 = HTTPStatus(code: 999) // nil
// FAILABLE VS THROWING INITIALIZERS
// Failable (init?) - Returns nil on failure
// Throwing (init throws) - Throws specific error
// Failable - simple yes/no
struct Age {
var value: Int
init?(value: Int) {
guard value >= 0, value <= 150 else {
return nil
}
self.value = value
}
}
// Throwing - detailed error info
struct AgeThrows {
var value: Int
enum AgeError: Error {
case negative
case tooOld
}
init(value: Int) throws {
if value < 0 {
throw AgeError.negative
}
if value > 150 {
throw AgeError.tooOld
}
self.value = value
}
}
// CHAINING FAILABLE INITIALIZERS
class Animal {
var name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class Dog: Animal {
var breed: String
init?(name: String, breed: String) {
if breed.isEmpty { return nil }
self.breed = breed
super.init(name: name) // Can fail!
}
}
let dog = Dog(name: "Max", breed: "Labrador") // Optional(Dog)
let badDog = Dog(name: "", breed: "Poodle") // nil
// OVERRIDING FAILABLE INIT
class Document {
var name: String?
init() { }
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class AutoNamedDocument: Document {
override init() {
super.init()
self.name = "Untitled"
}
// Override failable with non-failable
override init(name: String) {
super.init()
if name.isEmpty {
self.name = "Untitled"
} else {
self.name = name
}
}
}
// IMPLICITLY UNWRAPPED FAILABLE (init!)
struct Config {
var apiKey: String
init!(apiKey: String) {
if apiKey.isEmpty { return nil }
self.apiKey = apiKey
}
}
// Returns implicitly unwrapped optional
let config: Config = Config(apiKey: "abc123") // Force unwrapped
// let badConfig: Config = Config(apiKey: "") // Crash!
// PRACTICAL EXAMPLE: URL Validation
struct ValidatedURL {
let url: URL
init?(string: String) {
guard let url = URL(string: string),
url.scheme == "https" else {
return nil
}
self.url = url
}
}
let validURL = ValidatedURL(string: "https://apple.com")
let invalidURL = ValidatedURL(string: "not-a-url") // nil
// COMBINING WITH RESULT
enum ValidationResult<T> {
case success(T)
case failure(String)
}
struct Email {
let address: String
init?(address: String) {
guard address.contains("@"),
address.contains(".") else {
return nil
}
self.address = address
}
}Try It Yourself!
Create a CreditCard struct with a failable initializer that validates the card number using the Luhn algorithm!