Generics is a type of code that allows developers to write reusable and flexible functions that can be used with various data types. Moreover, it is one of the key features of Swift, Apple’s programming language that is used to develop iOS, Mac, Apple TV, and Apple Watch apps.
So, what’s the biggest advantage that Generics offers Swift? Well, the ability to write abstract code jumps to mind. Why is this important? That’s because code abstraction is an essential component of object-oriented programming.
Abstraction is the process of hiding the background parts or implementation details from users. This way, users can only see the required information. For example, a user operating an oven only needs to know how to handle the oven's controls and doesn't need to know anything about the internal working of the oven.
This abstraction in programming languages is achieved by defining the access modifiers like public or private and creating abstract classes. In the case of Swift, protocols can be used instead.
A protocol in Swift is a blueprint of methods, properties, and other requirements that suit a particular functionality. Such protocols can be adopted by types such as class, structure, or enumeration. This helps implement those requirements and is known as conforming to the protocol.
Another crucial element provided by Generics in Swift is polymorphism, which is also one of the principles of object-oriented programming. So, let’s dive into the world of polymorphism with some pizza 🍕
In an object-oriented system, developers often work with instances of various types that share characteristics. This occurs due to classes or subclasses inheriting from the same ancestor.
For example, if an object ‘car’ exists, it could have numerous subclasses such as sedan, hatchback, and SUV.
Here is where polymorphism enters the frame and provides an interface to entities of different types. Now, let's take a look at the types of polymorphism with the example of a pizza shop (we did promise you some pizza, didn't we 😉)
This type of polymorphism takes place when a parent type of a high-level code is created, and the detailed low-level code in the child type conforms to the parent type.
In this example, we have created a parent class ‘Pizza’, which has a type called toppings and a preparePizza(). To create a detailed product in the case of the pizza shop, we create a subclass Margherita that conforms to Pizza class and overrides the preparePizza() method. In subtype polymorphism, a new subclass has to be created for each type of pizza.
Users can then create new instances of pizzas they want and implement the preparePizza() method on them.
This form of polymorphism occurs when method overloading is implemented. Method overloading refers to a scenario where a class has numerous methods with the same name but differ in parameters.
Continuing with the pizza example, we have to set up overloaded methods to create different types of pizza. Furthermore, we also need to create a new overloaded method for each pizza type.
Here, we are creating two types of pizzas—Margherita and customized pizza. So, we create a class called Pizza with a type for toppings and overloaded the preparePizza methods for making different pizzas.
Users can now create an instance of the pizza using the appropriate method for the pizza that they want.
Parametric polymorphism forms the base of generics and employs the same implementation for different parameter types. Let’s understand this better with our pizza shop example.
Keeping the same pizza shop example, here we created the same PizzaTopping enum with a case for each topping. We also created a protocol ItemName that only has one type called string.
We’ll start off by creating a PizzaType that conforms to the ItemName protocol. In this case, we’ll be making three types of pizza—Margherita, farmhouse, and golden corn. We would also be using the array of PizzaToppings for the type toppings. Additionally, a switch case will also be implemented depending on the pizza type for each pizza’s respective toppings array. Finally, we’re also integrating the type message from protocol ItemName, which will print the name of the pizza that is ready.
Next, we’ll be creating an enum type called SideDishType that also conforms to the ItemName protocol. The cases in this example would be fries and calzones. Also, the type message from the ItemName protocol will return a string value when each of the side dish is ready.
As you can see, we have created a class named PizzaShop, which will use the generic prepare method. This method employs a type T, which conforms to the ItemName protocol in angular brackets. This type T will be passed to the method as a parameter. Within this method definition, we’ll print the type.message. Thus, this method can accept any PizzaType or SideDishType and return the item accordingly.
As you can see, we have created a class named PizzaShop, which will use the generic prepare method. This method employs a type T, which conforms to the ItemName protocol in angular brackets. This type T will be passed to the method as a parameter. Within this method definition, we’ll print the type.message. Thus, this method can accept any PizzaType or SideDishType and return the item accordingly.
You can now create an instance of PizzaShop and then call the prepare method. Within the type parameter, any PizzaType or SideDishType can be passed and hence the dish will be prepared!
This just highlights the potential of Generics and the value it adds to Swift. Next up, we keep our Swift series going with the exploration of the updates announced to Generics as part of the Swift 5.7 at WWDC 2022.
So, keep a lookout 😄
Explore More