Hallo allerseits! Wir teilen Ihnen eine Übersetzung mit, die speziell für Studenten des Kurses
„iOS Developer. Fortgeschrittenenkurs .
“ Gute Lektüre.
Generische Funktion, generischer Typ und TypeinschränkungenWas sind Generika?
Wenn sie arbeiten, liebst du sie und wenn nicht, hasst du sie!Im wirklichen Leben kennt jeder die Kraft von Generika: morgens aufwachen, entscheiden, was er trinken soll, eine Tasse füllen.
️
Swift ist eine typsichere Sprache. Wann immer wir mit Typen arbeiten, müssen wir sie explizit angeben. Zum Beispiel benötigen wir eine Funktion, die mit mehr als einem Typ funktioniert. Swift hat die Typen
Any
und
AnyObject
, aber sie sollten sorgfältig und nicht immer verwendet werden. Die Verwendung von
Any
und
AnyObject
macht Ihren Code unzuverlässig, da es unmöglich ist, die
AnyObject
während der Kompilierung zu verfolgen. Hier kommen Generika zur Rettung.
Mit generischem Code können Sie wiederverwendbare Funktionen und Datentypen erstellen, die mit jedem Typ arbeiten können, der bestimmte Einschränkungen erfüllt, und gleichzeitig die Typensicherheit während der Kompilierung gewährleisten. Mit diesem Ansatz können Sie Code schreiben, der Doppelarbeit vermeidet und dessen Funktionalität klar und abstrakt ausdrückt. Beispielsweise verwenden Typen wie
Array
,
Set
und
Dictionary
Generika zum Speichern von Elementen.
Angenommen, wir müssen ein Array erstellen, das aus ganzzahligen Werten und Zeichenfolgen besteht. Um dieses Problem zu lösen, werde ich zwei Funktionen erstellen.
let intArray = [1, 2, 3, 4] let stringArray = [a, b, c, d] func printInts(array: [Int]) { print(intArray.map { $0 }) } func printStrings(array: [String]) { print(stringArray.map { $0 }) }
Jetzt muss ich ein Array von Elementen vom Typ float oder ein Array von Benutzerobjekten ausgeben. Wenn wir uns die obigen Funktionen ansehen, werden wir sehen, dass nur der Unterschied im Typ verwendet wird. Anstatt den Code zu duplizieren, können wir daher eine generische Funktion zur Wiederverwendung schreiben.
Die Geschichte der Generika in Swift

