Haseeb Afsar

Table of Contents

Protocol Oriented Programming aka POP

Object oriented programming was the classic strategy to write modular code for decades. Imagine you’re developing a photo sharing app. You can take a picture, add special effects, or share it with your friends. A common approach to creating this type of application is by using object oriented design, encapsulating all of the logic inside of an object that gets inherited to all of those that share similarity.This design approach works, but does come with some drawbacks.

For example, if you add the ability to create digital content such as pictures, videos or sound bites. That requires to be shared across, or anything else that may want to share common logic.

There isn’t a good way to separate the functional components of app into something reusable. This scenario is where protocols really shine. Protocol are extremely powerful and can transform the way you write code.

Protocol-Oriented approach, we start designing our system by defining protocols. We rely on new concepts: protocol extensions, protocol inheritance, and protocol compositions. The paradigm also changes how we view semantics.

In POP (Protocol Oriented Programming) value types are preferred over classes. However, object-oriented concepts don’t work well with structs and enums: a struct cannot inherit from another struct, neither can an enum inherit from another enum. So inheritance – one of the fundamental object-oriented concepts – cannot be applied to value types. On the other hand, value types can inherit from protocols, even multiple protocols. Thus, with POP, value types have become first class citizens.

At WWDC 2015, Apple announced that Swift is the world’s first Protocol-Oriented Programming (POP) language.

As of this writing, the Swift standard library includes 54 public protocols. By “public” I mean protocols marked as public and don’t begin with an underscore, which is the compiler developer’s universal signal for “maybe don’t use this yourself?”

The numbers look like this:

  • 95 public protocols in the headers
  • 35 public protocols starting with _
  • That leaves 60 public protocols
  • But there are 55 in the docs since they include one underscore-prefixed one because of…reasons, I’m sure. This is where the “55” in the talk title comes from.

We’ll explore the ways you can create and use protocols, as well as use protocol-oriented programming patterns to make your code more extensible. However before we deep dive into POP. Let’s refresh ourselves with OOP and its limitations

Why we’ve relied on OOP for decades?

  1. Reusability: Well-written objects are stand-alone entities that can be plugged into other programs. If I implement an optimized tree, I can use it in any part of an existing project, or in a brand new one, without writing more code.
  2. Skip the details: Another programmer doesn’t need to know your object’s implementation. If I see a “Sort” function, I can use without worrying what the code looks like.
  3. Error-Proofing: We can hide access to functions or variables in a class, which prevents other developers, that are less familiar with our code, from create bugs. Others see only what they need to see.
  4. Make Big Projects Manageable: Big projects get complicated. OOP lets us break a large problem into sub problems — each of which are handled by different objects. If Bob, Freddie, and Alex are making a Instagram like app. Bob can build an object to represent Photo Gallery, Freddie can build a Photo Filter object that adds special filters, effects, emoji and Alex can make objects representing likes. In end, we compose these objects to build a functioning photo sharing app.
  5. Updating Code: With modular code we can write easily add to, update, or remove specific components as time goes on. e.g. In a building made of bricks, you can swap out a cracked brick with a new one instead of replacing entire sections of the building.

Though very handy, OOP comes with its fair share of issues:

“GOD” objects, Massive View Controller:

To maintain modularity programmers use the one class, one purpose principal. However bloated viewControllers and models are common by-products of popular software design patterns (MVC, MVVM). Passing around chunky objects slows your code, and makes modularity difficult as you build out. If an object holds too much of the functionality you need, new code you write will end up depending on it too.

“God classes is a common design problem that you will see in complex applications during code reviews”

Here is a visual representation of GOD class, which illustrates the base class or super class does too much or knows too much 🙂

Multiple Ownership: 

Lets suppose you and a fellow engineer (Bob), are working on separate ViewControllers that reference the same Employee object. If you update the age of employee in firstViewController while Nick updates in in secondViewController, both of you will end up with an unexpected result. Classes are passed by reference, so changing the values in one area of your project will cause changes to that object’s values in every other place. This makes it difficult to test the isolated effect of your functions on a given object. This is visually illustrated in the diagram below

Also with references, heaps memory management is an additional overhead that language implementation has to deal with. In programming languages such as Objective-C, Java or even swift with or without ARC, garbage collector you’d often see strong reference counts leading to memory leaks.

