How to use protocol in Swift


Protocols are a fundamental concept in Swift, playing a crucial role in Swift's "Protocol-Oriented Programming" paradigm.

They define a blueprint of methods, properties, and other requirements that can then be adopted by classes, structures, or enumerations.

This allows you to define shared behavior across different types without relying solely on class inheritance.


1. Defining a Protocol

protocol Greetable {
// Property requirement: must have a 'name' property that is readable
var name: String { get }

// Method requirement: must have a 'greet' method
func greet()

// Optional: mutating method requirement for value types (structs, enums)
mutating func updateName(newName: String)
}

- Properties: You only specify whether a property is get(readable) or get set (readable and writable).

You don't specify if it's stored or computed.

- Methods: You define the method signature without providing an implementation.

- mutating keyword: If a method in a protocol is intended to modify instances of value types(structs or enums) that conform to it, you must mark it with the mutating keyword. 

Classes don't need this when implementing such methods.

Type properties: To require a type property (static or class property), use the static keyword in the protocol definition.


2. Adopting and confroming to protocol

// Conforming a struct to Greetable

struct Person: Greetable {

    var name: String // Must implement 'name'

    

    func greet() { // Must implement 'greet()'

        print("Hello, my name is \(name).")

    }


    mutating func updateName(newName: String) { // Must implement 'updateName(newName:)'

        self.name = newName

    }

}


// Conforming a class to Greetable

class Robot: Greetable {

    var name: String

    

    init(name: String) {

        self.name = name

    }


    func greet() {

        print("Greetings, I am \(name).")

    }

    

    // No 'mutating' keyword needed for classes

    func updateName(newName: String) {

        self.name = newName

    }

}

To use a protocol, a type must adopt it and conform to its requirements.

- Multiple Protocols: A type can conform to multiple protocols by listing them, separated by commas

- Inheritance and Protocols: If a class inherits from a superclass, the protocol names follow the superclass name


3. Using Protocol Types

Once a type conforms to a protocol, you can use the protocol itself as a type.

This is powerful for creating flexible and decoupled code.

func introduce(entity: Greetable) {

    entity.greet()

    // You can't directly call updateName() here because 'entity' is a 'Greetable'

    // which could be a constant, even if the underlying type is a var.

    // If you need to modify, you might need to cast or ensure the parameter is `inout`

}


let human = Person(name: "Alice")

let cyborg = Robot(name: "Bender")


introduce(entity: human) // Output: Hello, my name is Alice.

introduce(entity: cyborg) // Output: Greetings, I am Bender.


// You can create arrays of protocol types

let greeters: [Greetable] = [human, cyborg]

for greeter in greeters {

    greeter.greet()

}

// Output:

// Hello, my name is Alice.

// Greetings, I am Bender.


4. Protocol Extensions

Protocol extensions allow you to provide default implementations for methods and computed properties that are defined in a protocol.

This enables you to add shared behavior to any type that conforms to the protocol without forcing them into an inheritance hierarchy.

protocol Describable {

    var description: String { get }

    func summarize() -> String

}


extension Describable {

    // Default implementation for summarize()

    func summarize() -> String {

        return "This is a describable item: \(description)"

    }

}


struct Item: Describable {

    var description: String = "A shiny new gadget"

    // No need to implement summarize() if the default is sufficient

}


let gadget = Item()

print(gadget.summarize()) // Output: This is a describable item: A shiny new gadget


// A type can still provide its own implementation, overriding the default

struct DetailedItem: Describable {

    var description: String = "An ancient artifact"

    

    func summarize() -> String {

        return "This ancient artifact is \(description) and has a long history."

    }

}


let artifact = DetailedItem()

print(artifact.summarize()) // Output: This ancient artifact is An ancient artifact and has a long history.


5. Associated Types

Protocols can define associated types, which act as placeholders for a type that will be specified by the conforming type.

This is useful when the protocol's methods or properties depend on a type that isn't known until the protocol is adopted.

protocol Container {

    associatedtype Item

    mutating func append(_ item: Item)

    var count: Int {get}

    subscript(i: Int) -> Item { get }

}


struct IntStack: Container {

    typealias Item = Int

    var items: [Int] = []

    

    mutating func append(_ item: Int) {

        items.append(item)

    }

    

    var count: Int {

        return items.count

    }

    

    subscript(i: Int) -> Int {

        return items[i]

    }

}


var stack = IntStack()

stack.append(10)

stack.append(20)

print(stack.count) // Output: 2

print(stack[0])    // Output: 10


6. Protocol Composition

You can combine multiple protocols into a single requirement using &. This allows a type to conform to a combination of behaviors.

protocol Identifiable {

    var id: String { get }

}


protocol Loggable {

    func logEvent(message: String)

}


func processUser(user: Identifiable & Loggable) {

    user.logEvent(message: "Processing user with ID: \(user.id)")

}


struct User: Identifiable, Loggable {

    let id: String

    func logEvent(message: String) {

        print("[LOG] \(message)")

    }

}


let myUser = User(id: "abc-123")

processUser(user: myUser)


7. AnyObject Protocol

The AnyObject protocol can be used as the type for a variable or parameter when you need to refer to any instance of a class type.

Only class types can conform to AnyObject.

protocol SomeClassProtocol: AnyObject {

    var someProperty: String { get set }

    func someMethod()

}


class MyClass: SomeClassProtocol {

    var someProperty: String = "Hello"

    func someMethod() {

        print("Some method called from MyClass")

    }

}


// Structs cannot conform to AnyObject protocols

// struct MyStruct: SomeClassProtocol { /* Error */ }