Protocols in Swift


Introduction to Protocols in Swift

A protocol in Swift defines a blueprint of methods, properties, and other requirements that a class, structure (struct), or enumeration (enum) must follow. It ensures that different types conform to the same set of behaviors, promoting code consistency, modularity, and reusability.

Swift protocols are widely used in protocol-oriented programming (POP), a key paradigm in Swift that emphasizes composition over inheritance.

Key Features of Protocols

  • Define required methods and properties.
  • Adopted by classes, structs, and enums.
  • Support default implementations via protocol extensions.
  • Enable multiple inheritance in Swift.
  • Used in delegation, abstraction, and decoupling.

1. Basic Protocol Syntax

protocol Greetable {
     var name: String { get } // Read-only property
     func greet() -> String
}

The Greetable protocol defines a name property and a greet() method. Any type conforming to this protocol must implement these requirements.

2. Conforming to a Protocol

A struct implementing the Greetable protocol:

struct Person: Greetable {
     var name: String

     func greet() -> String {
         return "Hello, my name is \(name)."
     }
}

let person = Person(name: "Alice")
print(person.greet())

Output

Hello, my name is Alice.

3. Using Protocols with Classes

A class can also conform to a protocol:

class Animal: Greetable {
     var name: String

     init(name: String) {
         self.name = name
     }

     func greet() -> String {
         return "I am a \(name)."
     }
}

let dog = Animal(name: "Dog")
print(dog.greet())

Output

I am a Dog.

4. Protocols with Mutating Methods

When using mutating methods inside a struct or enum, you must mark them as mutating in the protocol.

protocol Toggleable {
     mutating func toggle()
}

struct LightSwitch: Toggleable {
     var isOn = false

     mutating func toggle() {
         isOn.toggle()
     }
}

var switch1 = LightSwitch()
print(switch1.isOn) // false
switch1.toggle()
print(switch1.isOn) // true

Output

false
true

5. Protocols with Computed Properties

A protocol can define computed properties, but they must be marked as { get } or { get set }.

protocol Describable {
     var description: String { get }
}

struct Car: Describable {
     var model: String

     var description: String {
         return "Car Model: \(model)"
     }
}

let myCar = Car(model: "Tesla Model S")
print(myCar.description)

Output

Car Model: Tesla Model S

6. Protocol Inheritance

A protocol can inherit from one or more protocols.

protocol Vehicle {
     var speed: Int { get set }
}

protocol Electric {
     var batteryLevel: Int { get }
}

protocol ElectricCar: Vehicle, Electric {}

struct Tesla: ElectricCar {
     var speed: Int
     var batteryLevel: Int
}

let myTesla = Tesla(speed: 120, batteryLevel: 85)
print("Speed: \(myTesla.speed), Battery: \(myTesla.batteryLevel)%")

Output

Speed: 120, Battery: 85%

7. Protocols with Default Implementations

A protocol extension can provide a default implementation.

protocol Drawable {
     func draw()
}

extension Drawable {
     func draw() {
         print("Default drawing action")
     }
}

struct Circle: Drawable {}
struct Square: Drawable {
     func draw() {
         print("Drawing a square")
     }
}

let circle = Circle()
circle.draw() // Uses default implementation

let square = Square()
square.draw() // Uses custom implementation

Output

Default drawing action
Drawing a square

8. Protocols with Delegation

Protocols are commonly used for delegation in Swift.

protocol DownloadDelegate {
     func didFinishDownload()
}

class Downloader {
     var delegate: DownloadDelegate?

     func startDownload() {
         print("Downloading...")
         delegate?.didFinishDownload()
     }
}

class ViewController: DownloadDelegate {
     func didFinishDownload() {
         print("Download completed successfully!")
     }
}

let vc = ViewController()
let downloader = Downloader()
downloader.delegate = vc
downloader.startDownload()

Output

Downloading...
Download completed successfully!

9. Associated Types in Protocols

Protocols can define generic placeholders using associatedtype.

protocol Container {
     associatedtype Item
     func add(_ item: Item)
     func getAllItems() -> [Item]
}

struct IntContainer: Container {
     private var items: [Int] = []

     func add(_ item: Int) {
         items.append(item)
     }

     func getAllItems() -> [Int] {
         return items
     }
}

var numbers = IntContainer()
numbers.add(5)
numbers.add(10)
print(numbers.getAllItems())

Output

[5, 10]

10. Type Constraints in Protocols

You can constrain a protocol to work with specific types.

protocol Summable {
     associatedtype Number: Numeric
     func sum(_ a: Number, _ b: Number) -> Number
}

struct Calculator: Summable {
     func sum(_ a: Int, _ b: Int) -> Int {
         return a + b
     }
}

let calc = Calculator()
print(calc.sum(5, 10))

Output

15

Best Practices for Using Protocols


  • Use protocols to define behaviors that multiple types can adopt.
  • Leverage protocol extensions to add default implementations.
  • Prefer protocol-oriented programming over class inheritance.
  • Use protocols for delegation to improve code reusability.
  • Use associated types for flexible generic programming.

Conclusion

Protocols in Swift enable flexible and reusable designs by defining standardized behaviors across different types. Whether used for protocol-oriented programming, delegation, or generic programming, protocols make Swift applications more modular, scalable, and maintainable.