Die Kraft der Generika in Swift. Teil 2

Guten Tag, Freunde. Speziell für Studenten des Kurses "iOS Developer. Fortgeschrittenenkurs “haben wir eine Übersetzung des zweiten Teils des Artikels„ Die Kraft der Generika in Swift “vorbereitet.





Verwandte Typen, wobei Klauseln, Indizes und mehr ...

In dem Artikel „Die Kraft der Generika in Swift. Teil 1 “ beschrieb generische Funktionen, generische Typen und Typbeschränkungen. Wenn Sie ein Anfänger sind, würde ich empfehlen, dass Sie zuerst den ersten Teil lesen, um ein besseres Verständnis zu erhalten.

Bei der Definition eines Protokolls ist es manchmal hilfreich, einen oder mehrere verwandte Typen als Teil der Definition zu deklarieren. Der zugehörige Typ gibt den Stubnamen für den Typ an, der als Teil des Protokolls verwendet wird. Der tatsächliche Typ, der für diesen verwandten Typ verwendet wird, wird erst angegeben, wenn das Protokoll verwendet wird. Zugehörige Typen werden mit dem Schlüsselwort "Associated Type" deklariert.

Wir können das Protokoll für den Stapel definieren, den wir im ersten Teil erstellt haben .

 protocol Stackable { associatedtype Element mutating func push(element: Element) mutating func pop() -> Element? func peek() throws -> Element func isEmpty() -> Bool func count() -> Int subscript(i: Int) -> Element { get } } 

Das Stackable Protokoll definiert die erforderlichen Funktionen, die jeder Stapel bereitstellen sollte.

Jeder Typ, der dem Stackable entspricht, muss in der Lage sein, den Stackable anzugeben, den er speichert. Es sollte sichergestellt werden, dass nur Elemente des richtigen Typs zum Stapel hinzugefügt werden, und es sollte klar sein, welcher Elementtyp von seinem Index zurückgegeben wird.

Lassen Sie uns unseren Stack gemäß dem Protokoll ändern:

 enum StackError: Error { case Empty(message: String) } protocol Stackable { associatedtype Element mutating func push(element: Element) mutating func pop() -> Element? func peek() throws -> Element func isEmpty() -> Bool func count() -> Int subscript(i: Int) -> Element { get } } public struct Stack<T>: Stackable { public typealias Element = 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 } func count() -> Int { return array.count } } extension Stack: Collection { public func makeIterator() -> AnyIterator<T> { var curr = self return AnyIterator { curr.pop() } } public subscript(i: Int) -> T { return array[i] } public var startIndex: Int { return array.startIndex } public var endIndex: Int { return array.endIndex } public func index(after i: Int) -> Int { return array.index(after: i) } } extension Stack: CustomStringConvertible { public var description: String { let header = "***Stack Operations start*** " let footer = " ***Stack Operation end***" let elements = array.map{ "\($0)" }.joined(separator: "\n") return header + elements + footer } } var stack = Stack<Int>(capacity: 10) stack.push(element: 1) stack.pop() stack.push(element: 3) stack.push(element: 4) print(stack) 


Erweitern eines vorhandenen Typs, um einen verwandten Typ anzugeben


Sie können einen vorhandenen Typ erweitern, um dem Protokoll zu entsprechen.

 protocol Container { associatedtype Item mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } } extension Array: Container {} 

Hinzufügen von Einschränkungen zum zugeordneten Typ:


Sie können dem zugeordneten Typ im Protokoll Einschränkungen hinzufügen, um sicherzustellen, dass verwandte Typen diese Einschränkungen erfüllen.
Lassen Sie uns das Stackable Protokoll ändern.

 protocol Stackable { associatedtype Element: Equatable mutating func push(element: Element) mutating func pop() -> Element? func peek() throws -> Element func isEmpty() -> Bool func count() -> Int subscript(i: Int) -> Element { get } } 

Jetzt sollte der Typ des Equatable mit Equatable , da sonst ein Fehler bei der Kompilierung auftritt.

Rekursive Protokollbeschränkungen:


Das Protokoll kann Teil seiner eigenen Anforderungen sein.

 protocol SuffixableContainer: Container { associatedtype Suffix: SuffixableContainer where Suffix.Item == Item func suffix(_ size: Int) -> Suffix } 

Suffix weist zwei Einschränkungen auf: Es muss dem SuffixableContainer Protokoll entsprechen (das Protokoll wird hier definiert), und sein SuffixableContainer muss mit dem SuffixableContainer des Containers übereinstimmen.

In der Swift-Standardbibliothek in der Protocol Sequence gibt es ein gutes Beispiel Protocol Sequence um dieses Thema Protocol Sequence veranschaulichen.

Vorschlag zu den Einschränkungen des rekursiven Protokolls: https://github.com/apple/swift-evolution/blob/master/proposals/0157-recursive-protocol-constraints.md

Generische Typerweiterungen:


Wenn Sie einen generischen Typ erweitern, beschreiben Sie beim Definieren einer Erweiterung keine Liste von Typparametern. Stattdessen ist im Erweiterungshauptteil eine Liste von Typparametern aus der Quelldefinition verfügbar, und Parameternamen des Quelltyps werden verwendet, um auf Typparameter aus der Quelldefinition zu verweisen.


 extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1] } } 

