Explain generics and How to apply method or variable generics in Swift?


In this tutorial, you are about to learn how you can use generics to make your code reusable and flexible in an application. You will see how to create functions and properties that can work with any type with an example.

What is generics?

Swift allows you to write flexible, reusable code using generics. Generic code can work with any type. You can write code that avoids duplication and expresses its intent in a clear, abstract manner.

Generics are one of the most powerful features of Swift, and much of the Swift standard library is built with generic code. You’ve been using generics throughout the Language Guide, even if you didn’t realize it.

For example, Swift’s Array and Dictionary types are both generic collections. You can create an array that holds Int values or an array that holds String values. You can construct an array of just about any type that can be built in Swift. Similarly, you can create a dictionary to store values of any specified type, and there are no limitations on what that type can be.

Why do we need to use generics?

An example of a good generics use case is a stack. A stack is a container that has two operations: “Push” for adding an item to the container and “Pull” to remove the last item from the container. As a first step, we program the stack without generics. The result looks like this −

class IntStack {

   private var stackItems: [Int] = []
   
   func push(item: Int) {
      stackItems.append(item)
   }
   
   func pop() -> Int? {
      guard let lastItem = stackItems.last else {
         return nil
      }
      stackItems.removeLast()
      return lastItem
   }
}

The stack is capable of handling integers. To build a stack that can handle strings, what should we do? This is a very unreliable solution since we must replace "Int" at every place. At first glance, the following solution appears feasible −

class AnyObjectStack {
   private var items: [AnyObject] = []
   func push(item: AnyObject) {
      items.append(item)
   }
   func pop() -> AnyObject? {
      guard let lastItem = items.last else {
         return nil
      }
      items.removeLast()
      return lastItem
   }
}

Using AnyObject, we can now push strings to the stack. In this case, we lose type safety, and we also have to do a lot of casting when we use the stack.

With generics, you can define a generic type that behaves like a placeholder. Our example with a generic type −

class Stack<T> {
   private var items: [T] = []
   func push(_ item: T) {
      items.append(item)
   }
   func pop() -> T? {
      guard let lastItem = items.last else {
         return nil
      }
      items.removeLast()
      return lastItem
   }
}

A generic is defined with the diamond operator, in this case, we call it T . At initialization time we define the parameter and in the following, all T‘s are replaced by the compiler with that type −

let stack = Stack<Int>()
stack.push(89)
if let lastItem = stack.pop() {
   print("last item: \(lastItem)")
}

The big advantage is that we can now use a stack of any type.

Swift Generic Function

In Swift, we can create a function that can be used with any type of data. Such a function is known as a generic function.

Example

Here's how we can create a generic function in Swift −

// creating a generic function
func displayData<T>(data: T) {
   print("Example of generic function: ")
   print("Received Data: ", data)
}

// generic function working with String
displayData(data: "Swift")

// generic function working with Int
displayData(data: 5)

Output

Example of generic function:
Received Data: Swift
Example of generic function:
Received Data: 5

In the above example, we have created a generic function named displayData() with the type parameter <T>.

Generics Class

In the same way that the generic function can be used with any type of data, we can also create a class that can be used with any type of data. Generically speaking, such a class is referred to as a generic class.

Let's see an example

class Stack<T> {
   private var items: [T] = []
   func push(_ item: T) {
      items.append(item)
   }
   func pop() -> T? {
      guard let lastItem = items.last else {
         return nil
      }
      items.removeLast()
      return lastItem
   }
}

In the above example, we have created a generic class named Stack. This class can be used to work with any type of data.

Type constraints in Swift Generics

However, there is a downside to generics, since they can be of any type. A comparison of two generics will not work, even if the generics are the same.

class Stack<T> {
   private var items: [T] = []
   func push(_ item: T) {
      items.append(item)
   }
   func pop() -> T? {
      guard let lastItem = items.last else {
         return nil
      }
      items.removeLast()
      return lastItem
   }
   func isItemInStack(item: T) -> Bool {
      var found = false
      for stackItem in items {
         if stackItem == item { // Compiling Error
            found = true
         }
      }
      return found
   }
}

There is a compiler error in the function isItemInStack(item:T), because two values can only be compared if their corresponding types support the Equatable protocol. However, the type of generic can be constrained. The first line of the generic must implement the Equatable protocol in this case −

class Stack<T: Equatable> {
   // rest of the code will be the same as in the above example
}

Conclusion

With generics, you can avoid code duplication by creating reusable code. If you are uncomfortable writing generic code, you should feel fine staying away from it. However, generics allow you to create sustainable code and prepare for future instances in which you will need to reuse the same code.

Thanks to generics, we can not only use values as parameters but also types. Moreover, the Swift compiler can match generics to types thanks to its automatic type inference, checking the correctness of our code.

Generics should only be used when necessary. Making code generic before it's needed is premature optimization, which makes your code unnecessarily complex. It is always best to start with a specific code and generalise it only when you reach a concrete situation.

Updated on: 09-Dec-2022

149 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements