Guide to Prototype Pattern in Swift
Abstract object creation in your code
Prototype is a creational pattern defined by the book “Design Patterns, Elements of Reusable Object-Oriented Software”. The main objective of the pattern is to create new objects by copying a given instance.
The ability to copy objects while being agnostic of its concrete type is very powerful. For instance, you might be creating a Framework that requires an object from the client, and it needs to instantiate that object in different moments at run time. With Prototype you can decouple your Framework while still allowing clients to customise it.
In the book, an example about a music editor Framework is given. In this example, the Framework allows the clients registering different objects that represent notes, rests and staves.
There are other advantages of using the pattern, like reducing the number of classes of your application, specify new objets by varying a value or structure, and adding and removing products at run time, so I highly recommend the reading. In our case, however, we will focus on the example from before and see how it can be used to abstract the creation of new objects in which concrete types are not known to your classes.
The Setup
Let’s start with a simple Playground example. We will create a class ClientViewController which represents a client of our Music Sheet Framework.
The result is a simple quarter note being displayed in the Live View:
Now, let’s say that the Music Sheet Framework provides its users a class named MusicSheetPlotter. The client of this class passes a reference for a quarterNote, which is a UILabel for the sake of simplicity. With the reference in hand, the plotter tries to add the note 3 times to a stack when plot(in:)
is called.
Now, let’s modify our previous private lazy var content
from ClientViewController so it uses the MusicSheetPlotter:
If you run the Playground now, what do you think will happen?
You probably guessed it right. The result is that only one quarterNote shows up, exactly as before. This is because the UIView is being passed as reference, and it makes no sense to add the same reference multiple times into a stack view. What we need is to create different instances of that quarterNote at run-time.
Using Prototype
Although Foundation provides us with a NSCopying
, it requires us to comply with an ugly interface func copy(with zone: NSZone? = nil) -> Any
, in addition to inheriting from NSObject, which is very reminiscent of ol’ Objective-C.
Because of that, let’s create the protocol ourselves. This will also give as more flexibility. Don’t forget to extend UILabel so it complies to Prototype. See below:
Note that the Prototype protocol would live inside our Framework, and not on the client side.
Now we can modify our MusicSheetPlotter so it actually clones the objects.
Run the Playground again:
With the latest changes, our MusicSheetPlotter works as expected! The client can provide any kind of UIView (being a UILabel or not) as long as it complies to Prototype. Customisation now is easy and transparent to our Framework.
There you go, that’s the traditional way of implementing the Protocol Pattern. Yet, there is still something that doesn’t make me completely happy: the fact that we have to cast the cloned object.
Going for the extra mile
The problem with the above implementation is that it can easily break. For instance, if the client passes a Prototype which is not a UIView, it will crash.
We could remove the force unwrapping of the cloned objects and make sure that they are of type UIView before adding them, but still, that’s an open door for unexpected behaviour and bad experience for the client.
Clients don’t know that MusicSheetPlotter needs to cast quarterNote to UIView. The only thing we expect from them is a Prototype. From their perspective, any Prototype should do it.
We can solve this in two ways.
1. Protocol Composition
This is the easiest solution. We simply need to compose the Prototype with another type we expect. In this case, UIView:
This will guarantee that the client will pass an object that we can actually work with. We still have to cast it, but at least we know that the object can actually be casted to UIView.
2. Associated Type
The first step here is to modify our Prototype protocol so it uses an associated type. Secondly, we have to specialise the return type for our clone()
method in the UILabel extension, or whatever other classes implementing Prototype. See below.
This will result in the following error coming from quarterNote inside MusicSheetPlotter:
Protocol ‘Prototype’ can only be used as a generic constraint because it has Self or associated type requirements
Finally, in order to fix the error message, we pass the Prototype as a generic constraint, making sure that we specify that CloneType needs to conform to UIView:
Done! Now the clients of our Framework’s MusicSheetPlotter are limited to which type they can pass, thus, making it impossible to break due to casting. If they try, they will receive an error message:
That’s it for today, I hope you find this article helpful. Bye and happy coding!