Generic (generische Programmierung) — ist ein Swift-Mechanismus, der das Schreiben von Typen und Funktionen mit einem parametrisierten Typ
Das Wichtigste
Generic (generischer Typ) — ist ein syntaktisches Konstrukt in Swift, das das Schreiben von flexiblem, wiederverwendbarem Code mit Typ-Parametrisierung ermöglicht. Anstelle eines konkreten Typs wird ein Platzhalter in spitzen Klammern geschrieben — normalerweise
Ohne Generics müsste man separate Funktionen für jeden Typ schreiben: swapInts, swapStrings, swapDates. Eine generische swap-Funktion ersetzt alle drei Varianten mit einer einzigen Deklaration. Dies reduziert Duplizierung, verbessert die Lesbarkeit und verringert die Wahrscheinlichkeit von Fehlern beim Kopieren von Code.
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 10
var y = 20
swapValues(&x, &y)
Der Swift-Compiler generiert spezialisierten Code für jede konkrete Verwendung eines Generics. Das bedeutet, dass die generische Funktion swapValues
Generische Funktionen sind Funktionen, die einen oder mehrere Typparameter akzeptieren. Typparameter werden nach dem Funktionsnamen in spitzen Klammern angegeben:
Eine Funktion kann mehrere generische Parameter haben. Beispielsweise nimmt die Funktion findKey
func findKey<K: Hashable, V>(
for value: V,
in dictionary: [K: V]
) -> K? where V: Equatable {
for (key, dictValue) in dictionary {
if dictValue == value {
return key
}
}
return nil
}
Die Einschränkungen K: Hashable und where V: Equatable stellen sicher, dass findKey nur mit einem Dictionary aufgerufen werden kann, dessen Schlüssel hashbar und dessen Werte vergleichbar sind. Solche Einschränkungen sind keine Bürokratie, sondern eine Notwendigkeit: Ohne Hashable kann man keine Schlüsselsuche indizieren, ohne Equatable kann man value nicht mit dictValue vergleichen.
where — ist ein Swift-Schlüsselwort, das zusätzliche Einschränkungen für generische Parameter festlegt. Im Gegensatz zur einfachen Angabe eines Protokolls in spitzen Klammern (
Die where-Klausel wird nach der Funktions- oder Typsignatur vor der öffnenden geschweiften Klammer geschrieben. In where kann man angeben, dass T: Comparable & Hashable (gleichzeitig zwei Protokolle erfüllt) oder dass T.U == Int (ein Associated Type konkretisiert).
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
}
extension Container where Item: Comparable {
func isSorted() -> Bool {
// Implementation available only if Item: Comparable
return true
}
}
Die Erweiterung mit where fügt Methoden nur für die Typen hinzu, die die Bedingung erfüllen. Container
Associated Types (assoziierte Typen) — sind eine Möglichkeit, ein Protokoll generisch zu machen, ohne zur Deklarationszeit einen konkreten Typ anzugeben. Anstelle von
Mit where kann man Beziehungen zwischen Associated Types verschiedener Protokolle herstellen. Beispielsweise kann man fordern, dass das Item eines Protokolls mit Iterator.Element eines anderen übereinstimmt. Dies gewährleistet Typkompatibilität auf Compiler-Ebene.
protocol StackProtocol {
associatedtype Element
mutating func push(_ item: Element)
mutating func pop() -> Element?
}
struct IntStack: StackProtocol {
typealias Element = Int
private var items: [Int] = []
mutating func push(_ item: Int) { items.append(item) }
mutating func pop() -> Int? { items.popLast() }
}
IntStack konkretisiert Element als Int mithilfe von typealias. Der Compiler prüft, ob alle Protokollanforderungen mit diesem Typ erfüllt werden. Ohne Associated Types müsste man StackProtocol
Generische Erweiterungen ermöglichen das Hinzufügen von Methoden zu einem generischen Typ mit zusätzlichen Einschränkungen. Dies ist das Muster der „bedingten Konformität“, bei dem ein Typ nur unter bestimmten Bedingungen Funktionalität erhält. Subskripte können ebenfalls generisch sein.
Ein Subskript kann generisch sein: Es akzeptiert generische Parameter in spitzen Klammern. Dies ist nützlich für den sicheren Zugriff auf Sammlungen mit beliebigen Indizes, wobei der Index verschiedene Typen haben kann.
extension Array where Element: Numeric {
func sum() -> Element {
return reduce(0, +)
}
}
extension Array {
subscript<Indices: Sequence>(indices: Indices) -> [Element]
where Indices.Element == Int {
return indices.map { self[$0] }
}
}
Diese Konstruktion bietet eine leistungsstarke Komposition: Die Methode sum erscheint nur bei numerischen Arrays, und das Subskript mit beliebigen Indizes funktioniert mit jeder Sequenz von ganzzahligen Indizes. Der Compiler verwaltet die Sichtbarkeit dieser Mitglieder basierend auf den where-Bedingungen.
Fehler bei der Verwendung von Generics hängen oft mit falschen Einschränkungen oder dem Versuch zusammen, Generics dort zu verwenden, wo ein konkreter Typ benötigt wird. Betrachten wir drei häufige Szenarien, auf die Entwickler stoßen.
Das Hinzufügen generischer Parameter, wo ein konkreter Typ ausreicht, ist ein Antipattern. Wenn eine Funktion immer mit String arbeitet, muss man sie nicht zu
Das Aufrufen einer Methode auf einem generischen Parameter, ohne anzugeben, dass die Methode existiert, ist ein Fehler.
// ❌ Error: value has no count method
func countElements<T>>(value: T) -> Int { value.count }
// ✅ Works: T: Collection guarantees count exists
func countElements<T: Collection>(value: T) -> Int { value.count }
Der Fehler ist klar: Der generische
Häufig gestellte Fragen
Generic ist eine Möglichkeit, Code zu schreiben, der mit jedem Typ funktioniert, ohne dieselbe Logik für Int, String und andere Typen zu duplizieren. Anstelle eines konkreten Typs wird ein Platzhalter
Generic bewahrt Informationen über den konkreten Typ zur Compile-Zeit, sodass der Compiler Typen überprüfen und optimierten Code generieren kann. Any ist Typenlöschung: Jeder Wert kann übergeben werden, aber der Typ geht verloren, und ein Cast (as?) ist erforderlich.
Verwenden Sie die Syntax T: SomeProtocol in spitzen Klammern oder eine separate where-Klausel where T: SomeProtocol. Einschränkungen stellen sicher, dass der generische Parameter bestimmte Methoden und Eigenschaften unterstützt.
Associated Type (associatedtype) ist ein Platzhalter für einen Typ innerhalb eines Protokolls. Das Protokoll gibt keinen konkreten Typ an, sondern deklariert einen associatedtype, der in der Implementierung konkretisiert wird: Array hat Element, Dictionary hat Key und Value.
Swift kompiliert generischen Code in spezialisierte Versionen für jeden konkreten Typ. In der Praxis ist eine generische Funktion für Int nicht langsamer als eine handgeschriebene Funktion für Int. Es gibt keinen Abstraktions-Overhead.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch