Generics (泛型)

作者: 金旭峰 | 来源:发表于2017-01-22 10:12 被阅读24次

    Generic codeenables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.

    泛型代码让你能够根据自定义的需求,编写出适用于任意类型、灵活可重用的函数及类型。它能让你避免代码的重复,用一种清晰和抽象的方式来表达代码的意图。

    Generics are one of the most powerful features of Swift, and much of the Swift standard library is built with generic code. In fact, you’ve been using generics throughout theLanguage Guide, even if you didn’t realize it. For example, Swift’sArrayandDictionarytypes are both generic collections. You can create an array that holdsIntvalues, or an array that holdsStringvalues, or indeed an array for any other type that can be created in Swift. Similarly, you can create a dictionary to store values of any specified type, and there are no limitations on what that type can be.

    泛型是 Swift 最强大的特性之一,许多 Swift 标准库是通过泛型代码构建的。事实上,泛型的使用贯穿了整本语言手册,只是你可能没有发现而已。例如,Swift 的Array和Dictionary都是泛型集合。你可以创建一个Int数组,也可创建一个String数组,甚至可以是任意其他 Swift 类型的数组。同样的,你也可以创建存储任意指定类型的字典。

    The Problem That Generics Solve (泛型所解决的问题)

    Here’s a standard, non-generic function calledswapTwoInts(_:_:), which swaps twoIntvalues:

    下面是一个标准的非泛型函数swapTwoInts(_:_:),用来交换两个Int值:

    func swapTwoInts(_a:inoutInt,_b:inoutInt) {

        let temporaryA=a

        a=b

        b=temporaryA

    }

    This function makes use of in-out parameters to swap the values ofaandb, as described inIn-Out Parameters.

    这个函数使用输入输出参数(inout)来交换a和b的值,请参考输入输出参数

    TheswapTwoInts(_:_:)function swaps the original value ofbintoa, and the original value ofaintob. You can call this function to swap the values in twoIntvariables:

    swapTwoInts(_:_:)函数交换b的原始值到a,并交换a的原始值到b。你可以调用这个函数交换两个Int变量的值:

    var someInt=3

    var anotherInt=107

    swapTwoInts(&someInt, &anotherInt)

    print("someInt is now\(someInt), and anotherInt is now\(anotherInt)")

    // Prints "someInt is now 107, and anotherInt is now 3"

    TheswapTwoInts(_:_:)function is useful, but it can only be used withIntvalues. If you want to swap twoStringvalues, or twoDoublevalues, you have to write more functions, such as theswapTwoStrings(_:_:)andswapTwoDoubles(_:_:)functions shown below:

    诚然,swapTwoInts(_:_:)函数挺有用,但是它只能交换Int值,如果你想要交换两个String值或者Double值,就不得不写更多的函数,例如swapTwoStrings(_:_:)和swapTwoDoubles(_:_:),如下所示:

    func swapTwoStrings(_a:inoutString,_b:inoutString) {

      let temporaryA=a

      a=b

      b=temporaryA

    }

    func swapTwoDoubles(_a:inoutDouble,_b:inoutDouble) {

      let temporaryA=a

      a=b

      b=temporaryA

    }

    You may have noticed that the bodies of theswapTwoInts(_:_:),swapTwoStrings(_:_:), andswapTwoDoubles(_:_:)functions are identical. The only difference is the type of the values that they accept (Int,String, andDouble).

    你可能注意到swapTwoInts(_:_:)、swapTwoStrings(_:_:)和swapTwoDoubles(_:_:)的函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是Int、String和Double。

    It would be much more useful, and considerably more flexible, to write a single function that could swap two values ofanytype. Generic code enables you to write such a function. (A generic version of these functions is defined below.)

    在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。)

    NOTE

    In all three functions, it is important that the types ofaandbare defined to be the same as each other. Ifaandbwere not of the same type, it would not be possible to swap their values. Swift is a type-safe language, and does not allow (for example) a variable of typeStringand a variable of typeDoubleto swap values with each other. Attempting to do so would be reported as a compile-time error.

    在上面三个函数中,a和b类型相同。如果a和b类型不同,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个String类型的变量和一个Double类型的变量互换值。试图这样做将导致编译错误。

    Generic Functions (泛型函数)

    Generic functionscan work with any type. Here’s a generic version of theswapTwoInts(_:_:)function from above, calledswapTwoValues(_:_:):

    泛型函数可以适用于任何类型,下面的swapTwoValues(_:_:)函数是上面三个函数的泛型版本:

    func swapTwoValues(_a:inoutT,_b:inoutT) {

      let temporaryA=a

      a=b

      b=temporaryA

    }

    The body of theswapTwoValues(_:_:)function is identical to the body of theswapTwoInts(_:_:)function. However, the first line ofswapTwoValues(_:_:)is slightly different fromswapTwoInts(_:_:). Here’s how the first lines compare:

    swapTwoValues(_:_:)的函数主体和swapTwoInts(_:_:)函数是一样的,它们只在第一行有点不同,如下所示:

    func swapTwoInts(_a:inoutInt,_b:inoutInt)

    func swapTwoValues(_a:inoutT,_b:inoutT)

    The generic version of the function uses aplaceholdertype name (calledT, in this case) instead of anactualtype name (such asInt,String, orDouble). The placeholder type name doesn’t say anything about whatTmust be, but itdoessay that bothaandbmust be of the same typeT, whateverTrepresents. The actual type to use in place ofTwill be determined each time theswapTwoValues(_:_:)function is called.

    这个函数的泛型版本使用了占位类型名(在这里用字母T来表示)来代替实际类型名(例如Int、String或Double)。占位类型名没有指明T必须是什么类型,但是它指明了a和b必须是同一类型T,无论T代表什么类型。只有swapTwoValues(_:_:)函数在调用时,才能根据所传入的实际类型决定T所代表的类型。

    The other difference is that the generic function’s name (swapTwoValues(_:_:)) is followed by the placeholder type name (T) inside angle brackets (). The brackets tell Swift thatTis a placeholder type name within theswapTwoValues(_:_:)function definition. BecauseTis a placeholder, Swift does not look for an actual type calledT.

    另外一个不同之处在于这个泛型函数名(swapTwoValues(_:_:))后面跟着占位类型名(T),并用尖括号括起来()。这个尖括号告诉 Swift 那个T是swapTwoValues(_:_:)函数定义内的一个占位类型名,因此 Swift 不会去查找名为T的实际类型。

    TheswapTwoValues(_:_:)function can now be called in the same way asswapTwoInts, except that it can be passed two values ofanytype, as long as both of those values are of the same type as each other. Each timeswapTwoValues(_:_:)is called, the type to use forTis inferred from the types of values passed to the function.

    swapTwoValues(_:_:)函数现在可以像swapTwoInts(_:_:)那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。swapTwoValues(_:_:)函数被调用时,T所代表的类型都会由传入的值的类型推断出来。

    In the two examples below,Tis inferred to beIntandStringrespectively:

    在下面的两个例子中,T分别代表Int和String:

    var someInt=3

    var anotherInt=107

    swapTwoValues(&someInt, &anotherInt)

    // someInt is now 107, and anotherInt is now 3

    var someString="hello"

    var anotherString="world"

    swapTwoValues(&someString, &anotherString)

    // someString is now "world", and anotherString is now "hello"

    NOTE

    TheswapTwoValues(_:_:)function defined above is inspired by a generic function calledswap, which is part of the Swift standard library, and is automatically made available for you to use in your apps. If you need the behavior of theswapTwoValues(_:_:)function in your own code, you can use Swift’s existingswap(_:_:)function rather than providing your own implementation.

    上面定义的swapTwoValues(_:_:)函数是受swap(_:_:)函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似swapTwoValues(_:_:)函数的功能,你可以使用已存在的swap(_:_:)函数。

    Type Parameters (类型参数)

    In theswapTwoValues(_:_:)example above, the placeholder typeTis an example of atype parameter. Type parameters specify and name a placeholder type, and are written immediately after the function’s name, between a pair of matching angle brackets (such as).

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

    Once you specify a type parameter, you can use it to define the type of a function’s parameters (such as theaandbparameters of theswapTwoValues(_:_:)function), or as the function’s return type, or as a type annotation within the body of the function. In each case, the type parameter is replaced with anactualtype whenever the function is called. (In theswapTwoValues(_:_:)example above,Twas replaced withIntthe first time the function was called, and was replaced withStringthe second time it was called.)

    一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型(例如swapTwoValues(_:_:)函数中的参数a和b),或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。(在上面的swapTwoValues(_:_:)例子中,当函数第一次被调用时,T被Int替换,第二次调用时,被String替换。)

    You can provide more than one type parameter by writing multiple type parameter names within the angle brackets, separated by commas.

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

    Naming Type Parameters (命名类型参数)

    In most cases, type parameters have descriptive names, such asKeyandValueinDictionaryandElementinArray, which tells the reader about the relationship between the type parameter and the generic type or function it’s used in. However, when there isn’t a meaningful relationship between them, it’s traditional to name them using single letters such asT,U, andV, such asTin theswapTwoValues(_:_:)function above.

    在大多数情况下,类型参数具有一个描述性名字,例如Dictionary中的Key和Value,以及Array中的Element,这可以告诉阅读代码的人这些类型参数和泛型函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字母来命名,例如T、U、V,正如上面演示的swapTwoValues(_:_:)函数中的T一样。

    NOTE

    Always give type parameters upper camel case names (such asTandMyTypeParameter) to indicate that they are a placeholder for atype, not a value.

    请始终使用大写字母开头的驼峰命名法(例如T和MyTypeParameter)来为类型参数命名,以表明它们是占位类型,而不是一个值。

    Generic Types (泛型类型)

    In addition to generic functions, Swift enables you to define your owngeneric types. These are custom classes, structures, and enumerations that can work withanytype, in a similar way toArrayandDictionary.

    除了泛型函数,Swift 还允许你定义泛型类型。这些自定义类、结构体和枚举可以适用于任何类型,类似于Array和Dictionary。

    This section shows you how to write a generic collection type calledStack. A stack is an ordered set of values, similar to an array, but with a more restricted set of operations than Swift’sArraytype. An array allows new items to be inserted and removed at any location in the array. A stack, however, allows new items to be appended only to the end of the collection (known aspushinga new value on to the stack). Similarly, a stack allows items to be removed only from the end of the collection (known aspoppinga value off the stack).

    这部分内容将向你展示如何编写一个名为Stack(栈)的泛型集合类型。栈是一系列值的有序集合,和Array类似,但它相比 Swift 的Array类型有更多的操作限制。数组允许在数组的任意位置插入新元素或是删除其中任意位置的元素。而栈只允许在集合的末端添加新的元素(称之为入)。类似的,栈也只能从末端移除元素(称之为栈)。

    NOTE

    The concept of a stack is used by theUINavigationControllerclass to model the view controllers in its navigation hierarchy. You call theUINavigationControllerclasspushViewController(_:animated:)method to add (or push) a view controller on to the navigation stack, and itspopViewControllerAnimated(_:)method to remove (or pop) a view controller from the navigation stack. A stack is a useful collection model whenever you need a strict “last in, first out” approach to managing a collection.

    栈的概念已被UINavigationController类用来构造视图控制器的导航结构。你通过调用UINavigationController的pushViewController(_:animated:)方法来添加新的视图控制器到导航栈,通过popViewControllerAnimated(_:)方法来从导航栈中移除视图控制器。每当你需要一个严格的“后进先出”方式来管理集合,栈都是最实用的模型。

    The illustration below shows the push / pop behavior for a stack:

    下图展示了一个栈的入栈(push)和出栈(pop)的行为:

    1. There are currently three values on the stack.

    现在有三个值在栈中。

    2. A fourth value is “pushed” on to the top of the stack.

    第四个值被压入到栈的顶部。

    3. The stack now holds four values, with the most recent one at the top.

    现在有四个值在栈中,最近入栈的那个值在顶部。

    4. The top item in the stack is removed, or “popped”.

    栈中最顶部的那个值被移除,或称之为出栈。

    5. After popping a value, the stack once again holds three values.

    移除掉一个值后,现在栈又只有三个值了。

    Here’s how to write a non-generic version of a stack, in this case for a stack ofIntvalues:

    下面展示了如何编写一个非泛型版本的栈,以Int型的栈为例:

    struct Int Stack{

    var items= [Int]()

    mutating func push(_item:Int) {

    items.append(item)

    }

    mutating func pop() ->Int{

    return items.removeLast()

    }

    }

    This structure uses anArrayproperty calleditemsto store the values in the stack.Stackprovides two methods,pushandpop, to push and pop values on and off the stack. These methods are marked asmutating, because they need to modify (ormutate) the structure’sitemsarray.

    这个结构体在栈中使用一个名为items的Array属性来存储值。Stack提供了两个方法:push(_:)和pop(),用来向栈中压入值以及从栈中移除值。这些方法被标记为mutating,因为它们需要修改结构体的items数组。

    TheIntStacktype shown above can only be used withIntvalues, however. It would be much more useful to define agenericStackclass, that can manage a stack ofanytype of value.

    上面的IntStack结构体只能用于Int类型。不过,可以定义一个泛型Stack结构体,从而能够处理任意类型的值。

    Here’s a generic version of the same code:

    下面是相同代码的泛型版本:

    struct Stack {

    var items= [Element]()

    mutating func push(_item:Element) {

    items.append(item)

    }

    mutating func pop() ->Element{

    return items.removeLast()

    }

    }

    Note how the generic version ofStackis essentially the same as the non-generic version, but with a type parameter calledElementinstead of an actual type ofInt. This type parameter is written within a pair of angle brackets () immediately after the structure’s name.

    注意,Stack基本上和IntStack相同,只是用占位类型参数Element代替了实际的Int类型。这个类型参数包裹在紧随结构体名的一对尖括号里()。

    Elementdefines a placeholder name for “some typeElement” to be provided later on. This future type can be referred to as “Element” anywhere within the structure’s definition. In this case,Elementis used as a placeholder in three places:

    Element为待提供的类型定义了一个占位名。这种待提供的类型可以在结构体的定义中通过Element来引用。在这个例子中,Element在如下三个地方被用作占位符:

    1. To create a property calleditems, which is initialized with an empty array of values of typeElement

    创建items属性,使用Element类型的空数组对其进行初始化。

    2. To specify that thepush(_:)method has a single parameter calleditem, which must be of typeElement

    指定push(_:)方法的唯一参数item的类型必须是Element类型。

    3. To specify that the value returned by thepop()method will be a value of typeElement

    指定pop()方法的返回值类型必须是Element类型。

    Because it is a generic type,Stackcan be used to create a stack ofanyvalid type in Swift, in a similar manner toArrayandDictionary.

    由于Stack是泛型类型,因此可以用来创建 Swift 中任意有效类型的栈,就像Array和Dictionary那样。

    You create a newStackinstance by writing the type to be stored in the stack within angle brackets. For example, to create a new stack of strings, you writeStack():

    你可以通过在尖括号中写出栈中需要存储的数据类型来创建并初始化一个Stack实例。例如,要创建一个String类型的栈,可以写成Stack():

    var stack OfStrings=Stack()

    stackOfStrings.push("uno")

    stackOfStrings.push("dos")

    stackOfStrings.push("tres")

    stackOfStrings.push("cuatro")

    // the stack now contains 4 strings

    Here’s howstackOfStringslooks after pushing these four values on to the stack:

    下图展示了stackOfStrings如何将这四个值入栈:

    Popping a value from the stack removes and returns the top value,"cuatro":

    移除并返回栈顶部的值"cuatro",即将其出栈:

    let fromTheTop=stackOfStrings.pop()

    // fromTheTop is equal to "cuatro", and the stack now contains 3 strings

    Here’s how the stack looks after popping its top value:

    下图展示了stackOfStrings如何将顶部的值出栈:

    Extending a Generic Type (扩展一个泛型类型)

    When you extend a generic type, you do not provide a type parameter list as part of the extension’s definition. Instead, the type parameter list from theoriginaltype definition is available within the body of the extension, and the original type parameter names are used to refer to the type parameters from the original definition.

    当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。

    The following example extends the genericStacktype to add a read-only computed property calledtopItem, which returns the top item on the stack without popping it from the stack:

    下面的例子扩展了泛型类型Stack,为其添加了一个名为topItem的只读计算型属性,它将会返回当前栈顶端的元素而不会将其从栈中移除:

    extension Stack{

      var topItem:Element? {

      return items.isEmpty?nil:items[items.count-1]

      }

    }

    ThetopItemproperty returns an optional value of typeElement. If the stack is empty,topItemreturnsnil; if the stack is not empty,topItemreturns the final item in theitemsarray.

    topItem属性会返回一个Element类型的可选值。当栈为空的时候,topItem会返回nil;当栈不为空的时候,topItem会返回items数组中的最后一个元素。

    Note that this extension does not define a type parameter list. Instead, theStacktype’s existing type parameter name,Element, is used within the extension to indicate the optional type of thetopItemcomputed property.

    注意,这个扩展并没有定义一个类型参数列表。相反的,Stack类型已有的类型参数名称Element,被用在扩展中来表示计算型属性topItem的可选类型。

    ThetopItemcomputed property can now be used with anyStackinstance to access and query its top item without removing it:

    计算型属性topItem现在可以用来访问任意Stack实例的顶端元素且不移除它:

    if let topItem=stackOfStrings.topItem{

      print("The top item on the stack is\(topItem).")

    }

    // Prints "The top item on the stack is tres."

    Type Constraints (类型约束)

    TheswapTwoValues(_:_:)function and theStacktype can work with any type. However, it is sometimes useful to enforce certaintype constraintson the types that can be used with generic functions and generic types. Type constraints specify that a type parameter must inherit from a specific class, or conform to a particular protocol or protocol composition.

    swapTwoValues(_:_:)函数和Stack类型可以作用于任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型添加一个特定的类型约束,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。

    For example, Swift’sDictionarytype places a limitation on the types that can be used as keys for a dictionary. As described inDictionaries, the type of a dictionary’s keys must behashable. That is, it must provide a way to make itself uniquely representable.Dictionaryneeds its keys to be hashable so that it can check whether it already contains a value for a particular key. Without this requirement,Dictionarycould not tell whether it should insert or replace a value for a particular key, nor would it be able to find a value for a given key that is already in the dictionary.

    例如,Swift 的Dictionary类型对字典的键的类型做了些限制。在字典的描述中,字典的键的类型必须是可哈希(hashable)的。也就是说,必须有一种方法能够唯一地表示它。Dictionary的键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,Dictionary将无法判断是否可以插入或者替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。

    This requirement is enforced by a type constraint on the key type forDictionary, which specifies that the key type must conform to theHashableprotocol, a special protocol defined in the Swift standard library. All of Swift’s basic types (such asString,Int,Double, andBool) are hashable by default.

    为了实现这个要求,一个类型约束被强制加到Dictionary的键类型上,要求其键类型必须符合Hashable协议,这是 Swift 标准库中定义的一个特定协议。所有的 Swift 基本类型(例如String、Int、Double和Bool)默认都是可哈希的。

    You can define your own type constraints when creating custom generic types, and these constraints provide much of the power of generic programming. Abstract concepts likeHashablecharacterize types in terms of their conceptual characteristics, rather than their explicit type.

    当你创建自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,描述的是类型在概念上的特征,而不是它们的显式类型。

    Type Constraint Syntax (类型约束语法)

    You write type constraints by placing a single class or protocol constraint after a type parameter’s name, separated by a colon, as part of the type parameter list. The basic syntax for type constraints on a generic function is shown below (although the syntax is the same for generic types):

    你可以在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束,它们将成为类型参数列表的一部分。对泛型函数添加类型约束的基本语法如下所示(作用于泛型类型时的语法与之相同):

    func someFunction(someT:T,someU:U) {

      // function body goes here

    }

    The hypothetical function above has two type parameters. The first type parameter,T, has a type constraint that requiresTto be a subclass ofSomeClass. The second type parameter,U, has a type constraint that requiresUto conform to the protocolSomeProtocol.

    上面这个函数有两个类型参数。第一个类型参数T,有一个要求T必须是SomeClass子类的类型约束;第二个类型参数U,有一个要求U必须符合SomeProtocol协议的类型约束。

    Type Constraints in Action (类型约束实践)

    Here’s a non-generic function calledfindIndex(ofString:in:), which is given aStringvalue to find and an array ofStringvalues within which to find it. ThefindIndex(ofString:in:)function returns an optionalIntvalue, which will be the index of the first matching string in the array if it is found, ornilif the string cannot be found:

    这里有个名为findIndex(ofString:in:)的非泛型函数,该函数的功能是在一个String数组中查找给定String值的索引。若查找到匹配的字符串,findIndex(ofString:in:)函数返回该字符串在数组中的索引值,否则返回nil:

    func findIndex(ofStringvalueToFind:String,inarray: [String]) ->Int? {

    for (index,value) in array.enumerated() {

      if value==valueToFind{

        returnindex

      }

    }

    return nil

    }

    ThefindIndex(ofString:in:)function can be used to find a string value in an array of strings:

    findIndex(ofString:in:)函数可以用于查找字符串数组中的某个字符串:

    let strings= ["cat","dog","llama","parakeet","terrapin"]

    if let foundIndex=findIndex(ofString:"llama",in:strings) {

       print("The index of llama is\(foundIndex)")

    }

    // Prints "The index of llama is 2"

    The principle of finding the index of a value in an array isn’t useful only for strings, however. You can write the same functionality as a generic function by replacing any mention of strings with values of some typeTinstead.

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

    Here’s how you might expect a generic version offindIndex(ofString:in:), calledfindIndex(of:in:), to be written. Note that the return type of this function is stillInt?, because the function returns an optional index number, not an optional value from the array. Be warned, though—this function does not compile, for reasons explained after the example:

    下面展示了findIndex(ofString:in:)函数的泛型版本findIndex(ofString:in:)。请注意这个函数返回值的类型仍然是Int?,这是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数无法通过编译,原因会在例子后面说明:

    func findIndex(ofvalueToFind:T,inarray:[T]) ->Int? {

    for (index,value) in array.enumerated() {

      if value==valueToFind{

        return index

      }

    }

    return nil

    }

    This function does not compile as written above. The problem lies with the equality check, “if value == valueToFind”. Not every type in Swift can be compared with the equal to operator (==). If you create your own class or structure to represent a complex data model, for example, then the meaning of “equal to” for that class or structure is not something that Swift can guess for you. Because of this, it is not possible to guarantee that this code will work foreverypossible typeT, and an appropriate error is reported when you try to compile the code.

    上面所写的函数无法通过编译。问题出在相等性检查上,即 "if value == valueToFind"。不是所有的 Swift 类型都可以用等式符(==)进行比较。比如说,如果你创建一个自定义的类或结构体来表示一个复杂的数据模型,那么 Swift 无法猜到对于这个类或结构体而言“相等”意味着什么。正因如此,这部分代码无法保证适用于每个可能的类型T,当你试图编译这部分代码时会出现相应的错误。

    All is not lost, however. The Swift standard library defines a protocol calledEquatable, which requires any conforming type to implement the equal to operator (==) and the not equal to operator (!=) to compare any two values of that type. All of Swift’s standard types automatically support theEquatableprotocol.

    不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个Equatable协议,该协议要求任何遵循该协议的类型必须实现等式符(==)及不等符(!=),从而能对该类型的任意两个值进行比较。所有的 Swift 标准类型自动支持Equatable协议。

    Any type that isEquatablecan be used safely with thefindIndex(of:in:)function, because it is guaranteed to support the equal to operator. To express this fact, you write a type constraint ofEquatableas part of the type parameter’s definition when you define the function:

    任何Equatable类型都可以安全地使用在findIndex(of:in:)函数中,因为其保证支持等式操作符。为了说明这个事实,当你定义一个函数时,你可以定义一个Equatable类型约束作为类型参数定义的一部分:

    func findIndex(ofvalueToFind:T,inarray:[T]) ->Int? {

      for (index,value) in array.enumerated() {

        if value==valueToFind{

        return index

      }

    }

    return nil

    }

    The single type parameter forfindIndex(of:in:)is written asT: Equatable, which means “any typeTthat conforms to theEquatableprotocol.”

    findIndex(of:in:)唯一的类型参数写做T: Equatable,也就意味着“任何符合Equatable协议的类型T”。

    ThefindIndex(of:in:)function now compiles successfully and can be used with any type that isEquatable, such asDoubleorString:

    findIndex(of:in:)函数现在可以成功编译了,并且可以作用于任何符合Equatable的类型,如Double或String:

    let double Index=findIndex(of:9.3,in: [3.14159,0.1,0.25])

    // doubleIndex is an optional Int with no value, because 9.3 is not in the array

    let string Index=findIndex(of:"Andrea",in: ["Mike","Malcolm","Andrea"])

    // stringIndex is an optional Int containing a value of 2

    Associated Types (关联类型)关联类型

    When defining a protocol, it is sometimes useful to declare one or more associated types as part of the protocol’s definition. Anassociated typegives a placeholder name to a type that is used as part of the protocol. The actual type to use for that associated type is not specified until the protocol is adopted. Associated types are specified with theassociatedtypekeyword.

    定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。你可以通过associatedtype关键字来指定关联类型。

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

    Here’s an example of a protocol calledContainer, which declares an associated type calledItemType:

    下面例子定义了一个Container协议,该协议定义了一个关联类型ItemType:

    protocol Container{

        associated typeItemType

       mutating func append(_item:ItemType)

        var count:Int{get}

        subscript(i:Int) ->ItemType{get}

    }

    TheContainerprotocol defines three required capabilities that any container must provide:

    Container协议定义了三个任何采纳了该协议的类型(即容器)必须提供的功能:

    1. It must be possible to add a new item to the container with anappend(_:)method.

    必须可以通过append(_:)方法添加一个新元素到容器里。

    2. It must be possible to access a count of the items in the container through acountproperty that returns anIntvalue.

    必须可以通过count属性获取容器中元素的数量,并返回一个Int值。

    3. It must be possible to retrieve each item in the container with a subscript that takes anIntindex value.

    必须可以通过索引值类型为Int的下标检索到容器中的每一个元素。

    This protocol doesn’t specify how the items in the container should be stored or what type they are allowed to be. The protocol only specifies the three bits of functionality that any type must provide in order to be considered aContainer. A conforming type can provide additional functionality, as long as it satisfies these three requirements.

    这个协议没有指定容器中元素该如何存储,以及元素必须是何种类型。这个协议只指定了三个任何遵从Container协议的类型必须提供的功能。遵从协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。

    Any type that conforms to theContainerprotocol must be able to specify the type of values it stores. Specifically, it must ensure that only items of the right type are added to the container, and it must be clear about the type of the items returned by its subscript.

    任何遵从Container协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以加进容器中,必须明确通过其下标返回的元素的类型。

    To define these requirements, theContainerprotocol needs a way to refer to the type of the elements that a container will hold, without knowing what that type is for a specific container. TheContainerprotocol needs to specify that any value passed to theappend(_:)method must have the same type as the container’s element type, and that the value returned by the container’s subscript will be of the same type as the container’s element type.

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

    To achieve this, theContainerprotocol declares an associated type calledItemType, written asassociatedtype ItemType. The protocol does not define whatItemTypeis—that information is left for any conforming type to provide. Nonetheless, theItemTypealias provides a way to refer to the type of the items in aContainer, and to define a type for use with theappend(_:)method and subscript, to ensure that the expected behavior of anyContaineris enforced.

    为了达到这个目的,Container协议声明了一个关联类型ItemType,写作associatedtype ItemType。这个协议无法定义ItemType是什么类型的别名,这个信息将留给遵从协议的类型来提供。尽管如此,ItemType别名提供了一种方式来引用Container中元素的类型,并将之用于append(_:)方法和下标,从而保证任何Container的行为都能够正如预期地被执行。

    Here’s a version of the non-genericIntStacktype from earlier, adapted to conform to theContainerprotocol:

    下面是先前的非泛型的IntStack类型,这一版本采纳并符合了Container协议:

    struct Int Stack:Container{

    // original IntStack implementation

    var items= [Int]()

    mutating func push(_item:Int) {

    items.append(item)

    }

    mutating func pop() ->Int{

    return items.removeLast()

    }

    // conformance to the Container protocol

    type aliasItemType=Int

    mutating func append(_item:Int) {

    self.push(item)

    }

    var count:Int{

    return items.count

    }

    subscript(i:Int) ->Int{

    return items[i]

    }

    }

    TheIntStacktype implements all three of theContainerprotocol’s requirements, and in each case wraps part of theIntStacktype’s existing functionality to satisfy these requirements.

    IntStack结构体实现了Container协议的三个要求,其原有功能也不会和这些要求相冲突。

    Moreover,IntStackspecifies that for this implementation ofContainer, the appropriateItemTypeto use is a type ofInt. The definition oftypealias ItemType = Intturns the abstract type ofItemTypeinto a concrete type ofIntfor this implementation of theContainerprotocol.

    此外,IntStack在实现Container的要求时,指定ItemType为Int类型,即typealias ItemType = Int,从而将Container协议中抽象的ItemType类型转换为具体的Int类型。

    Thanks to Swift’s type inference, you don’t actually need to declare a concreteItemTypeofIntas part of the definition ofIntStack. BecauseIntStackconforms to all of the requirements of theContainerprotocol, Swift can infer the appropriateItemTypeto use, simply by looking at the type of theappend(_:)method’sitemparameter and the return type of the subscript. Indeed, if you delete thetypealias ItemType = Intline from the code above, everything still works, because it is clear what type should be used forItemType.

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

    You can also make the genericStacktype conform to theContainerprotocol:

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

    struct Stack:Container{

    // original Stack implementation

    var items= [Element]()

    mutating func push(_item:Element) {

    items.append(item)

    }

    mutating func pop() ->Element{

    return items.removeLast()

    }

    // conformance to the Container protocol

    mutating func append(_item:Element) {

    self.push(item)

    }

    var count:Int{

    return items.count

    }

    subscript(i:Int) ->Element{

    return items[i]

    }

    }

    This time, the type parameterElementis used as the type of theappend(_:)method’sitemparameter and the return type of the subscript. Swift can therefore infer thatElementis the appropriate type to use as theItemTypefor this particular container.

    这一次,占位类型参数Element被用作append(_:)方法的item参数和下标的返回类型。Swift 可以据此推断出Element的类型即是ItemType的类型。

    Extending an Existing Type to Specify an Associated Type (通过扩展一个存在的类型来指定关联类型)

    You can extend an existing type to add conformance to a protocol, as described inAdding Protocol Conformance with an Extension. This includes a protocol with an associated type.

    通过扩展添加协议一致性中描述了如何利用扩展让一个已存在的类型符合一个协议,这包括使用了关联类型的协议。

    Swift’sArraytype already provides anappend(_:)method, acountproperty, and a subscript with anIntindex to retrieve its elements. These three capabilities match the requirements of theContainerprotocol. This means that you can extendArrayto conform to theContainerprotocol simply by declaring thatArrayadopts the protocol. You do this with an empty extension, as described inDeclaring Protocol Adoption with an Extension:

    Swift 的Array类型已经提供append(_:)方法,一个count属性,以及一个接受Int类型索引值的下标用以检索其元素。这三个功能都符合Container协议的要求,也就意味着你只需简单地声明Array采纳该协议就可以扩展Array,使其遵从Container协议。你可以通过一个空扩展来实现这点,正如通过扩展采纳协议中的描述:

    extension Array:Container{}

    Array’s existingappend(_:)method and subscript enable Swift to infer the appropriate type to use forItemType, just as for the genericStacktype above. After defining this extension, you can use anyArrayas aContainer.

    如同上面的泛型Stack结构体一样,Array的append(_:)方法和下标确保了 Swift 可以推断出ItemType的类型。定义了这个扩展后,你可以将任意Array当作Container来使用。

    Generic Where Clauses (泛型 Where 语句)

    Type constraints, as described inType Constraints, enable you to define requirements on the type parameters associated with a generic function or type.

    类型约束让你能够为泛型函数或泛型类型的类型参数定义一些强制要求。

    It can also be useful to define requirements for associated types. You do this by defining ageneric where clause. A genericwhereclause enables you to require that an associated type must conform to a certain protocol, or that certain type parameters and associated types must be the same. A genericwhereclause starts with thewherekeyword, followed by constraints for associated types or equality relationships between types and associated types. You write a genericwhereclause right before the opening curly brace of a type or function’s body.

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

    The example below defines a generic function calledallItemsMatch, which checks to see if twoContainerinstances contain the same items in the same order. The function returns a Boolean value oftrueif all items match and a value offalseif they do not.

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

    The two containers to be checked do not have to be the same type of container (although they can be), but they do have to hold the same type of items. This requirement is expressed through a combination of type constraints and a genericwhereclause:

    被检查的两个Container可以不是相同类型的容器(虽然它们可以相同),但它们必须拥有相同类型的元素。这个要求通过一个类型约束以及一个where子句来表示:

    func allItemsMatch(_someContainer:C1,_anotherContainer:C2) ->Bool

    whereC1.ItemType==C2.ItemType,C1.ItemType: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 are equivalent.

    for i in0..<someContainer.count{

      if someContainer[i] !=anotherContainer[i] {

        return false

    }

    }

    // All items match, so return true.

    return true

    }

    This function takes two arguments calledsomeContainerandanotherContainer. ThesomeContainerargument is of typeC1, and theanotherContainerargument is of typeC2. BothC1andC2are type parameters for two container types to be determined when the function is called.

    这个函数接受someContainer和anotherContainer两个参数。参数someContainer的类型为C1,参数anotherContainer的类型为C2。C1和C2是容器的两个占位类型参数,函数被调用时才能确定它们的具体类型。

    The following requirements are placed on the function’s two type parameters:

    这个函数的类型参数列表还定义了对两个类型参数的要求:

    1. C1must conform to theContainerprotocol (written asC1: Container).

    C1必须符合Container协议(写作C1: Container)。

    2. C2must also conform to theContainerprotocol (written asC2: Container).

    C2必须符合Container协议(写作C2: Container)。

    3. TheItemTypeforC1must be the same as theItemTypeforC2(written asC1.ItemType == C2.ItemType).

    C1的ItemType必须和C2的ItemType类型相同(写作C1.ItemType == C2.ItemType)。

    4. TheItemTypeforC1must conform to theEquatableprotocol (written asC1.ItemType: Equatable).

    C1的ItemType必须符合Equatable协议(写作C1.ItemType: Equatable)。

    The first and second requirements are defined in the function’s type parameter list, and the third and fourth requirements are defined in the function’s genericwhereclause.

    第三个和第四个要求被定义为一个where子句,写在关键字where后面,它们也是泛型函数类型参数列表的一部分。

    These requirements mean:

    这些要求意味着:

    1. someContaineris a container of typeC1.

    someContainer是一个C1类型的容器。

    2. anotherContaineris a container of typeC2.

    anotherContainer是一个C2类型的容器。

    3. someContainerandanotherContainercontain the same type of items.

    someContainer和anotherContainer包含相同类型的元素。

    4. The items insomeContainercan be checked with the not equal operator (!=) to see if they are different from each other.

    someContainer中的元素可以通过不等于操作符(!=)来检查它们是否彼此不同。

    The third and fourth requirements combine to mean that the items inanotherContainercanalsobe checked with the!=operator, because they are exactly the same type as the items insomeContainer.

    第三个和第四个要求结合起来意味着anotherContainer中的元素也可以通过!=操作符来比较,因为它们和someContainer中的元素类型相同。

    These requirements enable theallItemsMatch(_:_:)function to compare the two containers, even if they are of a different container type.

    这些要求让allItemsMatch(_:_:)函数能够比较两个容器,即使它们的容器类型不同。

    TheallItemsMatch(_:_:)function starts by checking that both containers contain the same number of items. If they contain a different number of items, there is no way that they can match, and the function returnsfalse.

    allItemsMatch(_:_:)函数首先检查两个容器是否拥有相同数量的元素,如果它们的元素数量不同,那么一定不匹配,函数就会返回false。

    After making this check, the function iterates over all of the items insomeContainerwith afor-inloop and the half-open range operator (..<). For each item, the function checks whether the item fromsomeContaineris not equal to the corresponding item inanotherContainer. If the two items are not equal, then the two containers do not match, and the function returnsfalse.

    进行这项检查之后,通过for-in循环和半闭区间操作符(..<)来迭代每个元素,检查someContainer中的元素是否不等于anotherContainer中的对应元素。如果两个元素不相等,那么两个容器不匹配,函数返回false。

    If the loop finishes without finding a mismatch, the two containers match, and the function returnstrue.

    如果循环体结束后未发现任何不匹配的情况,表明两个容器匹配,函数返回true。

    Here’s how theallItemsMatch(_:_:)function looks in action:

    下面演示了allItemsMatch(_:_:)函数的使用:

    var stackOfStrings=Stack()

    stackOfStrings.push("uno")

    stackOfStrings.push("dos")

    stackOfStrings.push("tres")

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

    if allItemsMatch(stackOfStrings,arrayOfStrings) {

      print("All items match.")

    } els e{

    print("Not all items match.")

    }

    // Prints "All items match."

    The example above creates aStackinstance to storeStringvalues, and pushes three strings onto the stack. The example also creates anArrayinstance initialized with an array literal containing the same three strings as the stack. Even though the stack and the array are of a different type, they both conform to theContainerprotocol, and both contain the same type of values. You can therefore call theallItemsMatch(_:_:)function with these two containers as its arguments. In the example above, theallItemsMatch(_:_:)function correctly reports that all of the items in the two containers match.

    上面的例子创建了一个Stack实例来存储一些String值,然后将三个字符串压入栈中。这个例子还通过数组字面量创建了一个Array实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵从Container协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用allItemsMatch(_:_:)函数。在上面的例子中,allItemsMatch(_:_:)函数正确地显示了这两个容器中的所有元素都是相互匹配的。

    相关文章

      网友评论

        本文标题:Generics (泛型)

        本文链接:https://www.haomeiwen.com/subject/oconbttx.html