Advanced Swift: Generics
Introduction to Generics
Generics are a powerful feature in Swift that allows you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. By using generics, you can avoid duplication and write more expressive, abstract code.
Generic Functions
A generic function can work with any type. Here's a simple example of a generic function:
func swapTwoValues<T>(_ a: inout T, _ b: inout T) { let temporaryA = a a = b b = temporaryA }
In this example, <T>
is a placeholder type name, which can be replaced with any type. This function works for any type that can be swapped.
Generic Types
Generics are also available for classes, structures, and enumerations. Here's an example of a generic stack:
struct Stack<Element> { var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } }
This stack can store elements of any type. Here's how you can use it:
var intStack = Stack<Int>() intStack.push(1) intStack.push(2) print(intStack.pop()) // 2 var stringStack = Stack<String>() stringStack.push("Hello") stringStack.push("World") print(stringStack.pop()) // World
World
Type Constraints
Sometimes you want to enforce certain constraints on the types that can be used with generics. You can use type constraints to specify that a type must conform to a certain protocol. Here's an example:
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { return index } } return nil }
In this function, T: Equatable
means that the type T
must conform to the Equatable
protocol.
Associated Types
When defining protocols, you can use associated types to define a placeholder name for a type that is used as part of the protocol. This type is not specified until the protocol is adopted. Here's an example:
protocol Container { associatedtype Item mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } }
This protocol does not specify what type Item
must be; it just specifies that there must be an Item
type and certain requirements.
Extending a Generic Type
You can extend a generic type to add new functionalities. Here's an example of extending the generic Stack
type to add a computed property:
extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1] } }
This extension adds a topItem
property that returns the top item of the stack without removing it.
Conclusion
Generics are an essential feature of Swift that allows you to write flexible, reusable code. By understanding and using generics, you can create more abstract and powerful code, reducing duplication and increasing type safety.