SECRET OF CSS

How to Achieve Dynamic Dispatch Using Generic Protocols in Swift 5.7


It’s never been easier!

1*M0AiI3dVSAfCcdnDlCBcOg

Dynamic dispatch is one of the most important mechanisms in Object-Oriented Programming (OOP). It is the core mechanism that makes run-time polymorphism possible, enabling developers to write code that decides their execution path during run-time rather than compile-time.

As easy as it seems to achieve dynamic dispatch in OOP, it is not the case when it comes to Protocol-Oriented Programming (POP). Trying to accomplish dynamic dispatch using protocols always comes with unpredicted difficulties due to various limitations in the Swift compiler.

With the release of Swift 5.7, all these have become history! Achieving dynamic dispatch in the realm of POP has never been easier. In this article, let’s explore what kind of improvements we get from Swift 5.7 and what it takes to accomplish dynamic dispatch using protocol with associated types.

So without further ado, let’s get right into it!

Note:

If you’re unfamiliar with the some and any keyword in Swift, I highly encourage you to first read my blog post called “Understanding the “some” and “any” keywords in Swift 5.7“.

Before I can start showing you the improvements in Swift 5.7, let’s define the protocols and structs that we need for our sample code throughout this article.

The definitions we have above are similar to what we are using in my previous article, but with a little bit of a twist. Here in our Vehicle protocol, we have 2 function requirements, startEngin() and fillGasTank(with:). For the sake of demonstration, We will try to achieve dynamic dispatch using these 2 functions in both Car and Bus structs.

Now, let’s say we want to create a startAllEngin() function that accepts a heterogeneous array as shown below:

You will notice that this is literally impossible in Swift 5.6 as you will be prompted with an error saying: “Protocol ‘Vehicle’ can only be used as a generic constraint because it has Self or associated type requirements”. The Swift compiler is prohibiting us to create a heterogeneous array with Vehicle as its element type due to the fact that Vehicle has an associated type (FuelType).

Pro Tip:

If you would like to learn more about the error, and how you can work around it prior to Swift 5.7, check out my article published on Medium: “Swift: Accomplishing Dynamic Dispatch on PATs (Protocol with Associated Types)

Thanks to the upgrade Apple made to the Swift compiler, this limitation no longer exists in Swift 5.7. We can finally use a protocol just like how we use a superclass in OOP. Let me show you how.

In Swift 5.7, creating a heterogeneous array is no longer prohibited by the compiler. All we need to do is to use the any keyword.

By using the any keyword, we are telling the compiler that the array will contain existential types and that their underlying concrete type will always conform to the Vehicle protocol.

With that, calling startAllEngin(for:) will give us the dynamic dispatch that we want.

Now let’s take a look at another more complicated example. Let’s say we want to create a function named fillAllGasTank(for:). This function will perform dynamic dispatch to the vehicle’s fillGasTank(with:) function based on the given vehiclesarray.

Define a Generic Parameter Type

What we trying to achieve might seems straightforward at first, but when we start coding, we will bump into our first problem:

Since different types of vehicles will require different kinds of fuel, we will have to create a generic protocol to represent both Gasoline and Diesel. Let’s go ahead and do that.

The Fuel protocol is just a simple protocol consisting of an associated type named FuelType, and a static purchase() function. Notice how we constrain FuelType to always equal to the type that conforms to the Fuel protocol. This constraint is very important in order for the compiler to determine the concrete type returned by the static purchase() function.

Next up, let’s conform both Gasoline and Diesel to the Fuel protocol.

On top of that, we also need to ensure that the Vehicle protocol’s FuelType is a type that conforms to the Fuel protocol.

“any” to “some” Conversion

With the Fuel protocol and all other related changes in place, we can now revisit the fillAllGasTank(for:) function and update it accordingly.

In the above code, notice how we leverage the vehicle’s fuel type to get an instance of the Fuel concrete type, so that we can pass it into the fillGasTank(with:)function.

Unfortunately, if we try to compile our code, we will bump into our 2nd problem: Member ‘fillGasTank’ cannot be used on value of type ‘any Vehicle’; consider using a generic constraint instead”. What does that mean?

In order to understand the error that we are getting, let’s have a quick recap on what are the differences between the some and any keyword.

Compare the differences between the some and any keyword in Swift

As illustrated in the above image, the underlying concrete type of an existential type is being wrapped within a box. Therefore, the compiler is prohibiting us from accessing the fillGasTank(with:) function. To go about this, we must first convert (unbox) the existential type to an opaque type before accessing the fillGasTank(with:) function.

Fortunately, Apple has made the conversion (unboxing) process extremely easy in Swift 5.7. All we need to do is to pass the existential type to a function that accepts an opaque type and the conversion will happen automatically.

With that, we can now compile and execute our code without any error.

Feel free to grab the full sample code here if you want to try it out yourself.



News Credit

%d bloggers like this: