iOS Swift No.22 - 泛型4

第二十二章 泛型

9. Generic Where Clauses (泛型where子句)

类型约束在上面描述过了 使我们在关联的泛型方法,下标或类型上面定义类型参数的要求。为关联类型定义要求同样也是非常实用的。我们可以通过定义一个泛型where子句的方式来为关联类型定义要求。一个泛型where子句要求关联类型必须遵循某个协议,或者某个类型参数和关联类型必须是相同的。可以通过将where关键字紧跟在类型参数列表后面来定义where子句,where子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的尖括号之前添加where子句。

下面这个例子定义了一个泛型函数allItemMatch,用来检查看两个Container的实例中是否有两个相同顺序的相同物品。该函数返回的是true的布尔值,如果说所有的物品能够匹配成功,反之则返回false

要被用来检查的两个容器可以不是相同的(也可以是相同的),但是它们必须要拥有着相同类型的物品。 该要求是通过一个组合的类型约束和泛型where子句来表达的。

func allItemsMatch<C1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    where C1.Item == C2.Item, C1.Item: Equatable {
        // Check that both containers contain the same number of items.
        if someContainer.count != anotherContainer.count {
            return false
        }
        // Check each pair of items to see if they're equivalent.
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }
        // All items match, so return true.
        return true
}

该函数使用了两个参数一个是someContaineranotherContainer,在这两个参数里面C1是参数someContainer的类型,anotherContainer参数的类型是C2。C1和C2是容器的两个类型参数,只有当函数被调用是才能决定使用哪一个类型参数。

下面是两个函数类型的要求:

  • 参数类型C1必须遵循Container协议 ( 写作 C1: Container )
  • 参数类型C2必须遵循Container协议 ( 写作 C2: Container )
  • C1里面的Item必须和C2里面的Item相同 ( 写作 C1.Item == C2.Item )
  • C1里面的Item必须遵循个Equatable协议 ( 写作 C1.Item: Equatable )

第一个和第二个要求已定义在函数的类型参数列表中了。第三个和第四个要求定义在函数的泛型where子句中了。

这些要求也就意味着:

  • someContainer是类型C1的一个容器
  • anotherContainer是类型C2的一个容器
  • someContainer和anotherContainer容器里包含的物品是相同的
  • someContainer容器可以用非相等操作符 ( != )来查看里面的物品是否相同。

第三个和第四个要求组合在一起,也就意味着可以在anotherContainer容器中用非相等操作符 ( != ) 来检查里面的里面的物品,因为它和someContainer容器中的物品是相同的。

函数allItemsMatch(_:_:)开始检查两个容器中的物品数量是否相同,如果两个容器中物品的数量不相同函数则会返回false

在检查两个容器中物品的数量之后,该函数才会用for-in循环和半开范围运算符(half-open range operator …< )来迭代someContainer里面所有的物品,每一个来自于someContainer中的物品函数都会来检查它是否和anotherContainer中的物品相同。如果两个物品不相同,则两个容器不匹配,函数返回false。如果说该循环完成了 而没有发现不匹配,则说明着两个容器里面的物品完齐全相同,函数返回true,下面是实践中的函数allItemsMatch(_:_:)

var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")

var arrayOfStrings = ["uno", "dos", "tres"]

if allItemsMatch(stackOfStrings, arrayOfStrings) {
    print("All items match.")
} else {
    print("Not all items match.")
}
// 输出:All items match.

上面这个例子创建了存储String值的一个Stack实例,并且在栈添加了三个String,该例同样也创建了一个Array实例,并且初始化三个和栈里面的值相同的三个数组值。即使栈和数组中的值是不同类型的,但他们同时遵循着相同的协议Container,并且包含着相同类型的值。因此我们可以已着两个容器作为参数来调用allItemsMatch(_:_:)函数,上面的例子里面看出函数已经报告过了这两个容器里面的物品是匹配的。

扫描二维码关注公众号,回复: 11591809 查看本文章

10. Extensions with a Generic Where Clause (泛型where子句的扩展)

我们可以用泛型where子句作为扩展定义的一部分,下面这个例子是在泛型Stack结构体上的一个扩展,并且添加了一个isTop(_:)方法

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

这个新的isTop(_:)方法第一次检查栈是否为空白的栈,然后对比比较栈最上部的物品,如果我们没有用泛型where子句这样做,可能会出问题,该isTop(_:)方法的实现使用的是一个(==)相等操作符,但Stack的定义并没有要求物品为相同的,所以使用相等操作符会出现编译错误(complie-time error)。使用泛型where子句使我们给扩展添加一个新的要求。所以在扩展中添加isTop(_:)方法的时候,只有当这个栈里面的物品是完全相同的情况下。下面是该方法在实践中的表达。

if stackOfStrings.isTop("tres") {
    print("Top element is tres.")
} else {
    print("Top element is something else.")
}
// 输出:Top element is tres.

如果当我们调用isTop(_:)方法的时候,而栈里面的物品不相同,将会出现出现编译时的错误

struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue)  // complie-time Error 

所以我们要使用在协议(Equatable)的扩展中使用泛型where子句,下面这个例子是在前面例子的基础中确定了扩展Container协议并且添加了一个startsWith(_:)方法。

// 扩展在泛型where子句的Container协议
extension Container where Item: Equatable {
    func startsWith(_ item: Item) -> Bool {
        return count >= 1 && self[0] == item
    }
}

这个startsWith(_:)方法第一次确保了容器中至少有一个物品,然后再检查容器中的物品和给出的物品是否匹配。该新方法然后才能应用在任何遵循Container协议的任何类型。包括上面使用的栈和数组。只要是容器里面的物品相同的任何类型。

// 在数组用该方法检查数组内是否含有物品42
if [9, 9, 9].startsWith(42) {
    print("Starts with 42.")
} else {
    print("Starts with something else.")
}
// 输出:Starts with something else.

上例中的泛型where子句请求Item来遵行某个协议,但是我们同样也可以把请求Item的泛型where子句当作一个特殊的类型。

extension Container where Item == Double {
    func average() -> Double {
        var sum = 0.0
        for index in 0..<count {
            sum += self[index]
        }
        return sum / Double(count)
    }
}
print([1260.0, 1200.0, 98.6, 37.0].average())
// Prints "648.9"

该例添加一个average()方法给Item是Double的容器,在容器中迭代并且把这些物品加起来。然后除以容器中物品的总数,得到平均值。隐匿的将Int转换成Double的浮点数。同样也可以在泛型where子句中包括多个请求来当作扩展的一部分。就像在其他地方写泛型where子句那样,用逗号将每一个请求隔开就行了。

猜你喜欢

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