When Apple announced the release of Swift, the first protocol-oriented language, at their Worldwide Developers Conference (WWDC) in 2015, it indicated a shift away from their existing object-oriented programming, Objective-C.
But because Objective-C utilizes implicit sharing inheritance, creating new objects becomes a slow process, and the new objects often have functionalities that are unneeded. In this case, if a class hierarchy is complex, maintaining it can cause problems like inefficiency and race conditions.
With Swift’s protocol paradigm, developers can now build objects without inheritance, objects can be used by existing code, and a single class can work with multiple protocols without the need for inheritance.
In this post, we’ll discuss Swift’s protocol paradigm and its advantages. Knowledge of object-oriented programming is helpful for understanding this post’s content.
What are protocols and how do they work in Swift?
Generally, a protocol:
- Is a blueprint that a class or struct follows
- Is a communication contract for unrelated objects to rely on
- Defines methods and values
To understand how protocols work in Swift, let’s suppose we are building application software and must model the requirements to satisfy the application. We can either begin with a superclass and mold the relationship through inheritance or start with a protocol and mold the relationship through the implementation.
If we want to build a salary remittance system for our app and we have an Employee
class, using a protocol looks like the following:
protocol EmployeeProtocol { var emplname: String { get } var description: String { get } var salary: Int { get set } func paySalary(salary: Int) -> String }
Usually, if we use get
, we can make it a const
, var
, let
, or computed property. However, using the property declaration get set
for the salary
property limits var salary: Int { get set }
to var
.
If we want to write a class that follows this protocol, such as the Employee
class, we have the following:
class Employee: EmployeeProtocol { var emplname: String = "Victor Jonah" var description: String = "Software Engineer" var salary: Int = 5000 func paySalary(salary: Int) -> String { return "Salary disbursed to {emplname}" } }
In summary, protocols allow us to group our methods, properties, and functions. However, these protocols can only conform to classes, enums, and structs.
More than one protocol can conform to one object, but they must be separated by commas:
struct Player: MainPlayer, EnemyPlayer { // code definition }
Also, if a class has a superclass, we can define any protocols after the superclass name:
class TheClass: ItsSuperclass, FirstProtocol, SecondProtocol { // class definition goes here }
We can use enum
with our protocols for computed properties, but they do not work for stored properties:
enum Employer: EmployerProtocol { var name: String { return "Alex" } var description: String { return "CEO" } var salary: Int { get { return } } }
Swift also throws an error at the compile time if the protocol does not conform to the class, struct, or enum.
A Swift mobile protocol example
Let’s see a more common use case for the protocol with a mobile example:
protocol Mobile { var name: String { get } var iEMICode: Int { get } var sIMCard: String { get } var processor: String { get } var internalMemory: Int { get} var isSingleSIM: Bool { get } mutating func GetIEMICode() -> String func SendMessage() -> String func Dial() -> String func Receive() -> String init(name: String) } struct Apple: Mobile { var name: String = "Apple" init(name: String) { self.name = name } var iEMICode: Int = 3244332 var sIMCard: String = "Vodaphone" var processor: String = "Snapdragon" var internalMemory: Int = 213424 var isSingleSIM: Bool = true mutating func GetIEMICode() -> String { return "IEMEICode" } func SendMessage() -> String { return "Message sent" } func Dial() -> String { return "Dialed" } func Receive() -> String { return "Receiving call" } } struct Samsung: Mobile { var name: String = "Samsung" init(name: String) { self.name = name } var iEMICode: Int = 3243433 var sIMCard: String = "TMobile" var processor: String = "Snapdragon" var internalMemory: Int = 324432 var isSingleSIM: Bool = false func GetIEMICode() -> String { return "IEMEICode" } func SendMessage() -> String { return "Message sent" } func Dial() -> String { return "Dialed" } func Receive() -> String { return "Receiving call" } }
Notice that the mutating
keyword on line 9 works when we have an object that must change one of its properties. We must specify that GetIEMICode()
is a mutating method in our protocol. In our struct, we must also specify the keyword mutating
but not in the class.
Advantages of protocols in Swift
From the above examples, we can see why protocols are useful and why Swift uses the protocol-oriented paradigm. The advantages of using protocols manifest in the following ways:
Code clarity
Naming protocols provides a better understanding of their instances. In our first example, we created an EmployeeProtocol
that conforms to the Employee
class, showing how protocols offer meaning to classes, enums, or structs.
As Dave Abrahams said at the 2015 WWDC, “Don’t start with a class, start with a protocol.”
Reusability
With protocol extensions, we can have a default implementation for our method in the class, enum, or struct they conform to. We can see this in the code below:
protocol Person { var name: String { get } var age: Int { get } var gender: String { get } func speak() } extension Person { func speak() { print("Hello, this works!") } } class Male: Person { var name: String = "" var age: Int = 23 var gender: String = "Male" } struct Female: Person { var name: String var age: Int var gender: String }
By creating a default functionality using the extension
keyword on line 9, we do not need to repeat it in our class or struct.
Separation of classes
Protocols also eliminate the need for classes, enums, and structs to be dependent on each other because they do not use inheritance.
Conclusion
In summary, protocols in Swift offer communication between unrelated objects where we define the methods and variables observed in classes, enums, and structs. Because Swift embraces the protocol-oriented paradigm, we can model our system before defining classes, structs, or enums, making the process more efficient.
Get setup with LogRocket's modern error tracking in minutes:
- Visit https://logrocket.com/signup/ to get an app ID.
- Install LogRocket via NPM or script tag.
LogRocket.init()
must be called client-side, not server-side. - (Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- ngrx middleware
- Vuex plugin
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>