Protocols define contracts that types must conform to. Swift's error handling uses typed errors with try/catch.
Protocols
SWIFT
// Protocol definition
protocol Drawable {
func draw()
// Default implementation
func highlight() {
print("Highlighted!")
}
}
// Conformance
struct Circle: Drawable {
let radius: Double
func draw() {
print("Drawing circle with radius \(radius)")
}
// highlight() uses default implementation
}
struct Square: Drawable {
let side: Double
func draw() {
print("Drawing square with side \(side)")
}
}
// Protocol with associated types
protocol Container {
associatedtype Item
var count: Int { get }
mutating func append(_ item: Item)
subscript(index: Int) -> Item { get }
}
struct Stack<Element>: Container {
private var items: [Element] = []
var count: Int { items.count }
mutating func append(_ item: Item) {
items.append(item)
}
subscript(index: Int) -> Element {
items[index]
}
mutating func pop() -> Element? {
items.popLast()
}
}Protocol Extensions
SWIFT
// Add functionality to all conforming types
protocol Greetable {
var name: String { get }
}
extension Greetable {
func greet() {
print("Hello, I'm \(name)!")
}
func formalGreeting() {
print("Good day. My name is \(name).")
}
}
struct User: Greetable {
let name: String
}
User(name: "Alice").greet() // Hello, I'm Alice!
// Protocol composition
func display(_ item: Drawable & Greetable) {
item.draw()
item.greet()
}
// Protocol in extensions
extension Array: Container where Element: Codable {
// Custom conformance
}Error Handling
SWIFT
// Define error types
enum NetworkError: Error {
case timeout
case invalidResponse(statusCode: Int)
case unauthorized
case decodingFailed(underlying: Error)
}
// Functions that can throw
func fetchData(from url: String) throws -> Data {
guard let url = URL(string: url) else {
throw NetworkError.invalidResponse(statusCode: 0)
}
// In real code, this would be async
guard let data = try? Data(contentsOf: url) else {
throw NetworkError.timeout
}
return data
}
// Handling errors
do {
let data = try fetchData(from: "https://api.example.com")
print("Got \(data.count) bytes")
} catch NetworkError.timeout {
print("Request timed out")
} catch NetworkError.invalidResponse(let code) {
print("Invalid response: \(code)")
} catch {
print("Unexpected error: \(error)")
}
// try? — Convert to optional
let data = try? fetchData(from: "https://api.example.com")
// data is Data?
// try! — Force (crash on error)
// let data = try! fetchData(from: "https://api.example.com") // Dangerous!
// Propagate errors
func processUser() throws {
let data = try fetchData(from: "/users")
// ... process data
}
// Rethrows
func perform(_ operation: () throws -> Void) rethrows {
try operation()
}Enums with Associated Values
SWIFT
// Powerful pattern for modeling states
enum LoadingState<T> {
case idle
case loading(progress: Double)
case success(data: T)
case failure(error: Error)
var isLoading: Bool {
if case .loading = self { return true }
return false
}
}
var state: LoadingState<[User]> = .loading(progress: 0.5)
switch state {
case .idle:
print("Ready")
case .loading(let progress):
print("Loading: \(Int(progress * 100))%")
case .success(let users):
print("Got \(users.count) users")
case .failure(let error):
print("Error: \(error)")
}💡 Tip: Use protocols for defining capabilities, not just types. Protocol-oriented design makes your code more testable and flexible. Define protocols first, then build concrete types.
Next: Swift Collections