Swift Generics: A closer look
CocoaHeadsSKG
Dimitri James Tsiflitzis
Generics
Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.
For instance Swift’s Array and Dictionary types are both generic collections. So is ?. It's shorthand for Optional
Why?
/// Declare
func printIntElements(_ array: [Int])
{
array.map { print($0) }
}
/// Call
printIntElements([1, 2, 3, 4, 5])
/// Console
1
2
3
4
5
Why?
func printDoubleElements(_ array: [Double])
{
array.map { print($0) }
}
func printStringElements(_ array: [String])
{
array.map { print($0) }
}
Generic functions
func printWhateverTypeElements<PlaceHolderTypeName>(_ array: [PlaceHolderTypeName])
{
array.map { print($0) }
}
/// OR
func printWhateverTypeElements<T>(_ array: [T])
{
array.map { print($0) }
}
Placeholder & actual type
All placeholder types do is define that the parameters that are declared as that type must be of the same type when called.
Type Parameters
- Type parameters specify and name a placeholder type, and are written immediately after the function’s name, between a pair of matching angle brackets (such as <T>).
- Use it to define the type of a function’s parameters, or as the function’s return type, or as a type annotation within the body of the function.
- You can provide more than one type parameter by writing multiple type parameter names within the angle brackets, separated by commas.
Naming Type Parameters
From the docs:
Always give type parameters upper camel case names (such as T and MyTypeParameter) to indicate that they’re a placeholder for a type, not a value.
Element is a fan favourite too.
Type Parameters
func pairs<Key, Value>(from dictionary: [Key: Value]) -> [(Key, Value)]
{
return Array(dictionary)
}
let some = pairs(from: ["minimum": 199, "maximum": 299])
// result is [("maximum", 299), ("minimum", 199)]
let more = pairs(from: [1: "Swift", 2: "Generics", 3: "Rule"])
// result is [(2, "Generics"), (3, "Rule"), (1, "Swift")]
Constraining a Generic Type
func mid<T>(array: [T]) -> T?
{
guard
!array.isEmpty
else
{
return nil
}
return array.sorted()[(array.count - 1) / 2]
}
/// 👹 on sorted
/// The problem is that for sorted() to work, the elements
/// of the array need to be Comparable
Constraining a Generic Type
func mid<T: Comparable>(array: [T]) -> T?
{
guard
!array.isEmpty
else
{
return nil
}
return array.sorted()[(array.count - 1) / 2]
}
Constraining a Generic Type
extension Array where Element: Equatable
{
mutating func removeObject(object: Element)
{
guard
let index = self.index(of: object)
else
{
return
}
self.remove(at: index)
}
}
Use case: A Stack
/// Int Stack
struct IntStack
{
}
Interlude: Reference Vs. Value Types
Interlude: Reference Vs. Value Types
Swift from Objective-C is the heavy preference of value types over reference types.
value types keep a unique copy of their data, while reference types share a single copy of their data.
Swift represents a reference type as a class. There are many kinds of value types in Swift, such as struct, enum, and tuples
Interlude: Reference Vs. Value Types
let c = Car()
let d = c
Car Instance
class Car
Interlude: Reference Vs. Value Types
let c = Car()
let d = c
Car Instance
struct Car
Car Instance
Use case: A Stack
/// Int Stack
struct IntStack
{
public private(set) var items = [Int]()
mutating func push(_ item: Int)
{
items.append(item)
}
mutating func pop() -> Int
{
return items.removeLast()
}
}
Use case: A Stack
/// Element Stack
struct Stack<Element>
{
public private(set) var items = [Element]()
mutating func push(_ item: Element)
{
items.append(item)
}
mutating func pop() -> Element
{
return items.removeLast()
}
}
Use case: A Stack
var stackOfStrings = Stack<String>()
stackOfStrings.push("ένα 1️⃣")
stackOfStrings.push("δύο 2️⃣")
stackOfStrings.push("τρία 3️⃣")
stackOfStrings.push("τέσσερα 4️⃣")
Use case: A Tree
Root
Leaf
Node
Node
Leaf
Leaf
Leaf
Level 0
Level 1
Level 2
Use case: A Tree
class Node
{
var value: String
var children: [Node] = []
weak var parent: Node?
init(value: String)
{
self.value = value
}
func add(child: Node)
{
children.append(child)
child.parent = self
}
}
Use case: A Tree
class Node<T>
{
var value: T
weak var parent: Node?
var children: [Node] = []
init(value: T)
{
self.value = value
}
func add(child: Node)
{
children.append(child)
child.parent = self
}
}
Extending a Generic Type
extension Stack
{
var topItem: Element?
{
return items.isEmpty ? nil : items[items.count - 1]
}
}
Extensions with a Generic Where Clause
extension Stack where Element: Equatable
{
func isTop(_ item: Element) -> Bool
{
guard
let topItem = items.last
else
{
return false
}
return topItem == item
}
}
Associated Types
protocol Container
{
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
}
Associated Types
struct Stack<Element>: Container
{
var items = [Element]()
mutating func push(_ item: Element)
{
items.append(item)
}
mutating func pop() -> Element
{
return items.removeLast()
}
// conformance to the Container protocol
mutating func append(_ item: Element)
{
self.push(item)
}
var count: Int
{
return items.count
}
}
Generics is arguably one of the major benefits of using Swift over Objective-C.
By being able to associate generic types with things like collections, we can write code that is a lot more predictable and safe.
Anecdotal evidence. Swift app rarely crash. They might be full of bugs but they don't crash.
Ευχαριστούμε
🎈
CocoaHeadsSKG
Dimitri James Tsiflitzis
Swift Generics
By tsif
Swift Generics
- 153