iOS Swift No.22 - 泛型3

第二十二章 泛型

8. Associated Type (关联类型)

当我们在定义协议的时候,定义一个或者多个关联类型作为协议定义的一部分是非常有用的,关联类型会给出一个占位符名称,该占位符名称会作为协议定义的一部分,知道协议被采用的时候,实际为关联类型使用的类型才会被使用。关联类型可以用关键字associatedtype来加以说明。

8.1 Associated Types in Action (实践中的关联类型)

下面是一个协议Container,它声明了一个关联类型Item

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

该协议Container定义了三个任何容器都要提供的功能。

  1. 它必须要用append(_ : ) 方法来给容器里添加新的物品(item)。
  2. 它必须要能够读取容器中的物品的总数,并且能返回一个Int的值
  3. 它必须要能够用下标语法和Int的索引值来检索容器中的每一个物品。

该协议并没有指明容器中的物品时怎样被存储的和什么类型的物品可以存储在容器中。该协议只是指明了三个任何遵循Container协议的类型必须提供的功能。遵循协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。

任何遵循该Container协议的类型都必须指明要需要存储值的类型。特别是,它必须确保只有正确类型的物品可以被添加到容器里,并且它还要通过用下标返回来明确物品的类型。

为了定义这三个条件,Container协议需要在不知道容器中元素的具体类型的情况下引用这种类型, 协议需要指定任何通过 append(_:) 方法添加到容器中的元素和容器中的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型。

为了实现该目的,协议Container通过定义一个关联类型Item 可以将该关联类型写成associatedtype Item,该协议并没有定义什么是Item,这个信息将留给遵循协议的类型来提供。尽管如此,Item别名还是提供了可以引用容器中元素的方法,并且定义了用来使用append(_:)方法和下标的一个类型,从而来确保期望Container来执行的某些行为。

下面是一个非泛型版本IntStack的类型,采用和遵循来这个Container协议。

	// adopt and conform container protocol
struct IntStack: Container {
    // 原IntStack的实现
    var items = [Int]() // an array of intger value 
    mutating func push(_ item: Int) {
        items.append(item)
    }
    mutating func pop() -> Int {
        return items.removeLast()
    }
    // 遵循协议Container,该协议有一个关联类型叫Item.
    typealias Item = Int // 将抽象类型Item转换成具体类型Int
    mutating func append(_ item: Int) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Int {
        return items[i]
    }
}

类型IntStack实现了三个Container协议的要求,每一个case都是满足了IntStack现有的功能。此外,IntStack指明了Container的实现的要求时,即Item是Int类型。typealias Item = Int的定义是将Container协议中的抽象类型Item转换成具体的类型Int

swift中的类型推断,所以我们不需要声明将Int中的具体Item作为IntStack定义的一部分,因为IntStack遵循了协议Container的所有要求。Swift只需通过append(_:)方法的item参数类型和下标返回值的类型,就可以推断出ItemType的具体类型。事实上,如果你在上面的代码中删除了typealias ItemType = Int这一 行,一切仍旧可以正常工作,因为Swift清楚地知道 ItemType应该是哪种类型。

泛型类型Stack采用和遵循Container协议

struct Stack<Element>: Container {
	// 原Stack<Element>的实现
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

这个时候类型参数Element被用作为append(_:)方法的item参数,和下标的返回类型。因此swift可以推断出Element是为某个特定容器来使用的Item的特定类型。

8.2 Extending an Existing Type to Specify an Associated Type (扩展已有类型来指明一个关联类型)

我们可以扩展一个现有的类型来添加遵循某个协议。详情见通过协议3章节中扩展添加协议一致性。其中包括了含有关联类型的协议。

swift中的Array类型已经提供了append(_:)方法,count属性,和一个接受Int为索引值来检索元素的下标。这三个功能都符合协议Container的要求。可以通过声明Array采用该协议。用一个空白的扩展来做声明和采用协议。

// 创建一个空白的扩展,扩展一个Array来采用Container协议。
extension Array: Container {}

Array的现有append(_:)方法和下标可以使Swift为使用Item推断出合适的类型。就像上面那个泛型类型Stack那样。定义了这个扩展之后 我们可以把任何一个Array当作Container来使用。

8.3 Adding Constraints to an Associated Type (为关联类型添加约束)

我们可以在一个协议里面为关联类型添加一个类型约束,从而来要求遵循的类型必须要能满足这些约束。举个例子,下面这些代码定义了Container的一种版本,来要求容器中的所有物品必须是相同的。来遵行这个版本的Container,容器中的Item类型不得不遵循Equatable协议

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

8.4 Using a Protocol in Its Associated Type’s Containts (在关联类型的约束中使用协议)

一个协议可以作为协议本身要求的一部分出现,举个例子,下面是一个重新定义Container的协议,添加了suffix(_:)方法要求,该方法从容器的必读返回一定数量的元素,并把这些元素存储在Suffix类型的实例中。

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

在上面的协议中 Suffix是一个关联类型,就像上面Container中的Item类型那样。Suffix有两个约束:它必须遵循SuffixableContainer协议(当前该协议已定义),并且Item类型要和Container中的Item类型保持一致。该协议中的Item约束是一个泛型的where字句,将会在下段有详细描述。

下面是一个来自于上面给协议SuffixableContainer添加一致性的泛型类型Stack类型的扩展。

extension Stack: SuffixableContainer {
    func suffix(_ size: Int) -> Stack {
        var result = Stack()
        for index in (count-size)..<count {
            result.append(self[index])
        }
        return result
    }
    // Inferred that Suffix is Stack.
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix contains 20 and 30

在上面这个案例中,Stack的关联类型Suffix还是Stack,所以呢在Stack上的suffix操作返回的依然是另一个Stack。一个遵循SuffixableContainer协议的类型可以有一个不同于自身的Suffix类型。举个例子,下面是一个给非泛型类型IntStack的扩展,不过该扩展添加了SuffixableContainer的一致性。使用Stack<Int> 作为替代IntStack的suffix类型。

extension IntStack: SuffixableContainer {
    func suffix(_ size: Int) -> Stack<Int> {
        var result = Stack<Int>()
        for index in (count-size)..<count {
            result.append(self[index])
        }
        return result
    }
    // Inferred that Suffix is Stack<Int>.
}

猜你喜欢

转载自blog.csdn.net/weixin_45026183/article/details/107244442