Swift Protocol Oriented Programming
Protocol oriented programming builds software from small capabilities and avoids deep family trees. Each protocol describes one ability, such as flying, swimming, or printing. A type collects the abilities it needs and ignores the rest. Apple designed much of the Swift standard library around this idea.
Think of job skills on a resume. A person may drive, cook, and speak two languages. Nobody inherits all skills from a single parent. Protocol oriented design lets each type pick its own skills in the same way.
Diagram: Skills Versus Family Trees
Class inheritance Protocol composition
Animal Flyable Swimmable
| \ /
Bird \ /
| Duck
FlyingBird (adopts both)
|
Duck (inherits unwanted traits)Protocols Define Abilities
A protocol lists the properties and methods a type must provide. Any struct, class, or enum can adopt it.
protocol Flyable {
func fly()
}
protocol Swimmable {
func swim()
}
struct Duck: Flyable, Swimmable {
func fly() { print("Duck flaps wings") }
func swim() { print("Duck paddles") }
}
struct Fish: Swimmable {
func swim() { print("Fish glides") }
}The fish adopts only the swimming skill. A class hierarchy would force a fish to carry a useless flying method or an awkward workaround.
Default Implementations With Extensions
A protocol extension supplies ready-made behavior. Types adopt the protocol and receive the behavior without writing any code.
protocol Greeter {
var name: String { get }
}
extension Greeter {
func greet() -> String {
"Hello, I am \(name)"
}
}
struct Teacher: Greeter {
let name: String
}
print(Teacher(name: "Ms. Rao").greet())
// Output: Hello, I am Ms. RaoThe protocol asks for a name. The extension builds the greet method from it. Every adopting type gains the method for free.
Overriding the Default
A type can provide its own version when the default does not fit. The requirement must appear in the protocol itself for the custom version to run through a protocol-typed value.
protocol Describable {
func describe() -> String
}
extension Describable {
func describe() -> String { "A generic item" }
}
struct Book: Describable {}
struct Movie: Describable {
func describe() -> String { "A film with a story" }
}
print(Book().describe()) // Output: A generic item
print(Movie().describe()) // Output: A film with a storyProtocol Composition
The ampersand joins several protocols into one requirement. A function can demand exactly the skills it needs.
func rescue(_ animal: some Flyable & Swimmable) {
animal.fly()
animal.swim()
}
rescue(Duck())
// Output:
// Duck flaps wings
// Duck paddlesPassing a Fish to this function triggers a compile error, because a fish cannot fly. Swift catches the mistake before the app runs.
Constrained Extensions
An extension can apply only to types that meet extra conditions.
extension Flyable where Self: Swimmable {
func splashLanding() {
fly()
swim()
print("Splash landing complete")
}
}
Duck().splashLanding()Only types that adopt both protocols receive splashLanding.
Storing Mixed Types
Different types that share one protocol can live in the same array. Mark the element type with any.
let swimmers: [any Swimmable] = [Duck(), Fish()]
for swimmer in swimmers {
swimmer.swim()
}
// Output:
// Duck paddles
// Fish glidesWhy Developers Prefer This Style
- Value types such as structs adopt protocols, which keeps data safe and predictable.
- Small protocols stay easy to understand and test.
- Types combine abilities freely without a rigid hierarchy.
- Test code swaps real components for simple stand-ins that adopt the same protocol.
Common Mistakes
Developers sometimes create one large protocol with ten requirements. Large protocols behave like a heavy base class. Split them into small protocols that each describe a single ability.
Defining a method only in a protocol extension and not in the protocol itself produces surprising behavior. The call picks the default version when the variable has the protocol type. Declare the method in the protocol when custom versions must run.
Real-World Use
Swift's own library follows this plan. Types such as Equatable, Hashable, and Comparable each describe one ability. Apps define protocols for networking, storage, and analytics, and tests swap the real parts for simple stand-ins.
Testing With Stand-Ins
Protocols make testing simple. Suppose a screen loads data through a protocol named DataLoader. The real app passes a loader that contacts a server. A test passes a tiny struct that returns fixed sample data. The screen never knows the difference, and the test runs in milliseconds without a network.
This swap-in habit is one of the strongest reasons professional teams adopt protocol oriented design. Flexible parts, small interfaces, and clear boundaries keep large apps maintainable.
Key Takeaways
- Protocols describe abilities, and types adopt the abilities they need.
- Protocol extensions give default behavior to every adopting type.
- The ampersand combines protocols into one requirement.
- Composition replaces deep inheritance chains.
