make gitbook

This commit is contained in:
numbbbbb
2014-06-14 23:24:20 +08:00
parent a0489eef96
commit 4634005f9c
42 changed files with 981 additions and 1046 deletions

View File

@ -46,7 +46,7 @@
<div class="book" data-level="2.22" data-basepath=".." data-revision="1402750255397">
<div class="book" data-level="2.22" data-basepath=".." data-revision="1402759431779">
<div class="book-header">
<!-- Actions Left -->
<a href="#" class="btn pull-left toggle-summary" aria-label="Toggle summary"><i class="fa fa-align-justify"></i></a>
@ -587,11 +587,10 @@
<div class="page-inner">
<section class="normal" id="section-gitbook_76">
<section class="normal" id="section-gitbook_552">
<blockquote>
<p>翻译takalard</p>
<p>校对lifedim</p>
<p>翻译takalard<br>校对lifedim </p>
</blockquote>
<h1 id="-">泛型</h1>
<hr>
@ -611,20 +610,22 @@
<p><a name="the_problem_that_generics_solve"></a></p>
<h2 id="-">泛型所解决的问题</h2>
<p>这里是一个标准的,非泛型函数<code>swapTwoInts</code>,用来交换两个Int值</p>
<pre><code>func swapTwoInts(inout a: Int, inout b: Int)
let temporaryA = a
a = b
b = temporaryA
<pre><code class="lang-swift">func swapTwoInts(inout a: Int, inout b: Int)
let temporaryA = a
a = b
b = temporaryA
}
</code></pre><p>这个函数使用写入读出in-out参数来交换<code>a</code><code>b</code>的值,请参考[写入读出参数][1]。</p>
</code></pre>
<p>这个函数使用写入读出in-out参数来交换<code>a</code><code>b</code>的值,请参考[写入读出参数][1]。</p>
<p><code>swapTwoInts</code>函数可以交换<code>b</code>的原始值到<code>a</code>也可以交换a的原始值到<code>b</code>,你可以调用这个函数交换两个<code>Int</code>变量值:</p>
<pre><code>var someInt = 3
<pre><code class="lang-swift">var someInt = 3
var anotherInt = 107
swapTwoInts(&amp;someInt, &amp;anotherInt)
println(&quot;someInt is now \(someInt), and anotherInt is now \(anotherInt)&quot;)
// 输出 &quot;someInt is now 107, and anotherInt is now 3&quot;
</code></pre><p><code>swapTwoInts</code>函数是非常有用的,但是它只能交换<code>Int</code>值,如果你想要交换两个<code>String</code>或者<code>Double</code>,就不得不写更多的函数,如 <code>swapTwoStrings</code><code>swapTwoDoublesfunctions</code>,如同如下所示:</p>
<pre><code>func swapTwoStrings(inout a: String, inout b: String) {
</code></pre>
<p><code>swapTwoInts</code>函数是非常有用的,但是它只能交换<code>Int</code>值,如果你想要交换两个<code>String</code>或者<code>Double</code>,就不得不写更多的函数,如 <code>swapTwoStrings</code><code>swapTwoDoublesfunctions</code>,如同如下所示:</p>
<pre><code class="lang-swift">func swapTwoStrings(inout a: String, inout b: String) {
let temporaryA = a
a = b
b = temporaryA
@ -635,39 +636,41 @@ func swapTwoDoubles(inout a: Double, inout b: Double) {
a = b
b = temporaryA
}
</code></pre><p>你可能注意到 <code>swapTwoInts</code><code>swapTwoStrings</code><code>swapTwoDoubles</code>函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是<code>Int</code><code>String</code><code>Double</code></p>
</code></pre>
<p>你可能注意到 <code>swapTwoInts</code><code>swapTwoStrings</code><code>swapTwoDoubles</code>函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是<code>Int</code><code>String</code><code>Double</code></p>
<p>但实际应用中通常需要一个用处更强大并且尽可能的考虑到更多的灵活性单个函数,可以用来交换两个任何类型值,很幸运的是,泛型代码帮你解决了这种问题。(一个这种泛型函数后面已经定义好了。)</p>
<blockquote>
<p>注意:
在所有三个函数中,<code>a</code><code>b</code>的类型是一样的。如果<code>a</code><code>b</code>不是相同的类型那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个<code>String</code>类型的变量和一个<code>Double</code>类型的变量互相交换值。如果一定要做Swift 将报编译错误。</p>
<p>注意:<br>在所有三个函数中,<code>a</code><code>b</code>的类型是一样的。如果<code>a</code><code>b</code>不是相同的类型那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个<code>String</code>类型的变量和一个<code>Double</code>类型的变量互相交换值。如果一定要做Swift 将报编译错误。</p>
</blockquote>
<p><a name="generic_functions"></a></p>
<h2 id="-">泛型函数</h2>
<p><code>泛型函数</code>可以工作于任何类型,这里是一个上面<code>swapTwoInts</code>函数的泛型版本,用于交换两个值:</p>
<pre><code>func swapTwoValues&lt;T&gt;(inout a: T, inout b: T) {
<pre><code class="lang-swift">func swapTwoValues&lt;T&gt;(inout a: T, inout b: T) {
let temporaryA = a
a = b
b = temporaryA
}
</code></pre><p><code>swapTwoValues</code>函数主体和<code>swapTwoInts</code>函数是一样的,它只在第一行稍微有那么一点点不同于<code>swapTwoInts</code>,如下所示:</p>
<pre><code>func swapTwoInts(inout a: Int, inout b: Int)
</code></pre>
<p><code>swapTwoValues</code>函数主体和<code>swapTwoInts</code>函数是一样的,它只在第一行稍微有那么一点点不同于<code>swapTwoInts</code>,如下所示:</p>
<pre><code class="lang-swift">func swapTwoInts(inout a: Int, inout b: Int)
func swapTwoValues&lt;T&gt;(inout a: T, inout b: T)
</code></pre><p>这个函数的泛型版本使用了占位类型名字(通常此情况下用字母<code>T</code>来表示)来代替实际类型名(如<code>In</code><code>String</code><code>Doubl</code>)。占位类型名没有提示<code>T</code>必须是什么类型,但是它提示了<code>a</code><code>b</code>必须是同一类型<code>T</code>,而不管<code>T</code>表示什么类型。只有<code>swapTwoValues</code>函数在每次调用时所传入的实际类型才能决定<code>T</code>所代表的类型。</p>
</code></pre>
<p>这个函数的泛型版本使用了占位类型名字(通常此情况下用字母<code>T</code>来表示)来代替实际类型名(如<code>In</code><code>String</code><code>Doubl</code>)。占位类型名没有提示<code>T</code>必须是什么类型,但是它提示了<code>a</code><code>b</code>必须是同一类型<code>T</code>,而不管<code>T</code>表示什么类型。只有<code>swapTwoValues</code>函数在每次调用时所传入的实际类型才能决定<code>T</code>所代表的类型。</p>
<p>另外一个不同之处在于这个泛型函数名后面跟着的展位类型名字T是用尖括号括起来的<T>)。这个尖括号告诉 Swift 那个<code>T</code><code>swapTwoValues</code>函数所定义的一个类型。因为<code>T</code>是一个占位命名类型Swift 不会去查找命名为T的实际类型。</p>
<p><code>swapTwoValues</code>函数除了要求传入的两个任何类型值是同一类型外,也可以作为<code>swapTwoInts</code>函数被调用。每次<code>swapTwoValues</code>被调用T所代表的类型值都会传给函数。</p>
<p>在下面的两个例子中,<code>T</code>分别代表<code>Int</code><code>String</code></p>
<pre><code>var someInt = 3
<pre><code class="lang-swift">var someInt = 3
var anotherInt = 107
swapTwoValues(&amp;someInt, &amp;anotherInt)
// someInt is now 107, and anotherInt is now 3
var someString = &quot;hello&quot;
</code></pre>
<pre><code class="lang-swift">var someString = &quot;hello&quot;
var anotherString = &quot;world&quot;
swapTwoValues(&amp;someString, &amp;anotherString)
// someString is now &quot;world&quot;, and anotherString is now &quot;hello&quot;
</code></pre><blockquote>
<p>注意
上面定义的函数<code>swapTwoValues</code>是受<code>swap</code>函数启发而实现的。<code>swap</code>函数存在于 Swift 标准库,并可以在其它类中任意使用。如果你在自己代码中需要类似<code>swapTwoValues</code>函数的功能,你可以使用已存在的交换函数<code>swap</code>函数。</p>
</code></pre>
<blockquote>
<p>注意<br>上面定义的函数<code>swapTwoValues</code>是受<code>swap</code>函数启发而实现的。<code>swap</code>函数存在于 Swift 标准库,并可以在其它类中任意使用。如果你在自己代码中需要类似<code>swapTwoValues</code>函数的功能,你可以使用已存在的交换函数<code>swap</code>函数。</p>
</blockquote>
<p><a name="type_parameters"></a></p>
<h2 id="-">类型参数</h2>
@ -679,16 +682,14 @@ swapTwoValues(&amp;someString, &amp;anotherString)
<p>在简单的情况下,泛型函数或泛型类型需要指定一个占位类型(如上面的<code>swapTwoValues</code>泛型函数,或一个存储单一类型的泛型集,如数组),通常用一单个字母<code>T</code>来命名类型参数。不过,你可以使用任何有效的标识符来作为类型参数名。</p>
<p>如果你使用多个参数定义更复杂的泛型函数或泛型类型那么使用更多的描述类型参数是非常有用的。例如Swift 字典Dictionary类型有两个类型参数一个是键另外一个是值。如果你自己写字典你或许会定义这两个类型参数为<code>KeyType</code><code>ValueType</code>,用来记住它们在你的泛型代码中的作用。</p>
<blockquote>
<p>注意
请始终使用大写字母开头的驼峰式命名法(例如<code>T</code><code>KeyType</code>)来给类型参数命名,以表明它们是类型的占位符,而非类型值。</p>
<p>注意<br>请始终使用大写字母开头的驼峰式命名法(例如<code>T</code><code>KeyType</code>)来给类型参数命名,以表明它们是类型的占位符,而非类型值。</p>
</blockquote>
<p><a name="generic_types"></a></p>
<h2 id="-">泛型类型</h2>
<p>通常在泛型函数中Swift 允许你定义你自己的泛型类型。这些自定义类、结构体和枚举作用于任何类型,如同<code>Array</code><code>Dictionary</code>的用法。</p>
<p>这部分向你展示如何写一个泛型集类型--<code>Stack</code>(栈)。一个栈是一系列值域的集合,和<code>Array</code>(数组)类似,但其是一个比 Swift 的<code>Array</code>类型更多限制的集合。一个数组可以允许其里面任何位置的插入/删除操作,而栈,只允许在集合的末端添加新的项(如同<em>push</em>一个新值进栈)。同样的一个栈也只能从末端移除项(如同<em>pop</em>一个值出栈)。</p>
<blockquote>
<p>注意
栈的概念已被<code>UINavigationController</code>类使用来模拟试图控制器的导航结构。你通过调用<code>UINavigationController</code><code>pushViewController:animated:</code>方法来为导航栈添加add新的试图控制器而通过<code>popViewControllerAnimated:</code>的方法来从导航栈中移除pop某个试图控制器。每当你需要一个严格的<code>后进先出</code>方式来管理集合,堆栈都是最实用的模型。</p>
<p>注意<br>栈的概念已被<code>UINavigationController</code>类使用来模拟试图控制器的导航结构。你通过调用<code>UINavigationController</code><code>pushViewController:animated:</code>方法来为导航栈添加add新的试图控制器而通过<code>popViewControllerAnimated:</code>的方法来从导航栈中移除pop某个试图控制器。每当你需要一个严格的<code>后进先出</code>方式来管理集合,堆栈都是最实用的模型。</p>
</blockquote>
<p>下图展示了一个栈的压栈(push)/出栈(pop)的行为:</p>
<p>![此处输入图片的描述][2]</p>
@ -700,7 +701,7 @@ swapTwoValues(&amp;someString, &amp;anotherString)
<li>移除掉一个值后,现在栈又重新只有三个值。</li>
</ol>
<p>这里展示了如何写一个非泛型版本的栈,<code>Int</code>值型的栈:</p>
<pre><code>struct IntStack {
<pre><code class="lang-swift">struct IntStack {
var items = Int[]()
mutating func push(item: Int) {
items.append(item)
@ -709,10 +710,11 @@ swapTwoValues(&amp;someString, &amp;anotherString)
return items.removeLast()
}
}
</code></pre><p>这个结构体在栈中使用一个<code>Array</code>性质的<code>items</code>存储值。<code>Stack</code>提供两个方法:<code>push</code><code>pop</code>,从栈中压进一个值和移除一个值。这些方法标记为可变的,因为它们需要修改(或<em>转换</em>)结构体的<code>items</code>数组。</p>
</code></pre>
<p>这个结构体在栈中使用一个<code>Array</code>性质的<code>items</code>存储值。<code>Stack</code>提供两个方法:<code>push</code><code>pop</code>,从栈中压进一个值和移除一个值。这些方法标记为可变的,因为它们需要修改(或<em>转换</em>)结构体的<code>items</code>数组。</p>
<p>上面所展现的<code>IntStack</code>类型只能用于<code>Int</code>值,不过,其对于定义一个泛型<code>Stack</code>类(可以处理<em>任何</em>类型值的栈)是非常有用的。</p>
<p>这里是一个相同代码的泛型版本:</p>
<pre><code>struct Stack&lt;T&gt; {
<pre><code class="lang-swift">struct Stack&lt;T&gt; {
var items = T[]()
mutating func push(item: T) {
items.append(item)
@ -721,7 +723,8 @@ swapTwoValues(&amp;someString, &amp;anotherString)
return items.removeLast()
}
}
</code></pre><p>注意到<code>Stack</code>的泛型版本基本上和非泛型版本相同但是泛型版本的占位类型参数为T代替了实际<code>Int</code>类型。这种类型参数包含在一对尖括号里(<code>&lt;T&gt;</code>),紧随在结构体名字后面。</p>
</code></pre>
<p>注意到<code>Stack</code>的泛型版本基本上和非泛型版本相同但是泛型版本的占位类型参数为T代替了实际<code>Int</code>类型。这种类型参数包含在一对尖括号里(<code>&lt;T&gt;</code>),紧随在结构体名字后面。</p>
<p><code>T</code>定义了一个名为“某种类型T”的节点提供给后来用。这种将来类型可以在结构体的定义里任何地方表示为“T”。在这种情况下<code>T</code>在如下三个地方被用作节点:</p>
<ul>
<li>创建一个名为<code>items</code>的属性使用空的T类型值数组对其进行初始化</li>
@ -729,18 +732,20 @@ swapTwoValues(&amp;someString, &amp;anotherString)
<li>指定一个<code>pop</code>方法的返回值该返回值将是一个T类型值。</li>
</ul>
<p>当创建一个新单例并初始化时, 通过用一对紧随在类型名后的尖括号里写出实际指定栈用到类型,创建一个<code>Stack</code>实例,同创建<code>Array</code><code>Dictionary</code>一样:</p>
<pre><code>var stackOfStrings = Stack&lt;String&gt;()
<pre><code class="lang-swift">var stackOfStrings = Stack&lt;String&gt;()
stackOfStrings.push(&quot;uno&quot;)
stackOfStrings.push(&quot;dos&quot;)
stackOfStrings.push(&quot;tres&quot;)
stackOfStrings.push(&quot;cuatro&quot;)
// 现在栈已经有4个string了
</code></pre><p>下图将展示<code>stackOfStrings</code>如何<code>push</code>这四个值进栈的过程:</p>
</code></pre>
<p>下图将展示<code>stackOfStrings</code>如何<code>push</code>这四个值进栈的过程:</p>
<p>![此处输入图片的描述][3]</p>
<p>从栈中<code>pop</code>并移除值&quot;cuatro&quot;</p>
<pre><code>let fromTheTop = stackOfStrings.pop()
<pre><code class="lang-swift">let fromTheTop = stackOfStrings.pop()
// fromTheTop is equal to &quot;cuatro&quot;, and the stack now contains 3 strings
</code></pre><p>下图展示了如何从栈中pop一个值的过程
</code></pre>
<p>下图展示了如何从栈中pop一个值的过程
![此处输入图片的描述][4]</p>
<p>由于<code>Stack</code>是泛型类型,所以在 Swift 中其可以用来创建任何有效类型的栈,这种方式如同<code>Array</code><code>Dictionary</code></p>
<p><a name="type_constraints"></a></p>
@ -751,13 +756,14 @@ stackOfStrings.push(&quot;cuatro&quot;)
<p>当你创建自定义泛型类型时,你可以定义你自己的类型约束,当然,这些约束要支持泛型编程的强力特征中的多数。抽象概念如<code>可哈希</code>具有的类型特征是根据它们概念特征来界定的,而不是它们的直接类型特征。</p>
<h3 id="-">类型约束语法</h3>
<p>你可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基础语法如下所示(和泛型类型的语法相同):</p>
<pre><code>func someFunction&lt;T: SomeClass, U: SomeProtocol&gt;(someT: T, someU: U) {
<pre><code class="lang-swift">func someFunction&lt;T: SomeClass, U: SomeProtocol&gt;(someT: T, someU: U) {
// function body goes here
}
</code></pre><p>上面这个假定函数有两个类型参数。第一个类型参数<code>T</code>,有一个需要<code>T</code>必须是<code>SomeClass</code>子类的类型约束;第二个类型参数<code>U</code>,有一个需要<code>U</code>必须遵循<code>SomeProtocol</code>协议的类型约束。</p>
</code></pre>
<p>上面这个假定函数有两个类型参数。第一个类型参数<code>T</code>,有一个需要<code>T</code>必须是<code>SomeClass</code>子类的类型约束;第二个类型参数<code>U</code>,有一个需要<code>U</code>必须遵循<code>SomeProtocol</code>协议的类型约束。</p>
<h3 id="-">类型约束行为</h3>
<p>这里有个名为<code>findStringIndex</code>的非泛型函数,该函数功能是去查找包含一给定<code>String</code>值的数组。若查找到匹配的字符串,<code>findStringIndex</code>函数返回该字符串在数组中的索引值(<code>Int</code>),反之则返回<code>nil</code></p>
<pre><code>func findStringIndex(array: String[], valueToFind: String) -&gt; Int? {
<pre><code class="lang-swift">func findStringIndex(array: String[], valueToFind: String) -&gt; Int? {
for (index, value) in enumerate(array) {
if value == valueToFind {
return index
@ -765,15 +771,17 @@ stackOfStrings.push(&quot;cuatro&quot;)
}
return nil
}
</code></pre><p><code>findStringIndex</code>函数可以作用于查找一字符串数组中的某个字符串:</p>
<pre><code>let strings = [&quot;cat&quot;, &quot;dog&quot;, &quot;llama&quot;, &quot;parakeet&quot;, &quot;terrapin&quot;]
</code></pre>
<p><code>findStringIndex</code>函数可以作用于查找一字符串数组中的某个字符串:</p>
<pre><code class="lang-swift">let strings = [&quot;cat&quot;, &quot;dog&quot;, &quot;llama&quot;, &quot;parakeet&quot;, &quot;terrapin&quot;]
if let foundIndex = findStringIndex(strings, &quot;llama&quot;) {
println(&quot;The index of llama is \(foundIndex)&quot;)
}
// 输出 &quot;The index of llama is 2&quot;
</code></pre><p>如果只是针对字符串而言查找在数组中的某个值的索引,用处不是很大,不过,你可以写出相同功能的泛型函数<code>findIndex</code>,用某个类型<code>T</code>值替换掉提到的字符串。</p>
</code></pre>
<p>如果只是针对字符串而言查找在数组中的某个值的索引,用处不是很大,不过,你可以写出相同功能的泛型函数<code>findIndex</code>,用某个类型<code>T</code>值替换掉提到的字符串。</p>
<p>这里展示如何写一个你或许期望的<code>findStringIndex</code>的泛型版本<code>findIndex</code>。请注意这个函数仍然返回<code>Int</code>,是不是有点迷惑呢,而不是泛型类型?那是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数不会编译,原因在例子后面会说明:</p>
<pre><code>func findIndex&lt;T&gt;(array: T[], valueToFind: T) -&gt; Int? {
<pre><code class="lang-swift">func findIndex&lt;T&gt;(array: T[], valueToFind: T) -&gt; Int? {
for (index, value) in enumerate(array) {
if value == valueToFind {
return index
@ -781,10 +789,11 @@ if let foundIndex = findStringIndex(strings, &quot;llama&quot;) {
}
return nil
}
</code></pre><p>上面所写的函数不会编译。这个问题的位置在等式的检查上,<code>“if value == valueToFind”</code>。不是所有的 Swift 中的类型都可以用等式符(==)进行比较。例如,如果你创建一个你自己的类或结构体来表示一个复杂的数据模型,那么 Swift 没法猜到对于这个类或结构体而言“等于”的意思。正因如此,这部分代码不能可能保证工作于每个可能的类型<code>T</code>,当你试图编译这部分代码时估计会出现相应的错误。</p>
</code></pre>
<p>上面所写的函数不会编译。这个问题的位置在等式的检查上,<code>“if value == valueToFind”</code>。不是所有的 Swift 中的类型都可以用等式符(==)进行比较。例如,如果你创建一个你自己的类或结构体来表示一个复杂的数据模型,那么 Swift 没法猜到对于这个类或结构体而言“等于”的意思。正因如此,这部分代码不能可能保证工作于每个可能的类型<code>T</code>,当你试图编译这部分代码时估计会出现相应的错误。</p>
<p>不过所有的这些并不会让我们无从下手。Swift 标准库中定义了一个<code>Equatable</code>协议,该协议要求任何遵循的类型实现等式符(==)和不等符(!=)对任何两个该类型进行比较。所有的 Swift 标准类型自动支持<code>Equatable</code>协议。</p>
<p>任何<code>Equatable</code>类型都可以安全的使用在<code>findIndex</code>函数中,因为其保证支持等式操作。为了说明这个事实,当你定义一个函数时,你可以写一个<code>Equatable</code>类型约束作为类型参数定义的一部分:</p>
<pre><code>func findIndex&lt;T: Equatable&gt;(array: T[], valueToFind: T) -&gt; Int? {
<pre><code class="lang-swift">func findIndex&lt;T: Equatable&gt;(array: T[], valueToFind: T) -&gt; Int? {
for (index, value) in enumerate(array) {
if value == valueToFind {
return index
@ -792,24 +801,27 @@ if let foundIndex = findStringIndex(strings, &quot;llama&quot;) {
}
return nil
}
</code></pre><p><code>findIndex</code>中这个单个类型参数写做:<code>T: Equatable</code>也就意味着“任何T类型都遵循<code>Equatable</code>协议”。</p>
</code></pre>
<p><code>findIndex</code>中这个单个类型参数写做:<code>T: Equatable</code>也就意味着“任何T类型都遵循<code>Equatable</code>协议”。</p>
<p><code>findIndex</code>函数现在则可以成功的编译过,并且作用于任何遵循<code>Equatable</code>的类型,如<code>Double</code><code>String</code>:</p>
<pre><code>let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3)
<pre><code class="lang-swift">let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3)
// doubleIndex is an optional Int with no value, because 9.3 is not in the array
let stringIndex = findIndex([&quot;Mike&quot;, &quot;Malcolm&quot;, &quot;Andrea&quot;], &quot;Andrea&quot;)
// stringIndex is an optional Int containing a value of 2
</code></pre><p><a name="associated_types"></a></p>
</code></pre>
<p><a name="associated_types"></a></p>
<h2 id="-">关联类型</h2>
<p>当定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分是非常有用的。一个关联类型给定作用于协议部分的类型一个节点名(或<em>别名</em>)。作用于关联类型上实际类型是不需要指定的,直到该协议接受。关联类型被指定为<code>typealias</code>关键字。</p>
<h3 id="-">关联类型行为</h3>
<p>这里是一个<code>Container</code>协议的例子定义了一个ItemType关联类型</p>
<pre><code>protocol Container {
<pre><code class="lang-swift">protocol Container {
typealias ItemType
mutating func append(item: ItemType)
var count: Int { get }
subscript(i: Int) -&gt; ItemType { get }
}
</code></pre><p><code>Container</code>协议定义了三个任何容器必须支持的兼容要求:</p>
</code></pre>
<p><code>Container</code>协议定义了三个任何容器必须支持的兼容要求:</p>
<ul>
<li>必须可能通过<code>append</code>方法添加一个新item到容器里</li>
<li>必须可能通过使用<code>count</code>属性获取容器里items的数量并返回一个<code>Int</code>值;</li>
@ -820,7 +832,7 @@ let stringIndex = findIndex([&quot;Mike&quot;, &quot;Malcolm&quot;, &quot;Andrea
<p>为了定义这三个条件,<code>Container</code>协议需要一个方法指定容器里的元素将会保留,而不需要知道特定容器的类型。<code>Container</code>协议需要指定任何通过<code>append</code>方法添加到容器里的值和容器里元素是相同类型,并且通过容器下标返回的容器元素类型的值的类型是相同类型。</p>
<p>为了达到此目的,<code>Container</code>协议声明了一个ItemType的关联类型写作<code>typealias ItemType</code>。The protocol does not define what ItemType is an alias for—that information is left for any conforming type to provide这个协议不会定义<code>ItemType</code>是遵循类型所提供的何种信息的别名)。尽管如此,<code>ItemType</code>别名支持一种方法识别在一个容器里的items类型以及定义一种使用在<code>append</code>方法和下标中的类型,以便保证任何期望的<code>Container</code>的行为是强制性的。</p>
<p>这里是一个早前IntStack类型的非泛型版本适用于遵循Container协议</p>
<pre><code>struct IntStack: Container {
<pre><code class="lang-swift">struct IntStack: Container {
// original IntStack implementation
var items = Int[]()
mutating func push(item: Int) {
@ -841,11 +853,12 @@ let stringIndex = findIndex([&quot;Mike&quot;, &quot;Malcolm&quot;, &quot;Andrea
return items[i]
}
}
</code></pre><p><code>IntStack</code>类型实现了<code>Container</code>协议的所有三个要求,在<code>IntStack</code>类型的每个包含部分的功能都满足这些要求。</p>
</code></pre>
<p><code>IntStack</code>类型实现了<code>Container</code>协议的所有三个要求,在<code>IntStack</code>类型的每个包含部分的功能都满足这些要求。</p>
<p>此外,<code>IntStack</code>指定了<code>Container</code>的实现适用的ItemType被用作<code>Int</code>类型。对于这个<code>Container</code>协议实现而言,定义 <code>typealias ItemType = Int</code>,将抽象的<code>ItemType</code>类型转换为具体的<code>Int</code>类型。</p>
<p>感谢Swift类型参考你不用在<code>IntStack</code>定义部分声明一个具体的<code>Int</code><code>ItemType</code>。由于<code>IntStack</code>遵循<code>Container</code>协议的所有要求,只要通过简单的查找<code>append</code>方法的item参数类型和下标返回的类型Swift就可以推断出合适的<code>ItemType</code>来使用。确实,如果上面的代码中你删除了 <code>typealias ItemType = Int</code>这一行一切仍旧可以工作因为它清楚的知道ItemType使用的是何种类型。</p>
<p>你也可以生成遵循<code>Container</code>协议的泛型<code>Stack</code>类型:</p>
<pre><code>struct Stack&lt;T&gt;: Container {
<pre><code class="lang-swift">struct Stack&lt;T&gt;: Container {
// original Stack&lt;T&gt; implementation
var items = T[]()
mutating func push(item: T) {
@ -865,19 +878,21 @@ let stringIndex = findIndex([&quot;Mike&quot;, &quot;Malcolm&quot;, &quot;Andrea
return items[i]
}
}
</code></pre><p>这个时候,占位类型参数<code>T</code>被用作<code>append</code>方法的item参数和下标的返回类型。Swift 因此可以推断出被用作这个特定容器的<code>ItemType</code><code>T</code>的合适类型。</p>
</code></pre>
<p>这个时候,占位类型参数<code>T</code>被用作<code>append</code>方法的item参数和下标的返回类型。Swift 因此可以推断出被用作这个特定容器的<code>ItemType</code><code>T</code>的合适类型。</p>
<h3 id="-">扩展一个存在的类型为一指定关联类型</h3>
<p>在[使用扩展来添加协议兼容性][6]中有描述扩展一个存在的类型添加遵循一个协议。这个类型包含一个关联类型的协议。</p>
<p>Swift的<code>Array</code>已经提供<code>append</code>方法,一个<code>count</code>属性和通过下标来查找一个自己的元素。这三个功能都达到<code>Container</code>协议的要求。也就意味着你可以扩展<code>Array</code>去遵循<code>Container</code>协议,只要通过简单声明<code>Array</code>适用于该协议而已。如何实践这样一个空扩展,在[使用扩展来声明协议的采纳][7]中有描述这样一个实现一个空扩展的行为:</p>
<pre><code>extension Array: Container {}
</code></pre><p>如同上面的泛型<code>Stack</code>类型一样,<code>Array的append</code>方法和下标保证<code>Swift</code>可以推断出<code>ItemType</code>所使用的适用的类型。定义了这个扩展后,你可以将任何<code>Array</code>当作<code>Container</code>来使用。</p>
<pre><code class="lang-swift">extension Array: Container {}
</code></pre>
<p>如同上面的泛型<code>Stack</code>类型一样,<code>Array的append</code>方法和下标保证<code>Swift</code>可以推断出<code>ItemType</code>所使用的适用的类型。定义了这个扩展后,你可以将任何<code>Array</code>当作<code>Container</code>来使用。</p>
<p><a name="where_clauses"></a></p>
<h2 id="where-">Where 语句</h2>
<p>[类型约束][8]中描述的类型约束确保你定义关于类型参数的需求和一泛型函数或类型有关联。</p>
<p>对于关联类型的定义需求也是非常有用的。你可以通过这样去定义<em>where语句</em>作为一个类型参数队列的一部分。一个<code>where</code>语句使你能够要求一个关联类型遵循一个特定的协议,以及(或)那个特定的类型参数和关联类型可以是相同的。你可写一个<code>where</code>语句,通过紧随放置<code>where</code>关键字在类型参数队列后面,其后跟着一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型的等于关系。</p>
<p>下面的列子定义了一个名为<code>allItemsMatch</code>的泛型函数,用来检查是否两个<code>Container</code>单例包含具有相同顺序的相同元素。如果匹配到所有的元素,那么返回一个为<code>true</code><code>Boolean</code>值,反之,则相反。</p>
<p>这两个容器可以被检查出是否是相同类型的容器(虽然它们可以是),但它们确实拥有相同类型的元素。这个需求通过一个类型约束和<code>where</code>语句结合来表示:</p>
<pre><code>func allItemsMatch&lt;
<pre><code class="lang-swift">func allItemsMatch&lt;
C1: Container, C2: Container
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable&gt;
(someContainer: C1, anotherContainer: C2) -&gt; Bool {
@ -898,7 +913,8 @@ let stringIndex = findIndex([&quot;Mike&quot;, &quot;Malcolm&quot;, &quot;Andrea
return true
}
</code></pre><p>这个函数用了两个参数:<code>someContainer</code><code>anotherContainer</code><code>someContainer</code>参数是类型<code>C1</code><code>anotherContainer</code>参数是类型<code>C2</code><code>C1</code><code>C2</code>是容器的两个占位类型参数,决定了这个函数何时被调用。</p>
</code></pre>
<p>这个函数用了两个参数:<code>someContainer</code><code>anotherContainer</code><code>someContainer</code>参数是类型<code>C1</code><code>anotherContainer</code>参数是类型<code>C2</code><code>C1</code><code>C2</code>是容器的两个占位类型参数,决定了这个函数何时被调用。</p>
<p>这个函数的类型参数列紧随在两个类型参数需求的后面:</p>
<ul>
<li><code>C1</code>必须遵循<code>Container</code>协议 (写作 <code>C1: Container</code>)。</li>
@ -918,7 +934,7 @@ let stringIndex = findIndex([&quot;Mike&quot;, &quot;Malcolm&quot;, &quot;Andrea
<p>检查完之后,函数通过<code>for-in</code>循环和半闭区间操作(..)来迭代<code>someContainer</code>中的所有元素。对于每个元素,函数检查是否<code>someContainer</code>中的元素不等于对应的<code>anotherContainer</code>中的元素,如果这两个元素不等,则这两个容器不匹配,返回<code>false</code></p>
<p>如果循环体结束后未发现没有任何的不匹配,那表明两个容器匹配,函数返回<code>true</code></p>
<p>这里演示了allItemsMatch函数运算的过程</p>
<pre><code>var stackOfStrings = Stack&lt;String&gt;()
<pre><code class="lang-swift">var stackOfStrings = Stack&lt;String&gt;()
stackOfStrings.push(&quot;uno&quot;)
stackOfStrings.push(&quot;dos&quot;)
stackOfStrings.push(&quot;tres&quot;)
@ -931,7 +947,8 @@ if allItemsMatch(stackOfStrings, arrayOfStrings) {
println(&quot;Not all items match.&quot;)
}
// 输出 &quot;All items match.&quot;
</code></pre><p> 上面的例子创建一个<code>Stack</code>单例来存储<code>String</code>,然后压了三个字符串进栈。这个例子也创建了一个<code>Array</code>单例,并初始化包含三个同栈里一样的原始字符串。即便栈和数组否是不同的类型,但它们都遵循<code>Container</code>协议,而且它们都包含同样的类型值。你因此可以调用<code>allItemsMatch</code>函数,用这两个容器作为它的参数。在上面的例子中,<code>allItemsMatch</code>函数正确的显示了所有的这两个容器的<code>items</code>匹配。</p>
</code></pre>
<p> 上面的例子创建一个<code>Stack</code>单例来存储<code>String</code>,然后压了三个字符串进栈。这个例子也创建了一个<code>Array</code>单例,并初始化包含三个同栈里一样的原始字符串。即便栈和数组否是不同的类型,但它们都遵循<code>Container</code>协议,而且它们都包含同样的类型值。你因此可以调用<code>allItemsMatch</code>函数,用这两个容器作为它的参数。在上面的例子中,<code>allItemsMatch</code>函数正确的显示了所有的这两个容器的<code>items</code>匹配。</p>
</section>