Swift 5: Why Is Protocol-Oriented Programming Essential
Hi iOS Developers,
Do you know what a “God Class” is? It’s a class that has tons of different categories of functions and variables in one huge file. Your apps likely have some “God Classes” in them and you didn’t even know it!. You still have no idea about what a “God Class” is? Are you aware that it could destroy your codebase? Continue reading and I’ll teach you how to kill these evil things.
What’s a “God Class”?
It usually exists in the Controllers, if you are working with the MVC design pattern, and in the ViewModels, if you are working with the MVVM design pattern. Here’s see an example of a classic “God Class” in the MVVM design pattern.
Let’s say you are building a dating app. You probably will need a UserViewModel to store data and perform the business logic.
struct User {
let id: UUID
var name: String?
var phone: String?
var email: String?
}class UserViewModel {
private(set) var user: User?
init(id: String) {
self.user = fetchUser(with: id)
} func fetchUser(with id: String) -> User? {
//fetch user data from the CoreData and optionally return a User object
} func verifyPhone() {
if let phone = user?.phone {
// Perform phone number verification
}
} func verifyEmail() {
if let email = user?.email {
// Perform email verification
}
} func update(name: String) {
user?.name = name
} func update(email: String) {
user?.email = email
verifyEmail()
}
}It should be quite straightforward.
- The class has a
Userobject; - It has an initializer of
init(id: String); - It has a
func fetchUser(with id: String) -> User?to fetch the user from the database; - It has two functions(
verifyPhoneandverifyEmail) to verify user info; - It has two functions(
update(name: String)andupdate(email: String)) to update user info.
As you can see, this class has three types of logic in one big class:
- Fetching info;
- Verifying info;
- Updating info.
- It may have many more.
This is a typical “God Class”, it does everything. As the project grows, the “God Class” becomes super huge and hard to read and maintain.
Let’s see how Protocol-Oriented Programming chops this up.
What’s Protocol-Oriented Programming
According to the Swift documentation:
A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol.
Naming Convention
It’s recommended that you name your protocols with adjectives, usually with suffixes of ble or ing.
Create the protocols based on the category of the functions.
1. UserIdentifiable
// Identify User
protocol UserIdentifiable {
var user: User? init(id: String)
}This protocol has an optional user object and an init(id: String) initializer.
2. UserFetchable
// Fetch data from the database
protocol UserFetchable {
func fetchUser(with id: String) -> User?
}This protocol has a function fetchUser(with id: String) that returns an optional User object.
3. UserVerifiable
// Verify user info
protocol UserVerifiable {
func verifyPhone()
func verifyEmail()
}This protocol contains two verification functions: verifyPhone and verifyEmail.
4. UserUpdatable
// Update user info
protocol UserUpdatable {
mutating func update(name: String)
mutating func update(email: String)
}This protocol contains two update functions: update(name: String) and update(email: String). Because we will be updating values to another protocol, we need the mutating keyword for the functions.
Chop Up the “God Class” with Protocols
Notes: A protocol can inherit from another protocol in order to access the properties and functions.
Let’s implement the protocols
We can simply write an extension below the protocol, implementing the fetchUser function.
protocol UserFetchable {
func fetchUser(with id: String) -> User?
}extension UserFetchable {
func fetchUser(with id: String) -> User? {
//fetch user data from the CoreData and optionally return a User object
}
}For the UserVerifiable protocol, because we need to use the User object to retrieve the user info, we need to inherit from the UserIdentifiable protocol.
protocol UserVerifiable: UserIdentifiable {
func verifyPhone()
func verifyEmail()
}extension UserVerifiable {
func verifyPhone() {
if let phone = user?.phone {
// Perform phone number verification
}
} func verifyEmail() {
if let email = user?.email {
// Perform email verification
}
}
}The same thing applies to the UserUpdatable protocol. Because we will be calling the verifyEmail function from the UserVerifiable protocol, we need to inherit it from UserVerifiable. Because UserVerifiable already inherited from UserIdentifiable, UserUpdatable can access the user data.
protocol UserUpdatable: UserVerifiable {
mutating func update(name: String)
mutating func update(email: String)}
extension UserUpdatable {
mutating func update(name: String) {
user?.name = name
} mutating func update(email: String) {
user?.email = email
verifyEmail()
}
}We’re almost done. Finally, let’s inherit the UserViewModel from UserFetchable and UserUpdatable. Because we never implement the UserIdentifiable , in addition, it’s OK to put the initializer in the class. So we’ll leave it like this.
struct User {
let id: UUID
var name: String?
var phone: String?
var email: String?
}class UserViewModel: UserFetchable, UserUpdatable {
var user: User?
required init(id: String) {
self.user = fetchUser(with: id)
}
}Conclusion
With all these refactorings, the UserViewModel has become much cleaner. All the functions and properties are grouped according to their own categories. If we decide to add, delete or update the functions and properties, we can just go straight to the protocols.
There is much more we can do with protocols. Follow me for Part 2.