Allgemeine Funktionen
Die generische Funktion kann mit jedem universellen Parameter vom Typ
T
Der Typname sagt nichts darüber aus, was
sein soll, aber er sagt, dass beide Arrays vom Typ
, unabhängig davon, was
ist. Der Typ selbst, der anstelle von
soll, wird bei jedem Aufruf der Funktion
print(
_:
)
.
func print<T>(array: [T]) { print(array.map { $0 }) }
Generische Typen oder parametrischer Polymorphismus
Der generische Typ T aus dem obigen Beispiel ist ein Typparameter. Sie können mehrere Typparameter angeben, indem Sie mehrere Typparameternamen in spitze Klammern schreiben, die durch Kommas getrennt sind.
Wenn Sie sich Array und Wörterbuch <Schlüssel, Element> ansehen, werden Sie feststellen, dass sie Typparameter benannt haben, dh Element und Schlüssel, Element, was von der Beziehung zwischen dem Typparameter und dem generischen Typ oder der generischen Funktion spricht, in der er verwendet wird .
Hinweis: Geben Sie Typparametern in einer CamelCase-Notation (z. B.
T
und
TypeParameter
) immer Namen, um
TypeParameter
, dass es sich um einen Namen für den Typ und nicht um einen Wert handelt.
Generische Typen
Hierbei handelt es sich um benutzerdefinierte Klassen, Strukturen und Aufzählungen, die mit jedem Typ arbeiten können, ähnlich wie Arrays und Wörterbücher.
Lassen Sie uns einen Stapel erstellen
import Foundation enum StackError: Error { case Empty(message: String) } public struct Stack { var array: [Int] = [] init(capacity: Int) { array.reserveCapacity(capacity) } public mutating func push(element: Int) { array.append(element) } public mutating func pop() -> Int? { return array.popLast() } public func peek() throws -> Int { guard !isEmpty(), let lastElement = array.last else { throw StackError.Empty(message: "Array is empty") } return lastElement } func isEmpty() -> Bool { return array.isEmpty } } extension Stack: CustomStringConvertible { public var description: String { let elements = array.map{ "\($0)" }.joined(separator: "\n") return elements } } var stack = Stack(capacity: 10) stack.push(element: 1) stack.push(element: 2) print(stack) stack.pop() stack.pop() stack.push(element: 5) stack.push(element: 3) stack.push(element: 4) print(stack)
Jetzt kann dieser Stapel nur ganzzahlige Elemente akzeptieren. Wenn ich Elemente eines anderen Typs speichern muss, muss ich entweder einen anderen Stapel erstellen oder diesen in einen generischen Look konvertieren.
enum StackError: Error { case Empty(message: String) } public struct Stack<T> { var array: [T] = [] init(capacity: Int) { array.reserveCapacity(capacity) } public mutating func push(element: T) { array.append(element) } public mutating func pop() -> T? { return array.popLast() } public func peek() throws -> T { guard !isEmpty(), let lastElement = array.last else { throw StackError.Empty(message: "Array is empty") } return lastElement } func isEmpty() -> Bool { return array.isEmpty } } extension Stack: CustomStringConvertible { public var description: String { let elements = array.map{ "\($0)" }.joined(separator: "\n") return elements } } var stack = Stack<Int>(capacity: 10) stack.push(element: 1) stack.push(element: 2) print(stack) var strigStack = Stack<String>(capacity: 10) strigStack.push(element: "aaina") print(strigStack)
Allgemeine Typbeschränkungen
Da ein Generikum von jedem Typ sein kann, können Sie nicht viel damit anfangen. Manchmal ist es nützlich, Einschränkungen auf Typen anzuwenden, die mit generischen Funktionen oder generischen Typen verwendet werden können. Typbeschränkungen geben an, dass der Typparameter einem bestimmten Protokoll oder einer bestimmten Protokollzusammensetzung entsprechen muss.
Der Swift
Dictionary
Typ legt beispielsweise Einschränkungen für Typen fest, die als Schlüssel für ein Wörterbuch verwendet werden können. Das Wörterbuch erfordert, dass die Schlüssel gehasht werden, um überprüfen zu können, ob sie bereits Werte für einen bestimmten Schlüssel enthalten.
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
Im Wesentlichen haben wir einen Stapel vom Typ T erstellt, aber wir können nicht zwei Stapel vergleichen, da hier die Typen nicht mit
Equatable
. Wir müssen dies ändern, um
Stack<
T:
Equatable
>
.
Wie funktionieren Generika? Schauen wir uns ein Beispiel an.
func min<T: Comparable>(_ x: T, _ y: T) -> T { return y < x ? y : x }
Dem Compiler fehlen zwei Dinge, die zum Erstellen des Funktionscodes erforderlich sind:
- Größen von Variablen vom Typ T;
- Die Adressen der spezifischen Überladung der Funktion <, die zur Laufzeit aufgerufen werden muss.
Immer wenn der Compiler auf einen Wert vom Typ generic stößt, platziert er den Wert in einem Container. Dieser Container hat eine feste Größe zum Speichern von Werten. Falls der Wert zu groß ist, weist Swift ihn dem Heap zu und speichert einen Link dazu im Container.
Der Compiler verwaltet außerdem eine Liste mit einer oder mehreren Zeugen-Tabellen für jeden generischen Parameter: eine Zeugen-Tabelle für Werte sowie eine Zeugen-Tabelle für jedes Typeinschränkungsprotokoll. Zeugen-Tabellen werden verwendet, um Funktionsaufrufe zur Laufzeit dynamisch an gewünschte Implementierungen zu senden.
Das Ende des ersten Teils. Traditionell warten wir auf Ihre Kommentare, Freunde.
Zweiter Teil