Swift: From Protocol to AssociatedType then Type Erasure

Mohammed Rokon Uddin
Monstar Lab Bangladesh Engineering
6 min readMar 11, 2019

--

Grand Sultan Tea Resort & Golf, Sreemangal, Bangladesh

Motivation

We use classes to represent a symmetric operation, like Comparison, for example, if we want to write a generalized sort or binary search where we need to compare two elements , we end up this something as below:

We don’t know anything about an arbitrary instance of Ordered yet. So if the method is not implemented by a subclass, well, there is nothing we can do other than trap. Now, this is the first sign that we are fighting the type system. And if we fail to recognize that, it is also where we start lying to ourselves. Because we brush the issue aside, telling ourselves as long as each subclass of Order implements precedes, we will be okay. Making it the subclass’s problem. So we go ahead and implement an example of Ordered as below.

It got double value and we override precedes to do the comparison. other is just arbitrary Ordered and not a number. So we don’t know that other has value property. We down-cast Other to Number to get to the right type to compare. It is a static type safety hole. Classes don’t let us express this crucial type relationship between the type of self and type of Other. It is a code smell. So any time we see a force down-cast in our code, it’s a good sign that some important type relationship has been lost, and often that’s due to classes for abstraction. Clearly, what we need is a better abstraction mechanism.

Better Abstraction Mechanism

A abstraction mechanism must have following properties:

  • Doesn’t force to accept implicit sharing or lost type relationships
  • Force to choose just one abstraction and do it at the time types are defined
  • Doesn’t force to accept unwanted instance data or the associated initialization complexity
  • Doesn’t leave ambiguity about what need to override.

And yes…!!! Protocol has all of these properties.

Don’t start with a class. Start with a protocol…!!!

Time for POP, no more OOP

Protocol-Oriented Programming in Swift

When Swift was made, it was made as the first protocol-oriented. Though Swift is great for object-oriented programming, but from the way for-loops and String literals work to the emphasis in the standard library on generics, at its hearts, Swift is protocol-oriented. There is a saying in Swift: “Don't start with class, start with protocol” . So let’s redo the binary search example with protocol:

Wow…!!! “Protocol” rocks…!!!

Protocol Self Requirement

Once we have a Self-requirement to a protocol, it moves the protocol into a very different world, where the capabilities have a lot less overlap with classes.

  • It stops being usable as a type
  • Collections become homogeneous instead of heterogeneous
  • An interaction between instances no longer implies an interaction between all model types.
  • We trade dynamic polymorphism for static polymorphism, but, in return for that extra type information we are giving the compiler, it is more optimizable
Protocol Oriented Programming in Swift, session 408, #WWDC2015

Protocol Extension

In our current implementation, we need to implement precedes method for each type. e.g:

Here comes the power of protocol. One implementation to rule them all by using a constrained extension on Ordered.

Let’s take constrained extension a step further

Protocol Associated Types:

By now we know what is Protocol Oriented Programming (POP). But POP without Protocol Associated Types (PAT) will never be completed.

associatedtype is a protocol generic placeholder for an unknown Concrete Type that requires concretisation on adoption at Compile time.

Protocol Associated Types (PAT)= Type Alias + Generics

At one stage of programming in Swift, many of us came across below error:

Error while trying to use Generic in Protocol

Swift does not allow us to use Generic parameters in Protocol. To bypass this limitation, Swift introduced Protocol Associated Type . Below example shows how to use associatedType in Protocol

Type Erasure

Though associatedType solved one problem but it also introduce another problem. In Swift we can not use Protocol with associatedType as a Type. What it means is, if we set a variable type to ViewModelType, Swift compiler will show following error:

Error while using Protocol with AssociatedType as Type

Type Erasure is the only saviour here to this error. There are three patterns that we can apply to solve the problem of generic constraints requirement.

  • Constrained Type Erasure: erases a type but keeps a constrain on it.
  • Unconstrained Type Erasure: erases a type without a constrain on it
  • Shadow Type Erasure: erases a type by camouflaging the type

Example

The below code snippet shows how to implement multi-sectioned heterogeneous TableViewCell using Unconstrained Type Erasure

If you download the project shared below and run the app, you will see the app has two models TextCellModel and ImageCellModel displayed using two table cells TextTableViewCell and ImageTableViewCell respectively. As the app has different models and cells under single TableView section, without TypeErasure we would face following problems

  • Self or Associated Type Requirement:
let items: [CellModel] = [a_text_model, a_image_mode] // Error explained above

CellModel has associated type requirement to avoid spaghetti code while dequeueing TableCell. Without associated type requirement out code would be as:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let items = sections[indexPath.section].items
let item = items[indexPath.row]
// More the model types, more the if-else
if item is TextCellModel {
// load TextTableViewCell
} else {
// load ImageTableViewCell
}
}
  • Heterogeneous Array:

As TableView has different models and cells under same section, without TypeErasure we had to populate different array for different model which will lead to lots of if-else. Instead we used as:

let imageCell = AnyCell(ImageCellModel("cyclamen"))
let quoteCell = AnyCell(TextCellModel("Hello World.", author: "-"))
let anyCells = [imageCell, quoteCell]

if we had just directly instantiated our ImageCellModel instance using the ImageCellModel initializer, it would be of type ImageCellModel. But because we have instantiated using this AnyCell wrapper class, ImageCellModel is now instantiated as type AnyCell . We have just erased type information (this is what type erasure means)

Wrapper classes are conventionally prefixed with the word “Any”, in order to guarantee that you will instantiate an object that implements our protocol and fills the generic type, without necessarily having the implementation on hand.

🎉 Using AnyCell wrapper we’ve erased the Type requirement when conforming to CellModel protocol. The init function is without clause and we now have a heterogenous collection Type and dynamic dispatch at our disposition. 👍🏿

Source Code:

Related Articles:

Swift Associated Type Design Patterns

Protocol-Oriented Programming in Swift

Swift: Attempting to Understand Type Erasure

Keep Calm and Type Erase On

Conclusion

We often end up writing spaghetti code while implementing TableView or CollectionView with different types of cells and models. TypeErasure is the right choice to avoid spaghetti code, makes code much more organized and increase readability. I hope that you have enjoyed this article. I encourage you to read my other articles Custom UIView from .xib and TableView Prefetching DataSource using Swift

Thank you all for your attention 🙏🏻. feel free to tweet and get connected.

Disclaimer: I went through bunch of other articles and copied easy to understand examples and sentences while writing this article

--

--

Mohammed Rokon Uddin
Monstar Lab Bangladesh Engineering

[ Apple Platform Developer | Tea & Coffee Brewer ☕️ | Occasional Chef 👨‍🍳 | Soccer Player ⚽️ ]