Crash Courses · Masters
Protocols & Extensions
Define behavior contracts with protocols, implement protocol-oriented design, add functionality to any type with extensions, and use protocol composition.
18 min read+180 XP on completionCert: Swift Crash Course
Tap any word in the text below to start reading from there.
Protocols & Extensions
Defining a Protocol
protocol Describable {
var description: String { get } // read-only property requirement
func shortDescription() -> String // method requirement
}
protocol Identifiable {
var id: String { get }
}
// Conforming a struct
struct Product: Describable, Identifiable {
let id: String
let name: String
let price: Double
var description: String {
"Product(\(id)): \(name) at $\(String(format: "%.2f", price))"
}
func shortDescription() -> String { "\(name): $\(String(format: "%.2f", price))" }
}
// Conforming a class
class User: Describable, Identifiable {
let id: String
let name: String
var email: String?
init(id: String, name: String, email: String? = nil) {
self.id = id; self.name = name; self.email = email
}
var description: String {
"User(\(id)): \(name)" + (email.map { " <\($0)>" } ?? "")
}
func shortDescription() -> String { name }
}
// Use the protocol as a type
func printAll(_ items: [any Describable]) {
items.forEach { print($0.description) }
}
let items: [any Describable] = [
Product(id: "p1", name: "Laptop", price: 1299.0),
User(id: "u1", name: "Jordan", email: "j@jst.com"),
]
printAll(items)---
Protocol Extensions — Default Implementations
protocol Greetable {
var name: String { get }
func greet() -> String
}
extension Greetable {
// Default implementation — conformers get this for free
func greet() -> String {
return "Hello, I am \(name)."
}
func shout() -> String {
return greet().uppercased() + "!!"
}
}
struct Customer: Greetable {
let name: String
// greet() is provided by the extension — no need to implement
}
struct VIPCustomer: Greetable {
let name: String
// Override the default:
func greet() -> String { "Welcome, VIP \(name)!" }
}
let c = Customer(name: "Alice")
let v = VIPCustomer(name: "Bob")
print(c.greet()) // Hello, I am Alice.
print(v.greet()) // Welcome, VIP Bob!
print(c.shout()) // HELLO, I AM ALICE.!!---
Codable: Free JSON Serialization
import Foundation
struct Article: Codable {
let id: Int
let title: String
let author: String
let tags: [String]
let publishedAt: Date?
// Custom key names
enum CodingKeys: String, CodingKey {
case id, title, author, tags
case publishedAt = "published_at" // snake_case in JSON
}
}
// Encoding to JSON
let article = Article(
id: 1,
title: "Swift Crash Course",
author: "Jordan Morris",
tags: ["swift", "ios", "programming"],
publishedAt: Date()
)
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
encoder.dateEncodingStrategy = .iso8601
if let jsonData = try? encoder.encode(article),
let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
// Decoding from JSON
let jsonString = """
{
"id": 2,
"title": "iOS Development Guide",
"author": "Alice Johnson",
"tags": ["ios", "xcode"],
"published_at": null
}
"""
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let data = jsonString.data(using: .utf8),
let decoded = try? decoder.decode(Article.self, from: data) {
print("Decoded: \(decoded.title) by \(decoded.author)")
}---
Extensions on Existing Types
// Add methods to String
extension String {
var isPalindrome: Bool {
let cleaned = self.lowercased().filter { $0.isLetter }
return cleaned == String(cleaned.reversed())
}
func truncated(to length: Int, trailing: String = "...") -> String {
guard count > length else { return self }
return String(prefix(length)) + trailing
}
var wordCount: Int {
split(separator: " ").count
}
}
print("racecar".isPalindrome) // true
print("hello".isPalindrome) // false
print("Swift is amazing and fast".truncated(to: 10)) // "Swift is a..."
print("Hello World".wordCount) // 2
// Add to Int
extension Int {
var isEven: Bool { self % 2 == 0 }
var isOdd: Bool { !isEven }
func times(_ action: () -> Void) {
(0..<self).forEach { _ in action() }
}
}
print(7.isOdd) // true
print(8.isEven) // true
3.times { print("Hello!") } // prints 3 times
// Add Comparable conformance to a custom type
struct Version: Comparable, CustomStringConvertible {
let major, minor, patch: Int
var description: String { "\(major).\(minor).\(patch)" }
static func < (lhs: Version, rhs: Version) -> Bool {
if lhs.major != rhs.major { return lhs.major < rhs.major }
if lhs.minor != rhs.minor { return lhs.minor < rhs.minor }
return lhs.patch < rhs.patch
}
}
let versions = [Version(major:2,minor:1,patch:0), Version(major:1,minor:9,patch:5), Version(major:2,minor:0,patch:3)]
print(versions.sorted()) // [1.9.5, 2.0.3, 2.1.0]Reading progress
0% read
In this module
Protocol
Protocol Extension
Codable
Protocol Composition
0%