Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
44: Protocol Composition
Imagine you're hiring for a very specific role at a company—say, a "Safety Inspector." To do this job, the person can't just be a licensed driver, and they can't just be a certified electrician. They have to be both. If they only have one of those certifications, they can't step onto the job site. They must exist at the intersection of those two sets of skills.
In Swift, we often run into the same problem. You might have a few small, focused protocols that do one thing well, but you'll eventually write a function that requires a type to satisfy multiple protocols at once. This is where Protocol Composition comes in. Instead of creating a brand new "mega-protocol" that inherits from others, you can just use the & operator to demand a combination of requirements on the fly.
Combining requirements on the fly
Let's look at a practical example. Suppose you're building a document management system. You have a Readable protocol for things that can be viewed, and a Writable protocol for things that can be edited.
protocol Readable {
func readContent() -> String
}
protocol Writable {
func write(content: String)
}
struct Note: Readable, Writable {
var text: String = "Hello!"
func readContent() -> String { return text }
func write(content: String) { print("Writing \(content)...") }
}
struct ReadOnlyFile: Readable {
func readContent() -> String { return "I am read-only." }
}
Now, imagine you have a function called syncDocument. This function needs to read the content from one place and write it to another. It can't work with just a Readable object (because it needs to write) and it can't work with just a Writable object (because it needs to read). It needs something that is both.
Here is how you compose those protocols:
func syncDocument(document: Readable & Writable) {
let content = document.readContent()
document.write(content: "Synced: \(content)")
}
let myNote = Note()
let myFile = ReadOnlyFile()
syncDocument(document: myNote) // Works perfectly!
// syncDocument(document: myFile) // Compiler error: ReadOnlyFile does not conform to Writable
Why not just make a new protocol?
I've had students ask me, "Why not just create a protocol ReadWrite: Readable, Writable {}?" You absolutely can, and if you find yourself using Readable & Writable in twenty different places, you probably should.
But composition is powerful because it prevents "protocol bloat." If you have five different small protocols, creating every possible combination of them as new named protocols would lead to a combinatorial explosion of names. ReadableAndWritable, ReadableAndSearchable, WritableAndSearchable, ReadableAndWritableAndSearchable... it gets messy fast. Using the & operator keeps your codebase lean by defining the requirement exactly where it's needed.
Using typealiases for readability
If a composition is used frequently but doesn't feel like a full-blown "entity" in your domain, I usually recommend a typealias. It gives you the best of both worlds: a clean name for the requirement without the overhead of a formal protocol hierarchy.
typealias ReadWriteDocument = Readable & Writable
func archiveDocument(document: ReadWriteDocument) {
// Logic here
}
It's a subtle distinction, but it makes your intent clear. You're telling other developers, "I don't care what this object is; I just care that it can be read and written to."
📋 Practical Task
Building a Secure Storage Coordinator
You are building a security module for an app. You have two protocols: Encryptable (which requires a method encrypt(_ data: String) -> String) and Storable (which requires a method save(data: String)).
Your Task:
- Define the
EncryptableandStorableprotocols. - Create a struct called
SecureVaultthat conforms to both. - Create a struct called
PublicFolderthat conforms only toStorable. - Write a function called
persistSecretlythat takes a parameter. Use protocol composition to ensure the parameter must be bothEncryptableandStorable. - Inside
persistSecretly, call theencryptmethod and then pass that result into thesavemethod. - Call the function using an instance of
SecureVaultto verify it works, and attempt to call it withPublicFolderto confirm the compiler stops you.
There are no comments for now.