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
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
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
Key Points:
Stack<T>
can store any type of elements.push(_:)
andpop()
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
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
Key Points:
associatedtype
allows defining custom type placeholders inside a protocol.- Any struct/class conforming to
Container
must implementadd(_:)
andgetAllItems()
.
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
Key Points:
T: Numeric
ensuresMathOperations
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
Key Points:
where Element: Numeric
limitssum()
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.