• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Swift里泛型

武飞扬头像
悠悠悠哉e
帮助1

目录

泛型解决的问题(不同类型数据进行相同运算的问题?)

泛型函数(没有具体的实际类型,会根据使用传入值的类型推导出来)

类型参数 ( =占位符类型名T ?

命名类型参数 (与函数有关系可命名有意义参数, 无关用字符T,U,V

泛型类型(Stack 栈类型示例)

泛型扩展 (添加Extension和实现,类似OC的Cartegory)

类型约束 (对入参限制为指定类、遵循特定的协议或协议组合

类型约束语法 (  

类型约束实践

关联类型 (associatedType 关键字指定关联类型)

关联类型实践 (保证protocol操作的类型一致性)

扩展现有类型来指定关联类型

给关联类型添加约束

在关联类型约束里使用协议(where 关键字对关联类型约束进行约束)

泛型 Where 语句(遵循协议或固定类型)

具有泛型 Where子句的扩展 (extension里用where对类型扩展和约束

具有泛型 Where子句的关联类型(protocol里泛型使用where约束?)

泛型下标


泛型是 Swift 最强大的特性之一,很多 Swift 标准库是基于泛型代码构建的。实际上,即使你没有意识到,你也一直在语言指南中使用泛型。例如,Swift 的 Array 和 Dictionary 都是泛型集合。你可以创建一个 Int 类型数组,也可创建一个 String 类型数组,甚至可以是任意其他 Swift 类型的数组。

泛型解决的问题(不同类型数据进行相同运算的问题?)

  1.  
    // Int 类型的值交换函数
  2.  
    func swapTwoInts(_ a: inout Int, _ b: inout Int) {
  3.  
    let temporaryA = a
  4.  
    a = b
  5.  
    b = temporaryA
  6.  
    }
  7.  
     
  8.  
    // Sting 类型的值交换函数
  9.  
    func swapTwoStrings(_ a: inout String, _ b: inout String) {
  10.  
    let temporaryA = a
  11.  
    a = b
  12.  
    b = temporaryA
  13.  
    }
  14.  
     
  15.  
    func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
  16.  
    let temporaryA = a
  17.  
    a = b
  18.  
    b = temporaryA
  19.  
    }
学新通

函数体是一样的,唯一的区别是它们接受的参数类型(IntString 和 Double

泛型函数(没有具体的实际类型,会根据使用传入值的类型推导出来)

泛型函数和非泛型函数的另外一个不同之处在于这个泛型函数名(swapTwoValues(_:_:))后面跟着占位类型名(T),并用尖括号括起来(<T>)。这个尖括号告诉 Swift 那个 T 是 swapTwoValues(_:_:) 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 T的实际类型。T 所代表的类型都会由传入的值的类型推断出来。

  1.  
    // 泛型函数 swapTwoValues(_:_:)
  2.  
    func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
  3.  
    let temporaryA = a
  4.  
    a = b
  5.  
    b = temporaryA
  6.  
    }

泛型版本的函数使用占位符类型名(这里叫做 T 。通常使用单个字符来表示,例如 TUV,例如上面演示函数)

swap(_:_:) 函数存在于 Swift 标准库,你可以在你的应用程序中使用它。

类型参数 ( =占位符类型名T ?

上面 swapTwoValues(_:_:) 例子中,占位类型 T 是一个类型参数的例子,类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 <T>)。

可提供多个类型参数,将它们都写在尖括号中,用逗号分开。<T, U>

命名类型参数 (与函数有关系可命名有意义参数, 无关用字符T,U,V

大多情况下,类型参数具有描述下的名称,例如字典 Dictionary<Key, Value> 中的 Key 和 Value 及数组 Array<Element> 中的 Element,这能告诉阅读代码的人这些参数类型与泛型类型或函数之间的关系。

当它们之间没有有意义的关系时,通常使用单个字符来表示,例如 TUV

泛型类型(Stack<Element> 栈类型示例)

  1.  
    示例:栈的泛型实现
  2.  
    struct Stack<Element> {
  3.  
    var items = [Element]()
  4.  
    mutating func push(_ item: Element) {
  5.  
    items.append(item)
  6.  
    }
  7.  
    mutating func pop() -> Element {
  8.  
    return items.removeLast()
  9.  
    }
  10.  
    }

用占位类型参数 Element 这个类型参数包裹在紧随结构体名的一对尖括号里(<Element>)。

使用类似于Array 和 Dictionary

泛型扩展 (添加Extension和实现,类似OC的Cartegory)

原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。

  1.  
    示例:为Stack 扩展 topItem 方法
  2.  
    extension Stack {
  3.  
     
  4.  
    Stack 中已有的类型参数名称 Element,被用在扩展中来表示计算型属性 topItem 的可选类型
  5.  
    var topItem: Element? {
  6.  
    return items.isEmpty ? nil : items[items.count - 1]
  7.  
    }
  8.  
    }
  9.  
     

类型约束 (对入参限制为指定类、遵循特定的协议或协议组合

类型约束指定类型参数必须继承自指定类、遵循特定的协议或协议组合。

例如,Swift 的 Dictionary 类型对字典的键的类型做了些限制。在 字典的描述 中,字典键的类型必须是可哈希(hashable)的

类型约束语法 ( <T: SomeClass, U:SomeProtocol> 

在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束。

  1.  
    func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
  2.  
    // 这里是泛型函数的函数体部分
  3.  
    }

类型约束实践

  1.  
    普通函数找字符串在数组中的索,(后续改为泛型函数)
  2.  
    func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
  3.  
    for (index, value) in array.enumerated() {
  4.  
    if value == valueToFind {
  5.  
    return index
  6.  
    }
  7.  
    }
  8.  
    return nil
  9.  
    }
  10.  
     
  11.  
    //使用
  12.  
    let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
  13.  
    if let foundIndex = findIndex(ofString: "llama", in: strings) {
  14.  
    print("The index of llama is \(foundIndex)")
  15.  
    }
  16.  
    // 打印“The index of llama is 2”
学新通

只能查找字符串在数组中的索引,用处不是很大。不过,你可以用占位类型 T 替换 String 类型来写出具有相同功能的泛型函数 findIndex(_:_:)

  1.  
    泛型函数
  2.  
    func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
  3.  
    for (index, value) in array.enumerated() {
  4.  
    if value == valueToFind {
  5.  
    return index
  6.  
    }
  7.  
    }
  8.  
    return nil
  9.  
    }
  10.  
     
  11.  
    但并不是所有类型都可以使用 == 号进行判断是否相等
  12.  
     

Swift 标准库中定义了一个 Equatable 协议,该协议要求任何遵循该协议的类型必须实现等式符(==)及不等符(!=)遵循 Equatable 协议的类型都可以安全地用于 findIndex(of:in:) 函数

  1.  
    func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
  2.  
    for (index, value) in array.enumerated() {
  3.  
    if value == valueToFind {
  4.  
    return index
  5.  
    }
  6.  
    }
  7.  
    return nil
  8.  
    }
  9.  
     
  10.  
     
  11.  
    let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
  12.  
    // doubleIndex 类型为 Int?,其值为 nil,因为 9.3 不在数组中
  13.  
     
  14.  
    let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
  15.  
    // stringIndex 类型为 Int?,其值为 2
学新通

关联类型 (associatedType 关键字指定关联类型)

定义一个协议时,声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位符名称,其代表的实际类型在协议被遵循时才会被指定。关联类型通过 associatedtype 关键字来指定。

关联类型实践 (保证protocol操作的类型一致性)

  1.  
    protocol Container {
  2.  
    associatedtype Item //关联类型Item
  3.  
     
  4.  
    append(_:) 方法添加一个新元素到容器里(mutating异变方法协议 表示可以在该方法中修改所属实例属性
  5.  
    mutating func append(_ item: Item)
  6.  
    var count: Int { get }
  7.  
    subscript(i: Int) -> Item { get } //subscript定义下标访问
  8.  
    }

该协议定义了一个关联类型 Item。

任何遵从 Container 协议的类型必须能够指定其存储的元素的类型。具体来说,它必须确保添加到容器内的元素以及下标返回的元素类型是正确的。为了定义这些条件,Container 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。Container 协议需要指定任何通过 append(_:) 方法添加到容器中的元素和容器内的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型。

如何保持操作类型的一致性,用到了关联类型 associatedtype

  1.  
    非泛型版本 IntStack 类型,使其遵循 Container 协议
  2.  
    struct IntStack: Container {
  3.  
    // IntStack 的原始实现部分
  4.  
    var items = [Int]()
  5.  
    mutating func push(_ item: Int) {
  6.  
    items.append(item)
  7.  
    }
  8.  
    mutating func pop() -> Int {
  9.  
    return items.removeLast()
  10.  
    }
  11.  
     
  12.  
    // Container 协议的实现部分
  13.  
    typealias Item = Int (可以删除)将Container协议中抽象的 Item类型转换为具体的Int类型
  14.  
    由于 Swift 的类型推断,实际上在 IntStack 的定义中不需要声明 ItemInt
  15.  
     
  16.  
    mutating func append(_ item: Int) {
  17.  
    self.push(item)
  18.  
    }
  19.  
    var count: Int {
  20.  
    return items.count
  21.  
    }
  22.  
    subscript(i: Int) -> Int {
  23.  
    return items[i]
  24.  
    }
  25.  
    }
学新通

也可以让泛型 Stack 结构体遵循 Container 协议:

  1.  
    struct Stack<Element>: Container {
  2.  
    // Stack<Element> 的原始实现部分
  3.  
    var items = [Element]()
  4.  
    mutating func push(_ item: Element) {
  5.  
    items.append(item)
  6.  
    }
  7.  
    mutating func pop() -> Element {
  8.  
    return items.removeLast()
  9.  
    }
  10.  
     
  11.  
    Container 协议的实现部分,Swift 可以据此推断出 Element 的类型即是 Item 的类型
  12.  
    mutating func append(_ item: Element) {
  13.  
    self.push(item)
  14.  
    }
  15.  
    var count: Int {
  16.  
    return items.count
  17.  
    }
  18.  
    subscript(i: Int) -> Element {
  19.  
    return items[i]
  20.  
    }
  21.  
    }
学新通

扩展现有类型来指定关联类型

利用扩展 Extension 让一个已存在的类型遵循一个协议,这包括使用了关联类型协议。

Swift 的 Array 类型已经提供 append(_:) 方法,count 属性,以及带有 Int 索引的下标来检索其元素。这三个功能都符合 Container 协议的要求,也就意味着你只需声明 Array 遵循Container 协议,就可以扩展 Array,使其遵从 Container 协议.(不明白意义为何,用来做泛型的参数使用??)

extension Array: Container {}

给关联类型添加约束

可以在协议里给关联类型添加约束来要求遵循的类型满足约束。例如,下面的代码定义了 Container 协议, 要求关联类型 Item 必须遵循 Equatable 协议:

  1.  
    protocol Container {
  2.  
    associatedtype Item: Equatable //对关联类型的约束
  3.  
    mutating func append(_ item: Item)
  4.  
    var count: Int { get }
  5.  
    subscript(i: Int) -> Item { get }
  6.  
    }

在关联类型约束里使用协议(where 关键字对关联类型约束进行约束)

协议可以作为它自身的要求出现。例如,有一个协议细化了 Container 协议,添加了一个suffix(_:) 方法。suffix(_:) 方法返回容器中从后往前给定数量的元素,并把它们存储在一个 Suffix 类型的实例里。

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

Suffix 是一个关联类型。 Container 的 Item 类型一样。Suffix 拥有两个约束:它必须遵循 SuffixableContainer 协议(就是当前定义的协议),以及它的 Item 类型必须是和容器里的 Item 类型相同Item 的约束是一个 where 分句

  1.  
    Stack 类型的扩展,它遵循了 SuffixableContainer 协议:
  2.  
    extension Stack: SuffixableContainer {
  3.  
    func suffix(_ size: Int) -> Stack {
  4.  
    var result = Stack()
  5.  
    for index in (count-size)..<count {
  6.  
    result.append(self[index])
  7.  
    }
  8.  
    return result
  9.  
    }
  10.  
    // 推断 suffix 结果是Stack。
  11.  
    }
  12.  
    var stackOfInts = Stack<Int>()
  13.  
    stackOfInts.append(10)
  14.  
    stackOfInts.append(20)
  15.  
    stackOfInts.append(30)
  16.  
    let suffix = stackOfInts.suffix(2)
  17.  
    // suffix 包含 20 和 30
学新通

泛型 Where 语句(遵循协议或固定类型)

可以通过定义一个泛型 where 子句来实现。通过泛型 where 子句让关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 where 关键字紧跟在类型参数列表后面来定义 where 子句,where 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。可以在函数体或者类型的大括号之前添加 where 子句

示例 :定义了一个名为 allItemsMatch 的泛型函数,用来检查两个 Container 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 true,否则返回 false

两个 Container 可以不是相同类型的容器,但它们必须拥有相同类型的元素

  1.  
    func allItemsMatch<C1: Container, C2: Container> //符合 Container 协议
  2.  
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
  3.  
    where C1.Item == C2.Item, C1.Item: Equatable {
  4.  
    // Item 类型相同且符合 Equatable协议 。多个约束间用逗号连接
  5.  
     
  6.  
    // 检查两个容器含有相同数量的元素
  7.  
    if someContainer.count != anotherContainer.count {
  8.  
    return false
  9.  
    }
  10.  
     
  11.  
    // 检查每一对元素是否相等
  12.  
    for i in 0..<someContainer.count {
  13.  
    if someContainer[i] != anotherContainer[i] {
  14.  
    return false
  15.  
    }
  16.  
    }
  17.  
     
  18.  
    // 所有元素都匹配,返回 true
  19.  
    return true
  20.  
    }
学新通

具有泛型 Where子句的扩展 (extension里用where对类型扩展和约束

可以使用泛型 where 子句作为扩展的一部分。

下面的示例扩展了泛型 Stack 结构体,添加一个 isTop(_:) 方法。

  1.  
    extension Stack where Element: Equatable {
  2.  
    func isTop(_ item: Element) -> Bool {
  3.  
    guard let topItem = items.last else {
  4.  
    return false
  5.  
    }
  6.  
    return topItem == item
  7.  
    }
  8.  
    }
  9.  
     
  10.  
    如果\不用泛型 where 子句,会有一个问题:在 isTop(_:) 里面使用了 == 运算符,
  11.  
    Stack 的定义没有要求它的元素是符合 Equatable 协议的,所以使用 == 运算符导致编译时错误
  12.  
    使用泛型 where 子句可以为扩展添加新的条件,因此只有当栈中的元素符合 Equatable 协议时,
  13.  
    扩展才会添加 isTop(_:) 方法
  14.  
    (Element里有实现了Equatable协议??)

如果尝试在其元素不符合 Equatable 协议的栈上调用 isTop(_:) 方法,则会收到编译时错误。

并非Element里有实现Equatable协议。

泛型 where 子句要求 Item 遵循协议,但也可以编写一个泛型 where 子句去要求 Item 为特定类型。例

  1.  
    extension Container where Item == Double {
  2.  
    func average() -> Double {
  3.  
    var sum = 0.0
  4.  
    for index in 0..<count {
  5.  
    sum = self[index]
  6.  
    }
  7.  
    return sum / Double(count)
  8.  
    }
  9.  
    }
  10.  
    print([1260.0, 1200.0, 98.6, 37.0].average())
  11.  
    // 打印“648.9”
  12.  
     
  13.  
    示例2
  14.  
    extension Container where Item: Equatable //where 子句去扩展一个协议

具有泛型 Where子句的关联类型(protocol里泛型使用where约束?)

可以在关联类型后面加上具有泛型 where 的字句。

例如,建立一个包含迭代器(Iterator)的容器,就像是标准库中使用的 Sequence 协议那样。

  1.  
    protocol Container {
  2.  
    associatedtype Item
  3.  
    mutating func append(_ item: Item)
  4.  
    var count: Int { get }
  5.  
    subscript(i: Int) -> Item { get }
  6.  
     
  7.  
    associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
  8.  
    func makeIterator() -> Iterator
  9.  
    }

迭代器(Iterator)的泛型 where 子句要求:无论迭代器是什么类型,迭代器中的元素类型,必须和容器项目的类型保持一致。makeIterator() 则提供了容器的迭代器的访问接口。

一个协议继承了另一个协议,你通过在协议声明的时候,包含泛型 where 子句,来添加了一个约束到被继承协议的关联类型。

  1.  
    声明一个 ComparableContainer 协议,它要求所有的 Item 必须是 Comparable
  2.  
    protocol ComparableContainer: Container where Item: Comparable { }

泛型下标

下标可以是泛型,它们能够包含泛型 where 子句。可以在 subscript 后用尖括号来写占位符类型,你还可以在下标代码块花括号前写 where 子句

  1.  
    extension Container {
  2.  
    subscript<Indices: Sequence>(indices: Indices) -> [Item]
  3.  
    where Indices.Iterator.Element == Int {
  4.  
    var result = [Item]()
  5.  
    for index in indices {
  6.  
    result.append(self[index])
  7.  
    }
  8.  
    return result
  9.  
    }
  10.  
    }
  11.  
     
  12.  
    Container 协议的扩展添加了一个下标方法,接收一个索引的集合,返回每一个索引所在的值的数组
  • 在尖括号中的泛型参数 Indices,必须是符合标准库中的 Sequence 协议的类型。
  • 下标使用的单一的参数,indices,必须是 Indices 的实例
  • 泛型 where 子句要求 Sequence(Indices)的迭代器,其所有的元素都是 Int 类型。这样就能确保在序列(Sequence)中的索引和容器(Container)里面的索引类型是一致的

这些约束意味着,传入到 indices 下标,是一个整型的序列。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbkhkf
系列文章
更多 icon
同类精品
更多 icon
继续加载