Extensions and Generics
Adding functionality to existing types with extension, and writing code that works with any type using generics.
읽는 데 2분
Two features round out the Swift toolkit for writing reusable, flexible code: extensions, which add functionality to a type after the fact, and generics, which let a single function or type work with many different types safely.
Extensions: adding to a type you didn't write
extension lets you add methods, computed properties, and more to an existing type — including types you don't own, like Swift's own Int or String:
extension Int {
var isEven: Bool {
return self % 2 == 0
}
func squared() -> Int {
return self * self
}
}
print(4.isEven) // true
print(5.squared()) // 25This is how Swift itself provides so much built-in functionality across simple types without a bloated base class — Int, String, and Array all gain capabilities through extensions defined in the standard library, and you can extend them further the exact same way in your own code.
Extensions are also the standard way to organize protocol conformance separately from a type's main definition:
struct Temperature {
var celsius: Double
}
extension Temperature: CustomStringConvertible {
var description: String {
return "\(celsius)°C"
}
}
print(Temperature(celsius: 22)) // "22.0°C"Generics: one implementation, many types
Without generics, you'd need a separate function for every type you wanted to support:
func swapInts(_ a: inout Int, _ b: inout Int) {
let temp = a
a = b
b = temp
}
// You'd need swapStrings, swapDoubles, swapBools... duplicated logic every timeA generic function replaces the concrete type with a placeholder — conventionally named T — that Swift fills in based on what's actually passed:
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 1, y = 2
swapValues(&x, &y) // T is inferred as Int
var first = "a", second = "b"
swapValues(&first, &second) // T is inferred as StringOne implementation now safely handles any type — and "safely" is the key word: the compiler still enforces that both arguments are the same type T, so swapValues(&x, &first) (mixing an Int and a String) fails to compile, just as it would with two separate non-generic functions.
Constraining a generic with a protocol
Sometimes a generic function needs to do more than just pass a value around — like compare two values, which requires them to support ==. A type constraint requires the generic type to conform to a specific protocol:
func areEqual<T: Equatable>(_ a: T, _ b: T) -> Bool {
return a == b
}
print(areEqual(3, 3)) // true
print(areEqual("cat", "dog")) // falseT: Equatable means "T can be anything, as long as it supports ==." Swift's own Array, Dictionary, and Optional are all themselves generic types built exactly this way — which is why Array<Int> and Array<String> are really the same underlying implementation, specialized safely for each element type.