This is where value types comes in handy, which addresses this problem. In swift with structs, enums and protocols it works like a charm as they are mutable copies. Also since structs are first class citizen in swift, they went ahead and implemented something called COW (Copy On Write) buffer. It can sometimes serve as intimidating subject matter, but luckily copy on write is both a simple concept and, as such, is easy to conceptualize. The elevator pitch is that instances pointing to the same object shouldn’t need to employ full copies unless one of them, does in fact, mutate.

Subclassing:

If you model an abstraction using classes, you’ll need to rely on inheritance. The superclass defines the core functionality and exposes it to subclasses. A subclass can completely override that behavior, add specific behavior, or get all the work done by the superclass. This works nicely until you realize that you need more functionality from a different class. Most programming languages, does not support multiple inheritance. Following the class-first approach, you’d have to keep adding new functionality to your superclass or otherwise create new intermediary classes, thereby complicating the issue. This also ends up with something called “Inheritance Hell”

The above diagram illustrates a complex structure of classes in UI building which is leading to a bad design which is as follows:

  1. Current and future behavior inherited
  2. Breaks encapsulation
  3. Long inheritance chain

Protocols, on the other hand, serve as blueprints rather than parents. A protocol models abstraction by describing what the implementation types shall implement. Let’s take for example the following protocol:

protocol Vehicle {
    var name: String {get set}
    static func drive()
}

What it tells us is that adopters of this protocol will be able to create a vehicle, assign it a name and implement driving functionality by implementing the type method drive(). One type can model multiple abstractions, since any type – including value types – can implement multiple protocols. This is a huge benefit over class inheritance. You can separate the concerns by creating as many protocols and protocol extensions as needed. Say good-bye to monolithic superclasses! The only caveat is that protocols define a template abstractly — with no implementation. Here’s where protocol extensions (available in Swift) come to the rescue.

Building Blocks Of POP

Protocol Extensions

Protocols serve as blueprints: they tell us what adopters shall implement, but you can’t provide implementation within a protocol. What if we need to define default behavior for conforming types? We need to implement it in a base class, right? Wrong! Having to rely on a base class for default implementation would eclipse the benefits of protocols. Besides, that would not work for value types. Luckily, there is another way: protocol extensions are the way to go! In Swift, you can extend a protocol and provide default implementation for methods, computed properties, subscripts and convenience initializers

Protocol Inheritance

Unlike OOP and class inheritance. A protocol is not limited to single inheritance. Protocols can inherit other protocols and add further requirement to the initial requirement.

Protocol Composition

Most programming languages does not allow multiple inheritance for classes. However, Swift types can adopt multiple protocols. Sometimes you may find this feature useful.

Here’s an example: let’s assume that we need a type which represents an Vehicle.

We also need to compare instances of given type. And we want to provide a custom description, too.

We have three protocols which define the mentioned requirements:

  • Vehicle
  • Equatable
  • CustomStringConvertible

If these were base classes, we’d have to merge the functionality into one superclass; however, with POP and protocol composition, the solution becomes:

struct Car: Vehicle, Equatable, CustomStringConvertible {
    var name: String// Equatablepublic static func ==(lhs: MyEntity, rhs: MyEntity) -> Bool {return lhs.name == rhs.name
    }
    // CustomStringConvertiblepublic var description: String {return "Car: \(name)"
    }
}
let car1 = Car(name: "Nissan")
print(car1)
let car2 = Car(name: "Nissan")
assert(car1 == car2, "Cars shall be equal")

This design not only is more flexible than squeezing all the required functionality into a monolithic base class but also works for value types.

Big Gains Of POP, Value Type vs OOP, Reference Types

There are many advantages of using POP, value type paradigm. However notably one key aspect I’d like to bring your attention is towards run-time & compile time optimizations. Which is “Method Dispatch” vital when writing high performance code.

Method Dispatch is how a program selects which instructions to execute when invoking a method. It’s something that happens every time a method is called, and not something that you tend to think a lot about. Knowing how method dispatch works is vital when writing performant code, and can illuminate some of the confusing behavior found in Swift.

Value types always use static/direct dispatch. Extensions of protocols, Structs use direct dispatch – Value Types, Extensions Of Protocols are the building block of POP

Types of Dispatch

