Protocols in Swift

·

5 min read

What is Protocol?

In simple terms a protocol is an interface which defines properties and methods. So any type that conforms to the protocol should implement the methods and properties requirements that are defined.

Protocols - The Swift Programming Language

A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality.

protocol Car {
    var topSpeed: Double { get }
    func noOfDoors() -> Int
}

What we can achieve using Protocols?

In object oriented programming a class can inherit only one class i.e a class cannot extend multiple classes. So multiple inheritance is not possible, however a class can extend multiple protocols

For instance

class Car {
    var topSpeed: Double

    init(topSpeed: Double) {
        self.topSpeed = topSpeed
    }

    func noOfDoors() -> Int {
        return 4
    }
}

class AirCraft {
    var airspeedVelocity: Double

    init(airspeedVelocity: Double) {
        self.airspeedVelocity = airspeedVelocity
    }

    func noOfWings() -> Int {
        return 3
    }
}

There are two classes Car and Aircraft So we can subclass different Cars (BMW, Honda, Toyota etc.) and Aircarfts (Boeing 707, Airbus A330 etc.) What if there is a Car which is also an Aircraft?

class FlyingCar: Car, AirCraft {

}

Oops that’s the issue and its not supported in object oriented programming. Let's assume if we want to provide the implementation as following:

class FlyingCar: Car {
    var airCraft: AirCraft? = nil

    override func noOfDoors() -> Int {
        return 2
    }
}

let flyingCar = FlyingCar(topSpeed: 100)
flyingCar.airCraft = AirCraft(airspeedVelocity: 100)
print(flyingCar.noOfDoors())
print(flyingCar.airCraft?.noOfWings())

The code is not readable and creates the strong dependency. That's why is it not allowed in OOP. So using protocols we can create a FlyingCar that conforms to both the protocols Car and AirCraft

protocol Car {
    var topSpeed: Double { get }
    func noOfDoors() -> Int
}

protocol AirCraft {
    var airspeedVelocity: Double { get }
    func noOfWings() -> Int
}

class FlyingCar: Car, AirCraft {
    var topSpeed: Double
    var airspeedVelocity: Double

    init(topSpeed: Double, airspeedVelocity: Double) {
        self.topSpeed = topSpeed
        self.airspeedVelocity = airspeedVelocity
    }

    func noOfDoors() -> Int {
        return 2
    }

    func noOfWings() -> Int {
        return 2
    }
}

let flyingCar = FlyingCar(topSpeed: 100, airspeedVelocity: 100)
print(flyingCar.noOfDoors())
print(flyingCar.noOfWings())

Voila! Looks clean. So Protocols solve the problem of multiple inheritance.

Protocol Extensions

Extensions - The Swift Programming Language

They allow us 2 things:

1) Makes properties and methods of protocol 'optional' by providing the default implementation. i.e Types that conforms to protocol can use default implementation or provides their own implementation.

2) Adds specific methods to multiple types that already conform to protocol without modifying the types individually!

Default implementation example

enum AppError: Error {
    case any

    var description: String {
        return "any error"
    }
}

protocol ErrorHandler {
    func onError(error: AppError)
}

class  Handler: ErrorHandler {
    func onError(error: AppError)
      print("Handler onError call \(error.description)")
    }
}

In above example the Handler class conforms to ErrorHandler protocol and provides its own implementation. So we can provide the default implementation of protocol as following which makes its implementation as optional.

extension ErrorHandler {
    func onError(error: AppError) {
        print("ErrorHandler onError call \(error.description)")
    }
}

class  Handler: ErrorHandler
  // No need to provide implementation if default implementation is needed
}

Protocol and Polymorphism

Polymorphism means having many forms. Let's see how Protocol changes the behaviour when the default implementation is provided and without default implementation.

protocol ErrorHandler {
    func onError(error: AppError)
}

extension ErrorHandler {
    func onError(error: AppError) {
        print("ErrorHandler onError call \(error.description)")
    }
}

class  Handler: ErrorHandler {
    func onError(error: Error) {
        print("Handler onError call \(error.description)")
    }
}

let handler: Handler = Handler()
handler.onError(error: AppError.any)

The output to the console is "Handler onError call any error". Now remove method declaration from ErrorHandler protocol

protocol ErrorHandler {

}

The output to the console is "Handler onError call any error". So does it mean there is no difference? No there is a difference.

let handler: ErrorHandler = Handler()

Now the output to the console is "ErrorHandler onError call any error"

If we add method again

protocol ErrorHandler {
    func onError(error: AppError)
}

The output to the console is "Handler onError call any error"

Method declaration in protocol

So if method is declared in protocol and default implementation is provided the method is overridden in Handler method implementation. It decides at runtime irrespective the type of variable

Method is not declared in protocol

As method is not declared in protocol the type is not able to override it. So the implementation depends on the type of a variable. If the variable is of type Handler, then its method implementation is invoked. If the variable is of type ErrorHandler, then its method implementation is invoked.

How Swift provides solution to resolve ambiguity using Protocols

Suppose there are 3 protocols with the same method signatures

protocol A {
  func show()
}

protocol B {
  func show()
}

protocol C {
  func show()
}

These protocols have the default method implementation

extension A {
  func show() {
    print("show A")
  }
}

extension B {
  func show() {
    print("show B")
  }
}

extension C {
  func show() {
    print("show C")
  }
}

Now there is a type which conforms to these protocols

struct S: A, B, C {

}

In this case type is not sure which implementation from the protocols A, B, C it needs to implement, that will result in compilation error. To fix this error we need to provide an implementation to type S

struct S: A, B, C {
func show() {
    print("show S")
  }
}

Swift handles this with ease compare to other programming languages by allowing programmer to take the control where the compiler fails.

Conclusion

Protocol is powerful concept of Swift programming language. Unlike other programming languages using protocols and protocols extensions swift provides a type safety at compile time that ensures the reusability and maintainability.

I hope you like my first article. Welcome any feedback. Thank you.