Protocol oriented programming in Swift

Nulab
Nulab
Published in
5 min readJun 9, 2016

This WWDC talk is where it all started. The presentation shows a strong objection against Object-oriented programming (OOP).

I have a special interest in this topic because the app that I’m now working on is getting bigger and more complex. It is becoming more difficult to comprehend the code and has a tendency of getting bloated. But when it is time to put the ideas in this video into practice, I find it challenging.

Instead of discussing how Protocol Oriented Programming could fix the world, let's take a look at what we can do with it to make our code better. I will be using a portion of Backlog iOS client code as the example in this article.

Value type

If you use struct instead of class but need a class-inheritance-like hierarchy, you have to use protocol because value types in Swift do not support inheritance.

In Backlog, an issue can have several custom fields. If you are using a class, you probably will make CustomFieldthe base class. But for struct, we have to use protocol. Here are the requirements forCustomField.

protocol CustomField {
var id: Int { get }
var name: String { get }
var required: Bool { get }
var description: String? { get }
var applicableIssueType: [IssueType] { get }
}

Based on type property in JSON data, CustomField can beTextField, TextArea, NumericField, DateField, Checkbox, RadioField, etc.

For example, we can now make DateFieldconform toCustomField. Please notice that the term is ‘conform’ not ‘inherit’.

struct DateField: CustomField {
// required by CustomField
let id: Int
let name: String
let required: Bool
let description: String?
var applicableIssueType: [IssueType] { get }

// specific to DateField type
let initialDate: NSDate?
let min: NSDate?
let max: NSDate?
}

Code reuse

One of the advantages of inheritance is that a subclass can provide all the work done in a superclass for free.

In Swift, Protocol Extension allows us to provide a default implementation to any method or property of the protocol.

Let's say we need a method to check if the custom field is applicable to a provided issue type.

extension CustomField {
func applicableToIssueType(type: IssueType) -> Bool {
return self.applicableIssueType.indexOf(type) != nil
}
}

If we need this method to be customizable by conforming types, we can add this method as a requirement.

protocol CustomField {
var id: Int { get }
var name: String { get }
var required: Bool { get }
var description: String? { get }
var applicableIssueType: [IssueType] { get }
func applicableToIssueType(type: IssueType) -> Bool
}

Helper

In Backlog, there are many issue lists such as my issues, project issues, recently viewed issues, etc. Users can filter these lists by typing keywords inside the search box. To make the search feature possible, a helper function is needed to turn the original issues and keywords into filtered issues.

There are many ways to write a helper:

  • You could write it as a global function, but be aware that it could pollute your global namespace.
  • You could create a class and make the helper a class method, but this method may feel awkward to you.
  • You could make a helper class and instantiate it, but you will have to manage that helper instance in your controller.
  • You could write it as a method of your controller and duplicate it for each of the other controllers, but this method will get messy.
  • You could also put the helper inside a common base class, but the superclass often gets bloated by doing this.

Now, let's use protocol. In my opinion, protocol is a more natural way to write a helper function.

protocol IssuesFilterable {}extension IssuesFilterable {
func filterByKeyword(originalIssues: [Issue], keyword: String) {
return originalIssues.filter {
$0.issueKey.rangeOfString(keyword, options: .CaseInsensitiveSearch) != nil ||
$0.summary.rangeOfString(keyword, options: .CaseInsensitiveSearch) != nil
}
}
}
extension RecentlyViewedIssuesController: IssuesFilterable {}

You can make originalIssues a requirement so you don’t need it as a function parameter.

protocol IssuesFilterable {
var originalIssues: [Issue] { get }
}
extension IssuesFilterable {
func filterByKeyword(keyword: String) {
return self.originalIssues.filter {
$0.issueKey.rangeOfString(keyword, options: .CaseInsensitiveSearch) != nil ||
$0.summary.rangeOfString(keyword, options: .CaseInsensitiveSearch) != nil
}
}
}

You can save the filter result to require thefilteredIssues property and reload tableViewif the controller is aUITableViewController.

protocol IssuesFilterable: class {
var originalIssues: [Issue] { get }
var filteredIssues: [Issue] { get set }
}
extension IssuesFilterable where Self: UITableViewController {
func filterByKeyword(keyword: String) {
self.filteredIssues = self.originalIssues.filter {
$0.issueKey.rangeOfString(keyword, options: .CaseInsensitiveSearch) != nil ||
$0.summary.rangeOfString(keyword, options: .CaseInsensitiveSearch) != nil
}
self.tableView.reloadData()
}
}

View model

In the Backlog app, user models reside in a separate module (API client library) that is rarely changed. Here is the model for users.

public struct User {
public let id: Int
public let userId: String?
public let name: String
public let roleType: RoleType
public let lang: String?
public let mailAddress: String?
}

Usually, before rendering, you need some kind of formatting that’s neither inside your controller norUITableViewCell. We need a view model. Let’s use protocol for this.

protocol UserPresentable {
var avatar: UIImage { get }
var attributedName: NSAttributedString { get }
}

Next, we will make all user asUserPresentable.

extension User: UserPresentable {
var avatar: UIImage {
// load and return image based on id
// Usually it's not this simple, it should be asynchronous and return some kind of promise
}
var attributedName: NSAttributedString {
// For example, base on roleType, bold name for administrator and regular for others
}
}

Now we can ask the user cell to render it.

extension UserCell {
func render(modelview: UserPresentable) {
self.avatarView.image = modelview.avatar
self.nameLbl.attributedText = modeview.attributedName
}
}

Mixins and Traits

Protocol is the closest thing to Mixins and Traits in Swift. Mixins and Traits shine in the game development world where there is usually a deep class hierarchy. As a rule of thumb, always “prefer composition over inheritance.” Basically, try to build your class or struct as an aggregation of several capabilities.

Say we need to cache and serialize a user to file. Not only a user, but we may also need other models to be cached. Instead of making a superclass, we use protocol.

protocol JSONSerializable {
var json: [String: AnyObject] { get }
}
extension User: JSONSerializable {
var json: [String: AnyObject] {
// return json representation of user
}
}

Now the user model conforms to UserPresentableandJSONSerializable. You can add more capabilities to the user as you need.

Wrapping up

In the above examples, we discussed things in terms of what a type can do, not what a type is. Although it is not always straightforward to design this way, trying to imagine how your classes or structs are structured using protocols could be helpful to better understand your code.

One year after the WWDC talk, myself and others still do not have a clear idea of what Protocol Oriented Programming is, but we are expecting much more information about this topic in the upcoming WWDC 2016.

--

--

Nulab
Nulab
Editor for

The best things are built through collaboration. Our apps help teams get there. We're the creators of @Backlogtool, @Cacoocom, and @Typetalkin.