Swift中泛型的力量。 第二部分

朋友们,下午好。 特别是针对“ iOS开发人员”课程的学生 “高级课程”,我们准备了文章“ Swift中泛型的力量”的第二部分的翻译。





相关类型,where子句,下标等

在文章“ Swift中泛型的力量。 第1部分介绍了通用功能,通用类型和类型限制。 如果您是初学者,建议您先阅读第一部分,以更好地理解。

定义协议时,有时将一个或多个相关类型声明为该定义的一部分很有用。 关联的类型为用作协议一部分的类型指定存根名称。 在使用协议之前,不会指定用于此相关类型的实际类型。 关联类型是使用associatedtype关键字声明的。

我们可以为在第一部分中创建的堆栈定义协议。

 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 } } 

Stackable协议定义了任何堆栈应提供的必要功能。

符合Stackable协议的任何类型都必须能够指定其存储的值的类型。 应该确保仅将正确类型的元素添加到堆栈中,并且应该清楚其下标返回哪种类型的元素。

让我们根据协议更改堆栈:

 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) 


扩展现有类型以指示相关类型


您可以扩展现有类型以符合协议。

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

将约束添加到关联的类型:


您可以在协议中为关联类型添加限制,以确保关联类型符合这些限制。
让我们更改Stackable协议。

 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 } } 

现在,堆栈元素的类型应匹配Equatable ,否则将发生编译时错误。

递归协议限制:


该协议可能是其自身要求的一部分。

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

Suffix有两个限制:它必须符合SuffixableContainer协议(在此定义协议),并且其Item类型必须与容器的Item类型匹配。

Swift标准库中的Protocol Sequence有一个很好的例子Protocol Sequence说明这一主题。

关于递归协议限制的建议: https : //github.com/apple/swift-evolution/blob/master/proposals/0157-recursive-protocol-constraints.md

通用类型扩展:


扩展通用类型时,在定义扩展时不会描述类型参数的列表。 相反,扩展主体中提供了来自源定义的类型参数的列表,并且源类型的参数名称用于引用源定义的类型参数。


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

通用where子句


对于相关类型,定义需求很有用。 通用的where子句描述了需求。 通用的where子句允许您要求关联的类型符合特定协议,或者某些类型参数和相关类型相同。 通用where子句以where关键字开头,后跟相关类型的约束或类型与相关类型之间的相等关系。 Generic where子句直接写在类型或函数体的大括号之前。

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

具有一般条件的扩展,其中


您可以将通用where子句用作扩展的一部分。 下面的示例通过添加isTop (_ :)方法扩展了先前示例的总体Stack结构。

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

该扩展仅在堆栈上的项目支持Equatable时才添加isTop (_ :)方法。 您还可以将泛型where子句与协议扩展一起使用。 您可以where使用逗号将它们添加到where

将类型与Generic子句相关联,其中:


您可以在关联类型中包含通用where子句。

 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 } 

对于从另一个协议继承的协议,可以向继承的绑定类型添加约束,包括协议声明中的常规where子句。 例如,以下代码声明了ComparableContainer协议,该协议要求Item支持Comparable

 protocol ComparableContainer: Container where Item: Comparable { } 

通用类型别名:


类型别名可以具有公共参数。 它仍将保留为别名(即不会引入新类型)。

 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) 

在这种机制中,不能使用对类型参数的附加限制。
这样的代码将不起作用:

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

通用上标


下标可以使用通用机制,并包括通用where子句。 您将类型名称写在下subscript之后的尖括号中,并将通用where子句写在下标主体的大括号之前。

 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 } } 

通用专业化


通用专业化意味着编译器会为特定参数类型(例如Int)克隆通用类型或函数(例如Stack < T > 。 然后可以专门针对Int优化此专用功能,同时将删除所有多余的功能。 在编译时用类型实参替换类型参数的过程称为专门化

通过专门针对这些类型的泛型函数,我们可以消除虚拟调度,内联调用(在必要时)的成本,并消除泛型系统的开销。

操作员超载


默认情况下,泛型类型不适用于运算符,为此,您需要一个协议。

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


关于泛型的有趣的事情


为什么不能为泛型定义静态存储属性?

这将需要为每个单独的通用(T)专业化单独存储属性。

深入研究的资源:


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


仅此而已。 在课程上见。

Source: https://habr.com/ru/post/zh-CN463789/


All Articles