avatarSwiftos

Summary

The article discusses the importance of Protocol-Oriented Programming (POP) in Swift 5 to eliminate "God Classes" and improve code organization and maintainability.

Abstract

The article "Swift 5: Why Is Protocol-Oriented Programming Essential" addresses the problem of "God Classes" in iOS development, where a single class handles multiple responsibilities, leading to code that is difficult to maintain and scale. The author illustrates this with an example of a UserViewModel that manages user data, verification, and updates. To combat this, the article introduces Protocol-Oriented Programming as a solution, explaining how it allows for the separation of concerns by defining protocols for specific categories of functionality. The author then demonstrates how to refactor the UserViewModel by creating protocols such as UserIdentifiable, UserFetchable, UserVerifiable, and UserUpdatable, each with a clear and focused purpose. By adopting these protocols, the UserViewModel is broken down into smaller, more manageable pieces, resulting in cleaner and more modular code. The article concludes by emphasizing the benefits of this approach for future updates and encourages readers to follow for a continuation of the topic.

Opinions

  • The author believes that "God Classes" are detrimental to codebases, leading to complexity and difficulty in maintenance.
  • Protocol-Oriented Programming is presented as a superior alternative to traditional class-based design patterns for organizing code in Swift.
  • The article suggests that POP enhances code readability, scalability, and maintainability by separating concerns into distinct protocols.
  • The author recommends naming protocols with adjectives ending in ble or ing to follow conventional naming practices in Swift.
  • By refactoring code using protocols, the author implies that developers can more easily adapt to changes and improvements in their applications.
  • The author's enthusiasm for POP is evident, as they invite readers to engage with the content and look forward to future articles on the subject.

Swift 5: Why Is Protocol-Oriented Programming Essential

Photo by Emma Matthews Digital Content Production on Unsplash

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.

  1. The class has a User object;
  2. It has an initializer of init(id: String) ;
  3. It has a func fetchUser(with id: String) -> User? to fetch the user from the database;
  4. It has two functions(verifyPhone and verifyEmail) to verify user info;
  5. It has two functions(update(name: String) and update(email: String)) to update user info.

As you can see, this class has three types of logic in one big class:

  1. Fetching info;
  2. Verifying info;
  3. Updating info.
  4. 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.

Please clap if you enjoyed this article. See you in subsequent articles :)

YOU MAY ALSO BE INTERESTED IN:

Swift Programming
iOS App Development
iOS Development
iOS Apps
Swift
Recommended from ReadMedium
avatarManuel Meyer
Agile Architecture in Swift

44 min read