Writing highly-modularized code is critical in building a robust iOS app, that is scalable and extensible. To make a well-architected iOS app in Swift, there are various design patterns that can be used, to build a high-quality codebase. We focus a lot on building robust architecture in our Xcode app templates, so in this article we are sharing the way we leverage protocol-oriented programming in Swift, to create scalable iOS apps. We are not creating just one app, but instead, we are making a ton of app templates, so a good design is critical to our mission.

protocol-oriented programming
1. Introduction to Protocol-Oriented Programming in Swift

If you are an iOS developer, perhaps you are already familiar with the concept of object-oriented programming (OOP). OOP helps us solve many problems, like organizing and expanding code elegantly. Through its main functions, including abstractions, polymorphism, and especially inheritance, developers are able to solve these problems efficiently. OOP still has its limits though. The most remarkable limitation of OOP is the single inheritance in Swift – you can only inherit from a single base class, as opposed to other programming languages such as C++, where you can have multiple inheritance.

So what is new about Protocol-Oriented Programming (a.k.a. POP)? In OOP, we usually think about classes and objects (which the instances of classes). But with POP, instead of thinking from the perspective of classes, you will focus on protocols. Now the question is, what is Protocol exactly? In the next part, we will find out about both Protocol and POP.

2. Swift Protocols & Protocol-Oriented Programming

A simple way of understanding the concept of a protocol is to know that it defines attributes, methods, and required tasks which are separated from functions. Protocols in Swift are similar to Java’s interfaces – you specify what is the interface of an object, without actually defining the behavior as well. Whereas with OOP, you can only use them with classes. Protocols can be adopted by classes, structs, and enums.

Because POP is inspired by OOP, it has many advantages that outperform OOP. In Swift, people prefer to use structs combined with protocols (Commonly used in POP) instead of using classes (OOP). All of the things classes can do, structs is also capable. Besides that, POP allows for structuring code based on value type instead of reference type. We do not need to worry much about memory leak or deadlock issues.

But the biggest advantage of using protocol-oriented programming in your app architecture is to avoid inheritance. Inheritance is inherently bad (pun intended), because inheritance is the strongest way of coupling two classes together. If class A inherits from class B, then these two classes are strongly coupled together, and chances are you will never be able to use one without the other.

Let’s assume we go with composition rather than inheritance, so class A will have a property of type B. While the composition pattern is definitely much better since now we decoupled the two classes a little (e.g. you can now make changes to A, without worrying about B), these two classes are still fairly coupled, since A literally owns an instance of B.

In Protocol-Oriented Programming, we go one step further, and decouple A and B entirely, by replacing inheritance and composition with protocols. So, instead of using inheritance or composition between A and B, we are going to introduce a new interface C, which is a protocol and will act as the bridge between A and B. Both A and B have knowledge of the C interface, but A and B don’t know of each other. In this way, A is dependent on C, B is dependent on C, but A and B are completely independent. Note that C is just an interface, and it has no business logic (as opposed to A and B, which are real classes).

Writing unit tests for classes A and B becomes a lot easier as well. If you modify the public interface of B, you don’t need to make any changes in A and vice-versa. This was not the case in the legacy OOP approach.

Benefits of Protocol-Oriented Programming:

  • All classes are decoupled from each other
  • Separating the concerns of declaration from implementation
  • Reusability
  • Testability

Now let’s consider an example, to make things a little clearer.

3. Code Example

a. With classes (OOP)

Let’s pretend we have a class called Footballer

class Footballer {
  var fullName: String?
  var numberOfGoals: Int?
  init(fullName: String?, numberOfGoals: Int?) {
    self.fullName = fullName
    self.numberOfGoals = numberOfGoals
  }
  func canDribble() {
    print("Yes, I can")
  }
}

Next, we have two subclasses of Footballer. They are Striker and Defender.

class Striker: Footballer {
  override init(fullName: String?, numberOfGoals: Int?) {
    super.init(fullName: fullName, numberOfGoals: numberOfGoals)
  }
}

class Defender: Footballer {
  override init(fullName: String?, numberOfGoals: Int?) {
    super.init(fullName: fullName, numberOfGoals: numberOfGoals)
  }
  override func canDribble() {
    super.canDribble()
    print(“But in general, I am not good“)
  }
}

It’s quite straightforward, but let me explain a little bit. A striker can dribble so well, therefore he doesn’t need to say anything more. But with a defender, he rarely dribbles and is thus not as adept at dribbling as the striker. So, the defender will have a different answer than the striker, when asked whether he could dribble. That’s why we have print(“But in general, I am not good“) after calling super.canDribble().

Now, let’s assume that we want to create a new class: Goalkeeper. Wait! A normal goalkeeper (except those who are so special) will never score a single goal and of course, he also can’t dribble.

So what we need to do here? Create a new class called Goalkeeper? No, it violates the OOP’s principles. Use `static func`? No, this is also a bad idea. It’s obvious where we are going with this. Try to imagine what happen if we have 20 subclasses, which could happen if your app gets successful enough.

Let’s see how protocol-oriented programming makes this architecture more robust. If we use POP, these issues will be addressed easily.

b. With protocols (POP)

We start by creating some protocols, which are interfaces of the jobs a general player can or cannot do. Think of them as job descriptions.

protocol Person {
  var fullName: String? { get set }
}

protocol Goals {
  var number: Int? { get set }
}

protocol Skill {
  func canDribble()
}

Now you can see, with the principle of protocol, any structs that use the first two protocols would declare the fullName, number variables. But with the Skills protocol, any structs which use this one would either use the default implementation from its Extension or it can re-write it for its own purpose. Let’s give a try.

extension Skill {
  func canDribble() {
    print(“I can dribble”)
  }
}

So from now on, if any structs use the Skill protocol, they have a default canDribble() implementation or they could simply override the behavior:

struct ConfidentStriker: Person, Skill {
  var fullName: String?
  func canDribble() {
    print(“Hell Yes, I am fantastic.”)
  }
}

struct DefenderV2: Person, Skill {
  var fullName: String?
}

So at this point, we could create the Goalkeeper struct without any dribbling skills and our design will be 100% correct. He doesn’t even need to use the Goal protocol (very few goalkeepers can score).

struct GoalKeeper: Principal {
  var fullName: String?
}

Now we can create some concrete instances of these classes:

let ronaldo = StrikerV2(fullName: “Ronaldinho”, number: 100)
ronaldo.canDribble() // Yes, I am really good at this
let pepe = DefenderV2(fullName: “Pepe”, number: 10)
pepe.canDribble() // I can dribble
let deGea = GoalKeeper(fullName: “De Gea”)

As you see, Goalkeeper did not use Skills and Goals and we don’t change much. In case you’re a new member in an existing project and you don’t need to understand everything, reading the protocol will give you an overview of it. You don’t need to dig into the implementation details, you can just read the interface and have a good idea of what’s the responsibility for a given object.

4. Conclusion

These are just some basic concepts of Protocol-Oriented Programming in Swift. In fact, POP is really useful if we could design and use it properly (for example protocol-delegate). It’s an extremely powerful design pattern, which goes along really well with MVC, MVVM and VIPER architectures. We hope you will like this article. Here is the complete Swift source code.

Categories: iOS Programming

Leave a Reply

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