Compiled programming languages have three primary methods of dispatch at their disposal: direct dispatch, table dispatch, and message dispatch, which I explain below. Most languages support one or two of these. Java uses table dispatch by default, but you can opt into direct dispatch by using the final keyword. C++ uses direct dispatch by default, but you can opt into table dispatch by adding the virtual keyword. Objective-C always uses message dispatch, but allows developers to fall back to C in order to get the performance gains of direct dispatch. Swift has taken on the noble goal of supporting all three types of dispatch. This works remarkably well, but is a source of confusion to many developers, and is behind a number of gotchas that most Swift developers have encountered.

The goal of dispatch is for the program to tell the CPU where in memory it can find the executable code for a particular method call. Let’s look at each of the three types of method dispatch one by one. Each one has tradeoffs between execution performance and dynamic behavior.

Direct Dispatch

Direct dispatch is the fastest style of method dispatch. Not only does it result in the fewest number of assembly instructions, but the compiler can perform all sorts of smart tricks, like inlining code, and many more things which are outside of the scope of this article. This is often referred to as static dispatch.

However, direct dispatch is also the most restrictive from a programming point of view, and it is not dynamic enough to support subclassing.

Table Dispatch

Table dispatch is the most common implementation of dynamic behavior in compiled languages. Table dispatch uses an array of function pointers for each method in the class declaration. Most languages refer to this as a “virtual table”. Every subclass has its own copy of the table with a different function pointer for every method that the class has overridden. As subclasses add new methods to the class, those methods are appended to the end of this array. This table is then consulted at runtime to determine the method to run.

Let’s look at the example below

class ParentClass {
    func method1() {}
    func method2() {}
}

class ChildClass: ParentClass {
    override func method2() {}
    func method3() {}
}



let obj = ChildClass()
obj.method2()

In this scenario, the compiler will create two dispatch tables, one for ParentClass, and one for ChildClass:

When a method is invoked, the process will:

  1. Read the dispatch table for the object 0xB00
  2. Read the function pointer at the index for the method. In this case, the method index for function2 is 1, so the address 0xB00 + 1 is read.
  3. Jump to the address 0x222

Table lookup is pretty simple, implementation-wise, and the performance characteristics are predictable. However, this method of dispatch is still slow compared to direct dispatch.

Byte-code point of view, there are two additional reads and a jump, which contribute some overhead. However, another reason this is considered slow is that the compiler can’t perform any optimizations based on what is occurring inside the method.

Another downfall of this array-based implementation is that extensions cannot extend the dispatch table. Since subclasses add new methods to the end of the dispatch table, there’s no index that an extension can safely add a function pointer to.

Message Dispatch

Message dispatch is the most dynamic method of invocation available. A key component to this functionality is that it allows developers to modify the dispatch behavior at runtime. Not only can method invocations be changed via swizzling, but objects can become different objects via swizzling, allowing dispatch to be customized on an object-by-object basis.

Let’s look at this code example below to understand the inner-working of message dispatch

class ParentClass {
    dynamic func method1() {}
    dynamic func method2() {}
}

class ChildClass: ParentClass {
    override func method2() {}
    dynamic func method3() {}
}

Compiler will model this hierarchy as a tree structure as illustrated below:

When a message is dispatched, the runtime will crawl the class hierarchy to determine which method to invoke. If this sounds slow, it is!

However, this lookup is guarded by a fast cache layer that makes lookups almost as fast as table dispatch once the cache is warmed up. But this is just scratching the surface of message dispatch. 

The Swift blog has a great article describing more details about optimization here

Final Thoughts

  1. Swift supports multiple paradigms – OOP, POP & Functional Programming
  2. POP or Value Oriented Programming (VOP) is an alternate paradigm to OOP. Also it provides cleaner, lean design
  3. POP is not a silver bullet to all the problems. Reference types, OOP will continue to be used as mix and match where reference types are required
  4. It is not like POP (interfaces) are better than OOP (inheritance), they both have their purposes and strengths. An interface is looser and more flexible. Inheritance ties you to the trunk of a particular class tree but it offers you free implementations of the basics, structure and guidance. If inheritance is done well, it is hard for the subclassing programmer to screw things up.
  5. Important reason for the rise of POP is probably the relatively new, distributed, networked world with many developers, that do not know each other, potentially working on the same project. It fits the model of a developing community and it can cross process and/or programming language boundaries (SOA).

Leave a Reply

Your email address will not be published. Required fields are marked *