Web Analytics Made
Easy - StatCounter

Menu

About us Contact us

Swift: From Protocol to AssociatedType then Type Erasure

Tech 2019 / 07 / 16

Swift: From Protocol to AssociatedType then Type Erasure



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:

https://gist.github.com/Ordered.swift

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 Orderedas below.

https://gist.github.com/Number.swift

It got double value and we override precedesto do the comparison. otheris 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 selfand 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.

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

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-loopsand 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:

https://gist.github.com/BinarySearch.swift

 

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

 

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

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

https://gist.github.com/Ordered.swift

Let’s take constrained extension a step further

https://gist.github.com/BinarySearch.swift

Let’s take constrained extension a step further

https://gist.github.com/BinarySearch.swift

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

associatedtypeis a protocol generic placeholder for an unknown Concrete Typethat 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 Genericparameters in Protocol. To bypass this limitation, Swift introduced Protocol Associated Type. Below example shows how to use associatedtype in Protocol

https://gist.github.com/pat.swift

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 ViewModelTypeSwiftcompiler will show following error:

Error while using Protocol with AssociatedType as Type

 

Type Erasureis 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

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

https://gist.github.com/TypeErasure.swift

If you download the project shared below and run the app, you will see the app has two models TextCellModeland ImageCellModel displayed using two table cells TextTableViewCelland ImageTableViewCellrespectively. As the app has different models and cells under single TableView section, without TypeErasurewe 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 TypeErasurewe 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 Typerequirement when conforming to CellModel protocol. The initfunction is without clause and we now have a heterogenous collection Type and dynamic dispatch at our disposition. 👍🏿

https://github.com/rokonuddin/TypeErasure

Swift Associated Type Design Patterns

Protocol-Oriented Programming in Swift

Swift: Attempting to Understand Type Erasure

Keep Calm and Type Erase On

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


Related Posts :

Step into Swift Combine

iOS TableView Prefetching DataSource using Swift

 

You have ideas, We have solutions.

CONTACT US!