Generics
Generics
Functions
You can follow along with this section by creating a blank iOS playground in Xcode. Replace the boilerplate with this code:
import UIKit
func swapTwoValues(a: Int, b: Int) -> (a: Int, b: Int) {
let temp = a
let newA = b
let newB = temp
return (newA, newB)
}
swapTwoValues(a: 42, b: 5)
When you run this code, the result of the last line is what you expect: (a: 5, b: 42). Now, suppose you want to swap two String values. You can overload swapTwoValues(a:b:):
func swapTwoValues(a: String, b: String) -> (a: String, b: String) {
let temp = a
let newA = b
let newB = temp
return (newA, newB)
}
swapTwoValues(a: "begin", b: "finish")
Now, running the playground yields (a: 5, b: 42) for the first function call and (a: "finish", b: "begin") for the second. Both functions work as expected, but there’s a lot of duplication. The only difference is the type of a and b.
You might think to use Any for the type. But this would allow the possibility of passing different types for parameters a and b. The Swift compiler doesn’t complain, but you’ll get a runtime error.
Warning: Don’t copy the following code into your playground. The mere presence of the function crashes the playground.
// Don't copy this in your playground
func swapTwoValues(a: Any, b: Any) -> (a: Any, b: Any) {
let temp = a
let newA = b
let newB = temp
return (newA, newB)
}
var name = "Tim Cook"
var phoneNumber = 5141111111
swapTwoValues(a: name, b: phoneNumber)
The solution is this generic function — give it a different name to be sure you’re calling it:
func swapValues<T>(a: T, b: T) -> (a: T, b: T) {
let temp = a
let newA = b
let newB = temp
return (newA, newB)
}
swapValues(a: 42, b: 5)
swapValues(a: "begin", b: "finish")
And running the playground produces the correct output for both types of input.
You need only one bit of syntax to make swapValues(a:b:) generic — the <T> in the function’s signature. Just as you enclose a function’s parameters in parentheses, you enclose a generic function’s type parameters in angle brackets ‹›. A generic function receives type parameters as part of a function call, just like it receives regular function parameters.
Note: This syntax only works if the type parameter appears in the function signature — at least one of the function parameters must be of type
T. In the next instruction page, you’ll learn how to define a function that uses a type parameter only in its body.
Once you define the type parameter inside the angle brackets, you can use it in the rest of the function’s signature and in the function’s body. When you call this function, Swift replaces T with the concrete type of the arguments you pass to it.
In the function’s prototype, you defined that the function receives two T values and returns a tuple of two T values. When you call the function with String literals, Swift knows that a and b are strings and can figure out that it needs to replace T with String.
Now, you have a single function that works across all possible types, saving you from having to copy and paste code. When you modify the function’s code, you do it in only one place, so there’s no chance of missing one of the copies.
Note: When your function is very generic, and the type can be any type, it’s fine to use single letter type parameter names like T or U. But your type parameters will usually have some sort of semantic meaning. Then, it’s best to use a more descriptive type name — like
Element,SequenceorOutput— that informs readers of its meaning.
Generic Types
All Swift collections are generic types. For example, this array initializer creates an array containing the elements of a sequence:
init<S>(_ s: S) where Element == S.Element, S : Sequence
And Sequence is a generic protocol — a protocol with associated type:
protocol Sequence<Element>
Note: You’ll learn about
wherein the next instruction page and associated types in the next lesson.
Creating a Generic Type
Here’s a generic struct you could use to store and retrieve any type from UserDefaults:
struct Setting<T> {
let key: String
var value: T? {
get {
UserDefaults.standard.value(forKey: key) as? T
} set {
UserDefaults.standard.setValue(newValue, forKey: key)
}
}
}
Like generic functions, generic types also have type parameters, declared right next to the type name. You can provide a concrete type to a generic type by writing the type inside angle brackets next to the type’s name:
var volume = Setting<Float>(key: "audioVolume")
volume.value = 0.5
Here, Swift replaces T with Float. The process of replacing a type parameter with a concrete type value is called specialization. In this case, typing <Float> is necessary because Swift doesn’t have a way to infer it. If you use the type parameter in the initializer, Swift can figure out what your concrete type is without you having to write angle brackets.
Remember that Setting is not a type. Declaring a variable of type Setting produces a compiler error. If you start to type let setting: Setting, Xcode suggests something like:
let setting: Setting<Int> = .init(key: "myKey")
Only specialized variants of the type, like Setting<Int>, are real types. Generic types are just a blueprint.
Extending a Generic Type
You can extend generic types the same way you can extend a normal class, struct, enum, or protocol. In the extension of a generic type, you have access to its type parameters:
extension Setting {
mutating func save(from untypedValue: Any) {
if let value = untypedValue as? T {
self.value = value
}
}
}
Here, you cast the received untypedValue to the type parameter T of Setting.
Now, you know how to create generic functions and generic types that work with any type of object. But most of the time, you don’t want to allow every object type. Continue to the next instruction page to learn how to constrain your generics.