Swift Generics


Introduction to Generics in Swift

Generics in Swift allow you to write flexible, reusable, and type-safe code. Instead of specifying a particular data type, generics enable functions, classes, structures, and enumerations to work with any type.

Generics eliminate code duplication, improve performance, and provide compile-time type safety, making them essential for scalable Swift development.

Why Use Generics?

  • Avoids Code Duplication – Write one function/class that works for multiple types.
  • Ensures Type Safety – The compiler enforces correct data types.
  • Boosts Performance – Swift optimizes generics to avoid runtime overhead.
  • Enhances Code Reusability – Generic functions and classes work with any data type.

1. Generic Functions in Swift

A generic function can accept any type using placeholder type parameters (e.g., <T>).

func swapValues<T>(a: inout T, b: inout T) {
     let temp = a
     a = b
     b = temp
}

var x = 10, y = 20
swapValues(a: &x, b: &y)
print("x = \(x), y = \(y)")

Output

x = 20, y = 10

Explanation:

  • <T> is a placeholder for any data type.
  • swapValues works for integers, strings, arrays, or any type.

2. Generic Structures (struct)

You can create generic structs to handle different data types.

struct Box<T> {
     var item: T

     func getItem() -> T {
         return item
     }
}

let intBox = Box(item: 42)
print(intBox.getItem())

let stringBox = Box(item: "Swift")
print(stringBox.getItem())

Output

42
Swift

3. Generic Classes

A generic class allows defining reusable data structures.

class Stack<T> {
     private var elements: [T] = []

     func push(_ item: T) {
         elements.append(item)
     }

     func pop() -> T? {
         return elements.popLast()
     }
}

let intStack = Stack<Int>()
intStack.push(10)
intStack.push(20)
print(intStack.pop()!) // 20

Output

20

Key Points:

  • Stack<T> can store any type of elements.
  • push(_:) and pop() work for integers, strings, or custom objects.

4. Generic Enumerations (enum)

Enums can also be generic to store different types.

enum Result<T> {
     case success(T)
     case failure(String)
}

let successCase = Result.success(200)
let failureCase = Result.failure("Network error")

switch successCase {
     case .success(let value):
         print("Success with value \(value)")
     case .failure(let message):
         print("Failed with message: \(message)")
}

Output

Success with value 200

5. Generic Constraints (where Clause)

Sometimes, generics should work only with certain types (e.g., Numeric, Comparable).

func findMaximum<T: Comparable>(_ a: T, _ b: T) -> T {
     return a > b ? a : b
}

print(findMaximum(5, 10)) // 10
print(findMaximum("Swift", "iOS")) // "Swift"

Key Points:

  • T: Comparable ensures only comparable types can be used.
  • Prevents passing types that don’t support the > operator.

6. Generic Protocols

A protocol can use generics for flexible implementations.

protocol Container {
     associatedtype Item
     func add(_ item: Item)
     func getAllItems() -> [Item]
}

struct StringContainer: Container {
     private var items: [String] = []

     func add(_ item: String) {
         items.append(item)
     }

     func getAllItems() -> [String] {
         return items
     }
}

var strings = StringContainer()
strings.add("Hello")
strings.add("Swift")
print(strings.getAllItems())

Output

["Hello", "Swift"]

Key Points:

  • associatedtype allows defining custom type placeholders inside a protocol.
  • Any struct/class conforming to Container must implement add(_:) and getAllItems().

7. Type Constraints in Generic Classes

A generic class can limit the type of data it accepts.

class MathOperations<T: Numeric> {
     var value: T

     init(value: T) {
         self.value = value
     }

     func square() -> T {
         return value * value
     }
}

let num = MathOperations(value: 5)
print(num.square())

Output

25

Key Points:

  • T: Numeric ensures MathOperations only works with numbers.
  • This prevents non-numeric types (e.g., String) from being used.

8. Extensions with Generics

You can extend generic types to add extra functionality.

extension Array where Element: Numeric {
     func sum() -> Element {
         return reduce(0, +)
     }
}

let numbers = [1, 2, 3, 4, 5]
print(numbers.sum()) // 15

Output

15

Key Points:

  • where Element: Numeric limits sum() to only work with numbers.
  • You can use extensions to enhance generic types dynamically.

Best Practices for Generics in Swift


  • Use generics to write clean, reusable, and flexible code.
  • Apply constraints (T: Comparable, T: Numeric) when necessary.
  • Use associated types in protocols to create flexible designs.
  • Extend generic types with custom methods to improve modularity.
  • Avoid unnecessary generics – use specific types if the function/class doesn't require flexibility.

Conclusion

Swift Generics make your code more scalable, type-safe, and reusable. Whether you're working with functions, structures, classes, protocols, or enumerations, generics allow you to create flexible and powerful Swift applications.