Useful of Protocol

We might all have gone through the pain of spending a lot of time to implement a function, but then realize that Swift has a built-in function that does the same thing. In this article, I’m going to talk about 3 useful protocols that can save you much time, and take your code to the next level.

CaseIterable

The CaseIterable protocol allows you to get all the values of the type. Let’s take a look at an example:

Without:

enum City {
    case new_york
    case bei_jing
    case vancouver
    ....
}
let cities: [City] = [.new_york, .bei_jing, .vancouver, ....]

In this example, when we want to pull out all the cities from the City enum, we would have to manually type them out. Imagine that if this enum has hundreds of thousands of cities, it would be a nightmare.

With CaseIterable:

enum City: CaseIterable {
    case new_york
    case bei_jing
    case vancouver
    ....
}
let cities = City.allCases

With the allCases property provided by the CaseIterable protocol, we are able to get an array of all the cases of City. This can save us a significant amount of time.

CustomStringConvertible

Not everyone likes the default description for each object, sometimes, we want to give an object a user-friendly description just for reading purposes.

Without:

struct Weather {
    let city: String
    let temperature: Double
}
let weather = Weather(city: "New York", temperature: 97.5)
print("The temperature in \(weather.city) is \(weather.temperature)°F.")
print("The temperature in \(weather.city) is \(weather.temperature)°F.")
print("The temperature in \(weather.city) is \(weather.temperature)°F.")

Every time, we want to print a readable message, we have to copy and paste the message over. If at some point we want to modify the message, we will have to go through everything single place which would waste a lot of time.

With CustomStringConvertible:

struct Weather: CustomStringConvertible {
    let city: String
    let temperature: Double
    var description: String {
        return "The temperature in \(city) is \(temperature)°F."
    }
}
let weather = Weather(city: "New York", temperature: 97.6)
print(weather) // The temperature in New York is 97.6°F.
print(weather) // The temperature in New York is 97.6°F.
print(weather) // The temperature in New York is 97.6°F.

By inheriting the CustomStringConvertible protocol, we need to provide a value to the description property. Every time, we want to use the object as a String, the program will refer to the description property. Now we can manage our message in one place.

IteratorProtocol

This protocol provides a .next() function that allows you to access to the next value. Let’s say we have a list of names and we want to iterate the list one by one without the need to store the current index.

Without:

let names = ["Bob", "Amy", "Mike"]
var currentIndex = 0
print("\(names[currentIndex]) did the laundry.")
currentIndex += 1
print("\(names[currentIndex]) did the dishes.")
currentIndex += 1
print("\(names[currentIndex]) did nothing.")
// Outputs
Bob did the laundry.
Amy did the dishes.
Mike did nothing.

Every time, we want to print a readable message, we have to copy and paste the message over. If at some point we want to modify the message, we will have to go through everything single place which would waste a lot of time.

With IteratorProtocol:

let names = ["Bob", "Amy", "Mike"]
var nameIterator = names.makeIterator()
print("\(nameIterator.next() ?? "") did the laundry.")
print("\(nameIterator.next() ?? "") did the dishes.")
print("\(nameIterator.next() ?? "") did nothing.")
// Outputs
Bob did the laundry.
Amy did the dishes.
Mike did nothing.

The makeIterator() function returns an object that inherits the IteratorProtocol protocol. We can use the next() function to get the next value without the need of tracking the current index. Remember, the .next() function returns an optional value.

Conclusion

There are tons of useful built-in protocols out there and I can’t list them all in one article. It would really waste you time if you write code from scratch when Swift already provides built-in supports. Using these protocols can also make your code much cleaner and more efficient.