What Are Generics in Swift and Why They Are Needed
\n\nImagine you are writing a function that swaps two numbers. Then you need the same function for strings. And then for dates. Without Generics, you would have to write three identical functions that differ only in data types. This violates the DRY (Don't Repeat Yourself) principle and makes the code cumbersome.
\n\nGenerics are a powerful mechanism in Swift that allows you to write flexible, reusable code that works with any data type. Instead of specifying a concrete type, you use a placeholder, which is replaced with the actual type when the function is called or the structure is created.
\n\nIn this article, we will break down how Generics work in Swift and show practical examples of how to use them in iOS development.
\n\n1. Basics of Generics: Functions and Types
\n\nLet's start with a simple example. Let's write a function that swaps two values:
\n\n// Without Generics — need to write separately for each type\nfunc swapInts(_ a: inout Int, _ b: inout Int) {\n let temp = a\n a = b\n b = temp\n}\n\nfunc swapStrings(_ a: inout String, _ b: inout String) {\n let temp = a\n a = b\n b = temp\n}\n\n// With Generics — one function for all types\nfunc swapValues<T>(_ a: inout T, _ b: inout T) {\n let temp = a\n a = b\n b = temp\n}\n\nvar x = 10, y = 20\nswapValues(&x, &y) // Works with Int\nprint("x: \\(x), y: \\(y)") // x: 20, y: 10\n\nvar str1 = "Hello", str2 = "World"\nswapValues(&str1, &str2) // Works with String\nprint("str1: \\(str1), str2: \\(str2)") // str1: World, str2: Hello\n\nHere <T> is a generic type parameter. The letter T is just a convention (from the word Type); you can use any name: <Element>, <ValueType>, etc.
Generics work not only with functions but also with types. Consider a simple container structure:
\n\n// Generic structure for storing a single value\nstruct Box<T> {\n let value: T\n \n init(_ value: T) {\n self.value = value\n }\n}\n\nlet intBox = Box(42) // Type Box<Int>\nlet stringBox = Box("Swift") // Type Box<String>\nlet arrayBox = Box([1, 2, 3]) // Type Box<[Int]>\n\nprint(intBox.value) // 42\nprint(stringBox.value) // Swift\n\nThe Swift compiler automatically determines the type T based on the passed value. This is called type inference.
2. Type Constraints
\n\nSometimes you need to restrict which types can be used with your generic. For example, you want to write a function that finds the maximum value in an array. But the > operator does not work with all types — only with those that conform to the Comparable protocol.
// Constraint: T must conform to the Comparable protocol\nfunc findMax<T: Comparable>(in array: [T]) -> T? {\n guard !array.isEmpty else { return nil }\n \n var maxValue = array[0]\n for element in array {\n if element > maxValue {\n maxValue = element\n }\n }\n return maxValue\n}\n\nlet numbers = [3, 7, 1, 9, 5]\nprint(findMax(in: numbers) ?? "No max") // 9\n\nlet words = ["apple", "zebra", "banana"]\nprint(findMax(in: words) ?? "No max") // "zebra" (alphabetically)\n\nThe syntax <T: Comparable> means: "Type T must conform to the Comparable protocol." You can add multiple constraints separated by a comma or use where for more complex conditions:
// Multiple constraints using where\nfunc process<T>(_ value: T) where T: Numeric, T: Comparable {\n if value > 0 {\n print("Positive