Generische where-Klausel


Für verwandte Typen ist es hilfreich, Anforderungen zu definieren. Die Anforderung wird durch die generische where-Klausel beschrieben . Mit der generischen where Klausel können Sie festlegen, dass der zugeordnete Typ einem bestimmten Protokoll entspricht oder dass bestimmte Typparameter und verwandte Typen identisch sind. Die generische where Klausel beginnt mit dem Schlüsselwort where , gefolgt von Einschränkungen für verwandte Typen oder der Gleichheitsbeziehung zwischen Typen und verwandten Typen. Die generische where Klausel wird direkt vor der öffnenden Klammer des Typs oder Funktionskörpers geschrieben.

 func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.Item == C2.Item, C1.Item: Equatable { } 

Erweiterungen mit generischen Bedingungen wo


Sie können die generische where Klausel als Teil der Erweiterung verwenden. Das folgende Beispiel erweitert die Gesamtstapelstruktur der vorherigen Beispiele durch Hinzufügen der Methode isTop (_ :) .

 extension Stack where Element: Equatable { func isTop(_ item: Element) -> Bool { guard let topItem = items.last else { return false } return topItem == item } } 

Die Erweiterung fügt die isTop (_ :) -Methode nur hinzu, wenn Elemente auf dem Stapel Equatable unterstützen. Sie können auch die generische where Klausel mit Protokollerweiterungen verwenden. Sie können der where mehrere Anforderungen hinzufügen, indem Sie sie durch ein Komma trennen.

Zugehörige Typen mit der generischen Klausel, wobei:


Sie können die generische where Klausel in den zugehörigen Typ aufnehmen.

 protocol Container { associatedtype Item mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } associatedtype Iterator: IteratorProtocol where Iterator.Element == Item func makeIterator() -> Iterator } 

Für ein Protokoll, das von einem anderen Protokoll erbt, fügen Sie dem geerbten gebundenen Typ eine Einschränkung hinzu, einschließlich der generischen where Klausel in der Protokolldeklaration. Der folgende Code deklariert beispielsweise ein ComparableContainer Protokoll, für das Item unterstützen muss:

 protocol ComparableContainer: Container where Item: Comparable { } 

Generische Typ-Aliase:


Typalias kann gemeinsame Parameter haben. Es bleibt weiterhin ein Alias ​​(dh es wird kein neuer Typ eingeführt).

 typealias StringDictionary<Value> = Dictionary<String, Value> var d1 = StringDictionary<Int>() var d2: Dictionary<String, Int> = d1 // okay: d1 and d2 have the same type, Dictionary<String, Int> typealias DictionaryOfStrings<T : Hashable> = Dictionary<T, String> typealias IntFunction<T> = (T) -> Int typealias Vec3<T> = (T, T, T) typealias BackwardTriple<T1,T2,T3> = (T3, T2, T1) 

In diesem Mechanismus können keine zusätzlichen Einschränkungen für die Typparameter verwendet werden.
Ein solcher Code funktioniert nicht:

 typealias ComparableArray<T where T : Comparable> = Array<T> 

Generische hochgestellte Zeichen


Indizes können den generischen Mechanismus verwenden und die generische where Klausel enthalten. Sie schreiben den Typnamen in spitzen Klammern nach dem subscript und schreiben die generische where Klausel unmittelbar vor der öffnenden Klammer des Indexkörpers.

 extension Container { subscript<Indices: Sequence>(indices: Indices) -> [Item] where Indices.Iterator.Element == Int { var result = [Item]() for index in indices { result.append(self[index]) } return result } } 

Generische Spezialisierung


Generische Spezialisierung bedeutet, dass der Compiler einen generischen Typ oder eine generische Funktion wie Stack < T > für einen bestimmten Parametertyp wie Int klont. Diese spezielle Funktion kann dann speziell für Int optimiert werden, während alles Überflüssige entfernt wird. Das Ersetzen von Typparametern durch Typargumente zur Kompilierungszeit wird als Spezialisierung bezeichnet .

Durch die Spezialisierung der generischen Funktion für diese Typen können wir die Kosten für virtuelles Dispatching, Inline-Anrufe bei Bedarf und den Overhead des generischen Systems eliminieren.

Bedienerüberlastung


Generische Typen funktionieren standardmäßig nicht mit Operatoren. Dazu benötigen Sie ein Protokoll.

 func ==<T: Equatable>(lhs: Matrix<T>, rhs: Matrix<T>) -> Bool { return lhs.array == rhs.array } 


Eine interessante Sache über Generika


Warum können Sie keine statisch gespeicherte Eigenschaft für einen generischen Typ definieren?

Dies erfordert eine separate Speicherung der Eigenschaften für jede einzelne generische (T) Spezialisierung.

Ressourcen für eingehende Studien:


https://github.com/apple/swift/blob/master/docs/Generics.rst
https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md
https://developer.apple.com/videos/play/wwdc2018/406/
https://www.youtube.com/watch?v=ctS8FzqcRug
https://medium.com/@vhart/protocols-generics-and-existential-containers-wait-what-e2e698262ab1


Das ist alles. Wir sehen uns auf dem Kurs .

Source: https://habr.com/ru/post/de463789/


All Articles