Protocol Oriented Programming in Swift

We've always heard about Object Oriented Programming (OOP) and now I present to you Protocol Oriented Programming (POP) and why it is such a powerful weapon when it comes to creating and maintaining a robust code base in Swift. But, be careful, with great power comes great responsilities, so, let's BEGIN.

Protocols

A protocol defines a blueprint of methods, properties… The protocol can then be adopted by a class, structure, or enumeration - Apple

protocol Speakable {
    func speak()
}

A conformer is supposed to implement all the properties and functions declared in the protocol. Swift compiler will throw an error at compile-time if there is a missing requirements, thus, this will help developers to ensure they are not forgetting any piece of code that would result in future hair loss. Furthermore, Protocols bring more abstraction than classes do in Swift.

Protocol Oriented Programming

POP is a new approach for programming which you decorate your classes, structs or enums using protocols.

Composition over Inheritance

Let's take a look at this example, we have to implement Fox and Cat, both have an ability to speak. We would usually have Animal be the base class and have Fox and Cat subclass Animal.

class Animal {

    func speak() {
        assertionFailure("Subclass should implement this.")
    }
}

class Fox: Animal {

    override func speak() {
        print("A-hee-ahee ha-hee")
    }
}

class Cat: Animal {
    override func speak() {
        print("Human feed me")
    }
}

Now, let's take a look at how it can be done in POP.

protocol Speakable {
    func speak()
}

class Fox: Speakable {
    
    func speak() {
        print("A-hee-ahee ha-hee")
    }
}

class Cat: Speakable {

    func speak() {
        print("Human feed me")
    }
}

As you see, subclassing does the job, then WHY not stick with it??? Yes, they may seem similar but protocol offers a few key advantages in Swift:

  • There is no multiple inheritance in Swift! On the other hand, a Type can conform to multiple protocol, thus, they can be decorated with behaviors from more than one protocols. Unlike multiple inheritance which some programming languages support, protocol don't introduce any additional states of a property.
  • Anything can conform to a protocol. Base classes and inheritances are restricted to class type. In other words, protocol provide the ability to define default behavior for value types and not just classes.

Testability

POP makes mocking class become so much easier.

typealias DataTaskHandler = (Data?, URLResponse?, Error?) -> Void

protocol URLSessionType {
    func dataTask(with request: URLRequest, completionHandler: @escaping DataTaskHandler) -> URLSessionDataTask
}

// Make `URLSession` conform to `URLSessionType`
extension URLSession: URLSessionType {}

// MARK: Network Provider

class NetworkProvider {

    let session: URLSessionType
    
    init(session: URLSessionType = .shared) {
        self.session = session
    }
}

Now when we test NetworkProvider we can inject any implementation of the URLSessionType we choose. In our production code we don't have to worry about creating a conforming object manually as the default parameter will instantiate an URLSession for us!

// Create a mock class for unit tests
class MockURLSession: URLSessionType {
    
    func dataTask(with request: URLRequest, completionHandler: @escaping DataTaskHandler) -> URLSessionDataTask {
        // Voila!
    }
}

Conclusion

Many object-oriented programming languages are plagued with limitations surrounding the resolution of ambiguous extension definitions. Swift handles this quite elegantly through protocol extensions by allowing the programmer to take control where the compiler falls short.

But just because you are using protocol everywhere does not mean it is Protocol Oriented programming. It is easy to write a spaghetti code by abusing the power of a feature. Know and understand what it does and how to use it properly is the key for writing a testable and robust code.