A Swift protocol is a way of defining a set of requirements that a type must fulfill. Think of it like a job description. The protocol says “anyone who wants this role must be able to do these things” — but it doesn’t say exactly how they do it. That part is left to the type that signs up.
In practice, you’ll run into protocols constantly in iOS development. View in SwiftUI is a protocol. Identifiable, Hashable, Equatable, Codable — all protocols. They’re how the Swift programming language lets you write flexible, reusable code without forcing everything into a rigid class hierarchy.
In this guide, you’ll learn what a Swift protocol is, how to define one, how to conform to one, and how protocols show up in real iOS app code. By the end, this concept should feel like a natural part of your Swift toolkit.
The Basic Syntax
Here’s the simplest possible protocol you can write in Swift. You can paste this into an Xcode Playground and it will run immediately:
// Define the protocol
protocol Greetable {
var name: String { get }
func greet()
}
// Conform a struct to the protocol
struct Person: Greetable {
var name: String
func greet() {
print("Hi, I'm \(name)!")
}
}
// Use it
let chris = Person(name: "Chris")
chris.greet() // Hi, I'm Chris!Let’s walk through each part so you know exactly what’s happening:
| Line | What it does |
|---|---|
protocol Greetable { } | Defines a new protocol called Greetable. The name is entirely up to you. By convention, Swift protocol names tend to be adjectives that describe a capability — like “Greetable”, “Identifiable”, or “Comparable”. |
var name: String { get } | This says any type conforming to Greetable must have a name property that can be read. The { get } means it needs to be at minimum readable — more on this in a moment. |
func greet() | This says any conforming type must have a greet() method. Notice there’s no body here — just the signature. The actual implementation is left to whoever conforms to the protocol. |
struct Person: Greetable | The colon after Person followed by the protocol name means “Person promises to conform to Greetable”. If Person doesn’t fulfill every requirement, Swift will refuse to compile the code. |
var name: String (in Person) | This is the actual implementation of the name requirement. Person provides a stored property that satisfies the protocol’s demand. |
func greet() { ... } (in Person) | This is Person’s actual implementation of the greet() method. Every type that conforms to Greetable can have a completely different implementation — the protocol just ensures the method exists. |
Breaking It Down
Now that you’ve seen a basic protocol, let’s clear up a few things that trip up almost every beginner.
What does { get } and { get set } mean?
When you declare a property inside a protocol, you have to tell Swift whether that property needs to be readable, or both readable and writable. You do this with the { get } and { get set } syntax.
{ get }means the conforming type just needs to be able to return a value for this property. It can be a stored property, a computed property, a constant — anything that can be read.{ get set }means the conforming type must provide a property that can be both read and written. This rules out constants (let) — you’d need avar.
{ get set } in your protocol when you actually need it to be mutable. If the protocol only says { get }, conforming types are free to implement it as a constant — which means you won’t be able to change it later.The protocol body vs. the conforming type’s body
A protocol never contains actual code. It only lists what must exist. The real code — the implementation — lives in the struct, class, or enum that conforms to the protocol. This separation is intentional and powerful. Different types can implement the same protocol in completely different ways.
What can conform to a protocol?
In Swift, protocols can be adopted by structs, classes, and enums. This is actually a big advantage over class-based inheritance, which only works with classes. Because most types in Swift (including SwiftUI Views) are structs, protocols are the primary way to share capabilities across them.
You can conform to more than one protocol
A type can conform to as many protocols as you want. Just separate them with commas after the colon:
struct Employee: Greetable, Identifiable {
var id: UUID
var name: String
func greet() {
print("Hey, I'm \(name)")
}
}Variations and Syntax Patterns
Protocols in the Swift language appear in several forms. Here are the most important patterns you’ll encounter as a beginner.
protocol Nameable {
var name: String { get set } // must be readable AND writable
}
struct User: Nameable {
var name: String // var satisfies { get set }
}
var user = User(name: "Jordan")
user.name = "Taylor" // allowed because name is { get set }{ get set } when the property needs to be updated after creation. If you try to satisfy a { get set } requirement with a let constant, Swift will give you a compile error.protocol Describable {
func describe() -> String
}
// This function works with ANY type that conforms to Describable
func printDescription(_ item: Describable) {
print(item.describe())
}
struct Book: Describable {
var title: String
func describe() -> String { "Book: \(title)" }
}
struct Movie: Describable {
var title: String
func describe() -> String { "Movie: \(title)" }
}
printDescription(Book(title: "Swift Programming"))
printDescription(Movie(title: "The Matrix"))Book or a Movie — it just knows it can call describe() on whatever comes in.protocol Animal {
var name: String { get }
func makeSound()
}
// Pet inherits all requirements from Animal, plus adds its own
protocol Pet: Animal {
var owner: String { get }
}
// Dog must satisfy BOTH Animal and Pet requirements
struct Dog: Pet {
var name: String
var owner: String
func makeSound() { print("Woof!") }
}Pet inherits from Animal, any type conforming to Pet must satisfy the requirements of both Pet and Animal.protocol Greetable {
var name: String { get }
func greet()
}
// Extend the protocol to provide a default greet() implementation
extension Greetable {
func greet() {
print("Hello, I'm \(name).")
}
}
struct Robot: Greetable {
var name: String
// No greet() needed — uses the default from the extension
}
let r2 = Robot(name: "R2-D2")
r2.greet() // Hello, I'm R2-D2.protocol Resettable {
mutating func reset()
}
struct Counter: Resettable {
var count = 0
mutating func reset() {
count = 0 // mutating lets us change count on a struct
}
}
var c = Counter()
c.count = 10
c.reset()
print(c.count) // 0mutating. If your protocol defines a method that will change a property, mark it mutating func in the protocol. Classes don’t need this keyword, but if you add it to the protocol, it doesn’t hurt class conformance.protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
// This function requires a type that is BOTH Named AND Aged
func introduce(_ person: Named & Aged) {
print("I'm \(person.name) and I'm \(person.age) years old.")
}
struct Student: Named, Aged {
var name: String
var age: Int
}
introduce(Student(name: "Sam", age: 22))
// I'm Sam and I'm 22 years old.& operator combines protocols inline. Instead of creating a brand new protocol that inherits from both, you can write Named & Aged directly as a type. This is common in SwiftUI where you’ll see things like Identifiable & Hashable.How Swift Protocols Appear in Real iOS Code
Here’s where it gets satisfying. Once you understand protocols in theory, you start recognizing them everywhere in SwiftUI and iOS development.
Consider one of the most common patterns in a beginner iOS app: displaying a list of items. You’ll want each item to be Identifiable (so SwiftUI knows which row is which), and you might want them to be comparable or hashable. All of those are built-in Swift protocols.
import SwiftUI
// Conforming to Identifiable is required for ForEach in SwiftUI
struct Task: Identifiable {
let id: UUID = UUID() // Identifiable requires an 'id' property
var title: String
var isComplete: Bool
}
// A custom protocol for anything that can be marked as done
protocol Completable {
var isComplete: Bool { get set }
mutating func markComplete()
}
// Extend Task to also conform to our custom Completable protocol
extension Task: Completable {
mutating func markComplete() {
isComplete = true
}
}
// A SwiftUI view that works with any Completable & Identifiable type
struct TaskRow: View {
var task: Task
var body: some View {
HStack {
Image(systemName: task.isComplete ? "checkmark.circle.fill" : "circle")
.foregroundColor(task.isComplete ? .green : .gray)
Text(task.title)
}
}
}Notice a few things here. First, Task conforms to the built-in Identifiable protocol by providing an id property. That’s all SwiftUI needs to use Task inside a ForEach list.
Second, you can add protocol conformance after the fact using an extension. That’s what extension Task: Completable does. This is a very common pattern in real iOS code — you define your type, then you add conformances in separate extensions to keep things organized.
Third, notice that View in SwiftUI is itself a protocol. When you write struct TaskRow: View, you’re conforming your struct to the View protocol by implementing a body property. You’ve been using protocols from day one.
Common Mistakes to Avoid
These are the mistakes I see most often from students learning Swift protocols for the first time. Each one has a clear fix.
Mistake 1: Missing a required method or property
If your type claims to conform to a protocol but doesn’t implement every requirement, Swift will refuse to build your project. You’ll see an error like “Type does not conform to protocol”. This is actually a feature — Swift is catching the problem for you at compile time, not at runtime. Fix it by checking the protocol definition and making sure you’ve implemented everything it asks for.
Mistake 2: Using { get } when you need { get set }
If you declare a property as { get } in your protocol, conforming types are allowed to make that property a constant (let). If you later try to update that value, Swift won’t let you. Think carefully about whether the value needs to change after it’s set, and use { get set } accordingly.
Mistake 3: Forgetting mutating on a struct’s protocol method
If your protocol defines a mutating func and you’re conforming with a struct, you must also mark your implementation mutating. If you miss it, Swift will give you an error saying you can’t assign to a property. The fix is simple: add mutating to the method in both the protocol and the struct.
Mistake 4: Expecting the protocol to “do” something
A protocol is a contract, not an implementation. It can’t be instantiated on its own. You can’t write let g = Greetable() — that will fail because Greetable is just a description. You always need a concrete type (a struct, class, or enum) that actually implements the protocol’s requirements. The protocol just ensures those requirements exist.
Quick Reference
Here’s a cheat sheet of the syntax patterns covered in this article. Bookmark it for when you need a quick reminder while building.
| Syntax | What It Does |
|---|---|
| protocol MyProtocol { } | Defines a new protocol called MyProtocol |
| var name: String { get } | Requires a readable property — can be a let or var |
| var name: String { get set } | Requires a readable and writable property — must be a var |
| func greet() | Requires a method with no implementation in the protocol body |
| mutating func reset() | Requires a method that can modify struct properties |
| struct Foo: MyProtocol { } | Conforms Foo to MyProtocol |
| struct Foo: A, B, C { } | Conforms to multiple protocols at once |
| protocol Child: Parent { } | Protocol inheritance — Child includes all of Parent‘s requirements |
| extension MyProtocol { func greet() { } } | Provides a default implementation — conforming types can use or override it |
| func foo(_ x: A & B) | Protocol composition — parameter must conform to both A and B |
| extension Foo: MyProtocol { } | Adds protocol conformance to an existing type in a separate extension |
Using AI to Go Deeper with Swift Protocols
AI tools like Claude, GitHub Copilot, and Cursor are now a real part of how developers work. Knowing the right questions to ask is itself a skill — and protocols are a topic where AI tutoring can be genuinely helpful. Here are three ways to use these tools to strengthen your understanding.
When To Use Swift Protocols
Here are some real scenarios where a protocol is the right tool for the job in an iOS app:
- 📋
A to-do app with multiple task types
You might have simple tasks, recurring tasks, and collaborative tasks. They all need a
title, adueDate, and amarkComplete()method. Define aTaskprotocol and write your list UI to work with any conforming type — regardless of the specifics underneath. - 🏋️
A fitness tracker with different workout types
Running, cycling, and swimming all track duration and calories, but each has unique data too. A
Workoutprotocol with shared requirements lets you write one summary view and one history list that works for every workout type you add later. - 🧪
Making your code testable
This is one of the most practical uses of protocols that beginners often don’t hear about. If you define a protocol for your data service (e.g.
DataService), you can swap in a fake implementation during testing that returns predictable data — no real network calls required. This makes your app much easier to test.
Summary
Here’s a quick recap of what you learned about Swift protocols:
- A protocol defines requirements — properties and methods — that conforming types must implement
- Use
{ get }for readable properties and{ get set }for properties that need to be writable - Structs, classes, and enums can all conform to protocols
- A type can conform to multiple protocols by listing them separated by commas
- Protocol extensions let you provide default implementations that conforming types can use or override
- Mark protocol methods
mutating funcif they need to change a struct’s properties - Use
&for protocol composition when a function needs a type that satisfies multiple protocols - Built-in protocols like
Identifiable,Codable, andVieware already part of your daily SwiftUI code
The best way to get comfortable with protocols is to start noticing them in code you’re already writing. Look at your SwiftUI views — every single one is conforming to the View protocol. Look at the data types you pass into ForEach — they’re conforming to Identifiable. Protocols are everywhere once you know what to look for.
Once you’re comfortable with protocols, a natural next step is learning about generics in Swift — the two topics are closely related and generics become much easier to understand once protocols click.

