JSTAcademy
0 XP
Dashboard
Crash Courses
Protocols & Extensions
18 min
Masters+180 XP
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]
0%