Einleitung: Warum kommt man ohne Generics in Swift nicht aus?
Stellen Sie sich vor, Sie müssen eine Funktion schreiben, die zwei Zahlen addiert. Dann eine Funktion zum Addieren zweier Zeichenketten. Und dann eine für Arrays. Ohne generische Programmierung (Generics) müssten Sie für jeden Datentyp eine separate Version des Codes erstellen. Das erhöht nicht nur den Code-Umfang, sondern erschwert auch dessen Wartung.
Generics in Swift sind ein mächtiges Werkzeug, das es ermöglicht, flexiblen, wiederverwendbaren und typsicheren Code zu schreiben. Sie sind die Grundlage für beliebte Strukturen wie Array, Dictionary und Optional. In diesem Artikel werden wir untersuchen, wozu Generics dienen, wie man sie in der Praxis einsetzt und warum jeder angehende iOS/macOS-Entwickler dieses Thema beherrschen sollte.
Was sind Generics in einfachen Worten?
Generics (Verallgemeinerungen) sind eine Möglichkeit, Code zu schreiben, der mit jedem Datentyp funktioniert, ohne dabei die strenge Typisierung von Swift zu opfern. Anstatt einen konkreten Typ (z. B. Int oder String) anzugeben, verwenden Sie einen Platzhalter (meistens den Buchstaben T), der beim Aufruf der Funktion oder beim Erstellen der Struktur durch den tatsächlichen Typ ersetzt wird.
Das Problem ohne Generics
Angenommen, Sie schreiben eine Funktion zum Vertauschen der Werte zweier Variablen. Ohne Generics müssten Sie so schreiben:
func swapInts(_ a: inout Int, _ b: inout Int) { let temp = a a = b b = temp}
func swapStrings(_ a: inout String, _ b: inout String) { let temp = a a = b b = temp}Das ist offensichtliche Duplizierung. Wenn Sie Double oder benutzerdefinierte Typen vertauschen müssen, wird die Anzahl der Funktionen weiter steigen.
Die Lösung mit Generics
Eine universelle Funktion ersetzt alle vorherigen:
func swapValues<T>(_ a: inout T, _ b: inout T) { let temp = a a = b b = temp}
// Verwendung:var x = 10var y = 20swapValues(&x, &y) // x = 20, y = 10
var firstName = "Anna"var lastName = "Ivanova"swapValues(&firstName, &lastName) // firstName = "Ivanova"Die spitzen Klammern <T> deklarieren den Typ-Platzhalter. Der Compiler ermittelt automatisch, dass T im ersten Fall Int und im zweiten Fall String ist. Der Code wird prägnant und sicher.
Wie man Generics verwendet: Hauptszenarien
Generics in Swift werden in Funktionen, Strukturen, Klassen, Aufzählungen und Protokollen verwendet. Betrachten wir die häufigsten Fälle.
1. Generische Funktionen
Wir haben bereits das Beispiel mit swapValues gesehen. Aber man kann noch weiter gehen – eine Funktion erstellen, die den Index eines Elements in einem Array findet:
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? { for (index, element) in array.enumerated() { if element == value { return index } } return nil}
let numbers = [1, 5, 9, 12]if let index = findIndex(of: 9, in: numbers) { print("Index: \\(index)") // Index: 2}Beachten Sie <T: Equatable>. Dies ist eine Typeinschränkung (type constraint). Wir sagen: „T muss dem Protokoll Equatable entsprechen, damit wir den Operator == verwenden können.“ Ohne diese Einschränkung würde der Code nicht kompilieren.
2. Generische Typen (Strukturen und Klassen)
Erstellen wir eine einfache „Stapel“-Struktur (LIFO), die beliebige Typen speichern kann:
struct Stack<Element> { private var items: [Element] = []
mutating func push(_ item: Element) { items.append(item) }
mutating func pop() -> Element? { return items.popLast() }
func peek() -> Element? { return items.last }}
// Verwendung:var intStack = Stack<Int>()intStack.push(1)intStack.push(2)print(intStack.pop