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.
Generally, a protocol:
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.
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.
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:
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.”
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.
Protocols also eliminate the need for classes, enums, and structs to be dependent on each other because they do not use inheritance.
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ 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>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowSOLID principles help us keep code flexible. In this article, we’ll examine all of those principles and their implementation using JavaScript.
JavaScript’s Date API has many limitations. Explore alternative libraries like Moment.js, date-fns, and the new Temporal API.
Explore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.