# By Hawstein (18) and others
# Via 梁杰 (9) and others
* 'gh-pages' of https://github.com/numbbbbb/the-swift-programming-language-in-chinese: (88 commits)
  update 03/07
  03/06 refine
  03/06 Done
  03/06 declaration attributes Done
  03/06 half done
  03/06 objc attribute done.
  03/06 refine
  03/06 Done
  03/06 declaration attributes Done
  03/06 half done
  update new articles
  Update README.md
  Update README.md
  Update 01_swift.md
  下标 Subscripts 翻译完成
  03/06 objc attribute done.
  又多翻了一点 还有一个小小节
  Update README.md
  update 02/08
  Update README.md
  ...
This commit is contained in:
Kam
2014-06-09 12:50:38 +08:00
58 changed files with 7167 additions and 2193 deletions

View File

@ -2,5 +2,11 @@
Swift 是苹果在 WWDC 2014 上发布的一款全新的编程语言,本书译自苹果官方的 Swift 教程《The Swift Programming Language》。
感谢您的阅读
翻译正在进行中,翻译完成的部分会实时同步到这里。您可以到[项目首页](https://github.com/numbbbbb/the-swift-programming-language-in-chinese)查看当前进度
由于是多人协同翻译,可能会有些术语或者句子翻译不太恰当,如果您在阅读过程中发现此类问题,请直接给我们提 issue 或者 pull request。翻译完成后我们会进行认真的校对给大家提供一本高质量的 Swift 教程。
如果您愿意加入进来,帮助我们进行翻译和校对,请阅读[项目首页](https://github.com/numbbbbb/the-swift-programming-language-in-chinese)中的说明并加入QQ群364279588我们期待您的加入在 Swift 的历史上留下您的足迹!
最后,非常感谢您的阅读,如果您觉得本文不错的话请分享给其他人,您的支持就是我们最大的动力!

View File

@ -17,14 +17,14 @@
* [方法](chapter2/11_Methods.md)
* [下标](chapter2/12_Subscripts.md)
* [继承](chapter2/13_Inheritance.md)
* [构造函数](chapter2/14_Initialization.md)
* [析构函数](chapter2/15_Deinitialization.md)
* [构造过程](chapter2/14_Initialization.md)
* [析构过程](chapter2/15_Deinitialization.md)
* [自动引用计数](chapter2/16_Automatic_Reference_Counting.md)
* [可选链](chapter2/17_Optional_Chaining.md)
* [类型检查](chapter2/18_Type_Casting.md)
* [嵌套类型](chapter2/19_Nested_Types.md)
* [扩展](chapter2/20_Extensions.md)
* [接口](chapter2/21_Protocols.md)
* [协议](chapter2/21_Protocols.md)
* [泛型](chapter2/22_Generics.md)
* [高级操作符](chapter2/23_Advanced_Operators.md)
* [语言参考](chapter3/chapter3.md)
@ -32,8 +32,9 @@
* [词法结构](chapter3/02_Lexical_Structure.md)
* [类型](chapter3/03_Types.md)
* [表达式](chapter3/04_Expressions.md)
* [语句](chapter3/10_Statements.md)
* [声明](chapter3/05_Declarations.md)
* [](chapter3/06_Attributes.md)
* [](chapter3/06_Attributes.md)
* [模式](chapter3/07_Patterns.md)
* [泛型参数](chapter3/08_Generic_Parameters_and_Arguments.md)
* [语法总结](chapter3/09_Summary_of_the_Grammar.md)

View File

@ -1,6 +1,6 @@
# 关于 Swift
Swift 是一种新的编程语言,用于编写 iOS 和 OS X 应用程序。Swift 结合了 C 和 Objective-C 的优点并且不受C的兼容性的限制。Swift 使用安全的编程模式并添加了很多新特性这将使编程更简单扩展性更强也更有趣。除此之外Swift 还支持人见人爱的 Cocoa 和 Cocoa Touch 框架。拥有了这些特性Swift将重新定义软件开发。
Swift 是一种新的编程语言,用于编写 iOS 和 OS X 应用。Swift 结合了 C 和 Objective-C 的优点并且不受C的兼容性的限制。Swift 使用安全的编程模式并添加了很多新特性这将使编程更简单扩展性更强也更有趣。除此之外Swift 还支持人见人爱的 Cocoa 和 Cocoa Touch 框架。拥有了这些特性Swift将重新定义软件开发。
Swift 的开发从很久之前就开始了。为了给 Swift 打好基础苹果公司改进了编译器调试器和框架结构。我们使用自动引用计数Automatic Reference Counting, ARC来简化内存管理。我们在 Foundation 和 Cocoa的基础上构建框架栈并将其标准化。Objective-C 本身支持块、集合语法和模块,所以框架可以轻松支持现代编程语言技术。得益于这些基础工作,我们现在可以发布一个新语言,用于未来的苹果软件的开发。

View File

@ -1,21 +1,5 @@
# 基础部分
本页包含内容:
- 常量与变量
- 注释
- 分号
- 整数
- 浮点数
- 类型安全和类型推导
- 数值类原始值
- 数值类型转换
- 类型别名
- 布尔值
- 元组
- 可选
- 断言
Swift 是 iOS 和 OS X 应用开发的一门新语言。然而,如果你有 C 或者 Objective-C 开发经验的话,你会发现 Swift 的很多内容都是你熟悉的。
Swift 的类型是在 C 和 Objective-C 的基础上提出的,`Int`是整型;`Double``Float`是浮点型;`Bool`是布尔型;`String`是字符串。Swift 还有两个有用的集合类型,`Array``Dictionary`,详情参见`集合类型(待添加链接)`
@ -176,7 +160,7 @@ Swift 也提供了一个特殊的无符号类型`UInt`,长度与当前平台
* 在32位平台上`UInt``UInt32`长度相同。
* 在64位平台上`UInt``UInt64`长度相同。
> 注意:尽量不要使用`UInt`,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用`Int`,即使你要存储的值已知是非负的。统一使用`Int`可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推,详情参见[类型安全和类型推](## 类型安全和类型推)。
> 注意:尽量不要使用`UInt`,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用`Int`,即使你要存储的值已知是非负的。统一使用`Int`可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推,详情参见[类型安全和类型推](## 类型安全和类型推)。
## 浮点数
@ -189,36 +173,36 @@ Swift 也提供了一个特殊的无符号类型`UInt`,长度与当前平台
> 注意:`Double`精确度很高至少有15位数字而`Float`最少只有6位数字。选择哪个类型取决于你的代码需要处理的数字大小。
## 类型安全和类型推
## 类型安全和类型推
Swift 是一个类型安全的语言。类型安全的语言可以让你清楚地知道代码要处理的值的类型。如果你的代码需要一个`String`,你绝对不可能不小心传进去一个`Int`
Swift 是类型安全的,会在编译你的代码时进行类型检查,如果遇到不匹配的类型会报错。这可以让你在开发的时候尽早发现并修复错误。
当你要处理不同类型的值时类型检查可以帮你避免错误。然而这并不是说你每次声明常量和变量的时候都需要显式指定类型。如果你没有显式指定类型Swift 会使用类型推来选择合适的类型。有了类型推,编译器可以在编译代码的时候自动推出表达式的类型。原理很简单,判断你赋的值即可。
当你要处理不同类型的值时类型检查可以帮你避免错误。然而这并不是说你每次声明常量和变量的时候都需要显式指定类型。如果你没有显式指定类型Swift 会使用类型推来选择合适的类型。有了类型推,编译器可以在编译代码的时候自动推出表达式的类型。原理很简单,判断你赋的值即可。
因为有类型推,和 C 或者 Objc 比起来 Swift 很少需要声明类型。常量和变量虽然需要明确类型,但是大部分工作并不需要你自己来完成。
因为有类型推,和 C 或者 Objc 比起来 Swift 很少需要声明类型。常量和变量虽然需要明确类型,但是大部分工作并不需要你自己来完成。
当你声明常量或者变量并赋初值的时候类型推非常有用。当你在声明常量或者变量的时候赋给它们一个原始值即可触发类型推。(原始值就是会直接出现在你代码中的值,比如`42``3.14159`。)
当你声明常量或者变量并赋初值的时候类型推非常有用。当你在声明常量或者变量的时候赋给它们一个原始值即可触发类型推。(原始值就是会直接出现在你代码中的值,比如`42``3.14159`。)
举个例子,如果你给一个新常量赋值`42`并且没有标明类型Swift 可以推出常量类型是`Int`,因为你给它赋的初值看起来很像一个整数:
举个例子,如果你给一个新常量赋值`42`并且没有标明类型Swift 可以推出常量类型是`Int`,因为你给它赋的初值看起来很像一个整数:
let meaningOfLife = 42
// meaningOfLife 会被推为 Int 类型
// meaningOfLife 会被推为 Int 类型
同理如果你没有给浮点原始值标明类型Swift 会推你想要的是`Double`
同理如果你没有给浮点原始值标明类型Swift 会推你想要的是`Double`
let pi = 3.14159
// pi 会被推为 Double 类型
// pi 会被推为 Double 类型
当推浮点数的类型时Swift 总是会选择`Double`而不是`Float`
当推浮点数的类型时Swift 总是会选择`Double`而不是`Float`
如果表达式中同时出现了整数和浮点数,会被推`Double`类型:
如果表达式中同时出现了整数和浮点数,会被推`Double`类型:
let anotherPi = 3 + 0.14159
// anotherPi 会被推为 Double 类型
// anotherPi 会被推为 Double 类型
原始值`3`没有显式声明类型,而表达式中出现了一个浮点原始值,所以表达式会被推`Double`类型。
原始值`3`没有显式声明类型,而表达式中出现了一个浮点原始值,所以表达式会被推`Double`类型。
## 数值类原始值
@ -260,8 +244,7 @@ Swift 是类型安全的,会在编译你的代码时进行类型检查,如
## 数值类型转换
通常来讲,即使代码中的整数常量和变量已知非负,也请使用`Int`类型。总是使用默认的整数类型可以保证你的整数常量和变量可以直接被复用并且可以匹配整数类原始值的类型推
通常来讲,即使代码中的整数常量和变量已知非负,也请使用`Int`类型。总是使用默认的整数类型可以保证你的整数常量和变量可以直接被复用并且可以匹配整数类原始值的类型推
只有在必要的时候才使用其他整数类型,比如要处理外部的长度明确的数据或者为了优化性能、内存占用等等。使用显式指定长度的类型可以及时发现值溢出并且可以暗示正在处理特殊数据。
### 整数转换
@ -281,7 +264,7 @@ Swift 是类型安全的,会在编译你的代码时进行类型检查,如
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
现在两个数字的类型都是`UInt16`,可以进行相加。目标常量`twoThousandAndOne`的类型被推`UInt16`,因为它是两个`UInt16`值的合。
现在两个数字的类型都是`UInt16`,可以进行相加。目标常量`twoThousandAndOne`的类型被推`UInt16`,因为它是两个`UInt16`值的合。
`SomeType(ofInitialValue)`是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部,`UInt16`有一个构造器,可以接受一个`UInt8`类型的值,所以这个构造器可以用现有的`UInt8`来创建一个新的`UInt16`。注意,你并不能传入任意类型的值,只能传入`UInt16`内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型),详情参见`扩展(链接待添加)`.
@ -292,18 +275,18 @@ Swift 是类型安全的,会在编译你的代码时进行类型检查,如
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi 等于 3.14159,所以被推为 Double 类型
// pi 等于 3.14159,所以被推为 Double 类型
这个例子中,常量`three`的值被用来创建一个`Double`类型的值,所以加号两边的数类型相同。如果不进行转换,两者无法相加。
浮点数转换为整数也一样,整数类型可以用`Double`或者`Float`类型来初始化:
let integerPi = Int(pi)
// integerPi 等于 3所以被推为 Int 类型
// integerPi 等于 3所以被推为 Int 类型
当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说`4.75`会变成`4``-3.9`会变成`3`
当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说`4.75`会变成`4``-3.9`会变成`-3`
> 注意:结合数字类常量和变量不同于结合数字类原始值。原始值`3`可以直接和原始值`0.14159`相加,因为数字原始值本身没有明确的类型。它们的类型只在编译器需要求值的时候被推
> 注意:结合数字类常量和变量不同于结合数字类原始值。原始值`3`可以直接和原始值`0.14159`相加,因为数字原始值本身没有明确的类型。它们的类型只在编译器需要求值的时候被推
## 类型别名
@ -327,7 +310,7 @@ Swift 有一个基本的布尔类型,叫做`Bool`。布尔值是指逻辑,
let orangesAreOrange = true
let turnipsAreDelicious = false
`orangesAreOrange``turnipsAreDelicious`的类型会被推`Bool`,因为它们的初值是布尔原始值。就像之前提到的`Int``Double`一样,如果你创建变量的时候给它们赋值`true`或者`false`,那你不需要给常量或者变量标明`Bool`类型。初始化常量或者变量的时候如果所赋的值类型已知,就可以触发类型推,这让 Swift 代码更加简洁并且可读性更高。
`orangesAreOrange``turnipsAreDelicious`的类型会被推`Bool`,因为它们的初值是布尔原始值。就像之前提到的`Int``Double`一样,如果你创建变量的时候给它们赋值`true`或者`false`,那你不需要给常量或者变量标明`Bool`类型。初始化常量或者变量的时候如果所赋的值类型已知,就可以触发类型推,这让 Swift 代码更加简洁并且可读性更高。
当你编写条件语句比如`if`语句的时候,布尔值非常有用:
@ -417,7 +400,7 @@ Swift 有一个基本的布尔类型,叫做`Bool`。布尔值是指逻辑,
* 没有值
> 注意C 和 Objective-C 中并没有可选这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回`nil``nil`表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者美剧类型不起作用。对于这些类型Objective-C 方法一般会返回一个特殊值(比如`NSNotFound`来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而Swift 的可选可以让你暗示任意类型的值缺失,并不需要一个特殊值。
> 注意C 和 Objective-C 中并没有可选这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回`nil``nil`表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型Objective-C 方法一般会返回一个特殊值(比如`NSNotFound`来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而Swift 的可选可以让你暗示任意类型的值缺失,并不需要一个特殊值。
来看一个例子。Swift 的`String`类型有一个叫做`toInt`的方法,作用是将一个`String`值转换成一个`Int`值。然而,并不是所有的字符串都可以转换成一个整数。字符串`"123"`可以被转换成数字`123`,但是字符串`"hello, world"`不行。
@ -425,7 +408,7 @@ Swift 有一个基本的布尔类型,叫做`Bool`。布尔值是指逻辑,
let possibleNumber = "123"
let convertedNumber = possibleNumber.toInt()
// convertedNumber 被推为类型 "Int?" 或者类型 "optional Int"
// convertedNumber 被推为类型 "Int?" 或者类型 "optional Int"
因为`toInt`方法可能会失败,所以它返回一个可选的`Int`,而不是一个`Int`。一个可选的`Int`被写作`Int?`而不是`Int`。问号暗示包含的值是可选,也就是说可能包含`Int`值也可能不包含值。(不能包含其他任何值比如`Bool`值或者`String`值。只能是`Int`或者什么都没有。)

View File

@ -14,31 +14,36 @@
- 字符串大小写
- Unicode
**String** 是一个有序的字符集合,例如 "hello, world", "albatross"。
Swift 字符串通过 **String** 类型来表示,也可以表示为 **Character** 类型值的集合。
---
**String** 是例如 "hello, world", "海贼王" 这样的有序的 **Character** (字符) 类型的值的集合,通过 **String** 类型来表示。
Swift 的 **String****Character** 类型提供了一个快速的,兼容 Unicode 的方式来处理代码中的文本信息。
创建和操作字符串的语法与 C的操作方式相似,轻量并且易读。
创建和操作字符串的语法与 C 语言中字符串操作相似,轻量并且易读。
字符串连接操作只需要简单地通过 `+` 号将两个字符串相连即可。
与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。
尽管语法简易,但 **String** 类型是一种快速、现代化的字符串实现。
每一个字符串都是由独立编码的 Unicode 字符组成,并提供了用于访问这些字符在不同Unicode表示的支持。
每一个字符串都是由独立编码的 Unicode 字符组成,并提供了不同 Unicode 表示 (representations) 来访问这些字符的支持。
**String** 也可以用于在常量、变量、字面量和表达式中进行字符串插值,这使得创建用于展示、存储和打印的字符串变得轻松自如
Swift可以在常量、变量、字面量和表达式中进行字符串插值操作,可以轻松创建用于展示、存储和打印的自定义字符串。
> 注意:
>
> Swift 的 **String** 类型与 Foundation NSString 类进行了无缝桥接。
> 如果您利用 Cocoa 或 Cocoa Touch 中的 Foundation 框架进行工作,整个 NSString API 都可以调用您创建的任意 String 类型的值,您额外还可以在任意 API 中使用本章介绍的 **String** 特性
> 您也可以在任意要求传入NSString 实例作为参数的 API 中使用 **String** 类型的值进行替换
> 如果您利用 Cocoa 或 Cocoa Touch 中的 Foundation 框架进行工作。
> 所有 **NSString** API 都可以调用您创建的任意 **String** 类型的值。
> 除此之外,还可以使用本章介绍的 **String** 特性。
> 您也可以在任意要求传入 **NSString** 实例作为参数的 API 中使用 **String** 类型的值作为替代。
>
>更多关于在 Foundation 和 Cocoa 中使用 **String** 的信息请查看 [Using Swift with Cocoa and Objective-C](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)。
---
### 字符串字面量
您可以在您的代码中包含一段预定义的字符串值作为字符串字面量。
字符串字面量是由双引号包裹着的具有固定顺序的文本字符集。
字符串字面量是由双引号 ("") 包裹着的具有固定顺序的文本字符集。
字符串字面量可以用于为常量和变量提供初始值。
@ -48,47 +53,51 @@ let someString = "Some string literal value"
> 注意:
>
> `someString` 变量通过字符串字面量进行初始化Swift 因此推断为 **String** 类型。
> `someString` 变量通过字符串字面量进行初始化Swift 因此推断该变量为 **String** 类型。
字符串字面量可以包含以下特殊字符:
*移特殊字符 `\0` (空字符)、`\\`(反斜线)、`\t` (水平制表符)、`\n` (换行符)、`\r` (回车符)、`\"` (双引号)、`\'` (单引号)。
* 单字节 Unicode 标量,写成 `\xnn`,其中 nn 为两位十六进制数。
* 双字节 Unicode 标量,写成 `\unnnn`,其中 nnnn 为四位十六进制数。
* 四字节 Unicode 标量,写成 `\Unnnnnnnn`,其中 nnnnnnnn 为八位十六进制数。
*字符 `\0` (空字符)、`\\`(反斜线)、`\t` (水平制表符)、`\n` (换行符)、`\r` (回车符)、`\"` (双引号)、`\'` (单引号)。
* 单字节 Unicode 标量,写成 `\xnn`,其中 `nn` 为两位十六进制数。
* 双字节 Unicode 标量,写成 `\unnnn`,其中 `nnnn` 为四位十六进制数。
* 四字节 Unicode 标量,写成 `\Unnnnnnnn`,其中 `nnnnnnnn` 为八位十六进制数。
下面的代码为各种特殊字符的使用示例。
`wiseWords` 常量包含了两个转移特殊字符 (双括号)`dollarSign``blackHeart``sparklingHeart` 常量演示了三种不同格式的 Unicode 标量:
`wiseWords` 常量包含了两个转移特殊字符 (双括号)
`dollarSign``blackHeart``sparklingHeart` 常量演示了三种不同格式的 Unicode 标量:
```
let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
// "Imagination is more important than knowledge" - Einstein
let dollarSign = "\x24" // $, Unicode scalar U+0024
let blackHeart = "\u2665" // ♥, Unicode scalar U+2665
let sparklingHeart = "\U0001F496" // 💖, Unicode scalar U+1F496
let wiseWords = "\"我是要成为海贼王的男人\" - 路飞"
// "我是要成为海贼王的男人" - 路飞
let dollarSign = "\x24" // $, Unicode 标量 U+0024
let blackHeart = "\u2665" // ♥, Unicode 标量 U+2665
let sparklingHeart = "\U0001F496" // 💖, Unicode 标量 U+1F496
```
---
### 初始化空字符串
为了构造一个很长的字符串,可以创建一个空字符串作为初始值。
可以将空的字符串字面量赋值给变量,也可以初始化一个新的 **String** 实例:
```
var emptyString = "" // empty string literal
var anotherEmptyString = String() // initializer syntax
// 两个字符串为空,并且两者等价
var emptyString = "" // 空字符串字面量
var anotherEmptyString = String() // 初始化 String 实例
// 两个字符串为空等价
```
您可以通过检查其 **Boolean** 类型的 `isEmpty` 属性来判断该字符串是否为空:
```
if emptyString.isEmpty {
println("Nothing to see here")
println("什么都没有")
}
// 打印 "Nothing to see here"
// 输出 "什么都没有"
```
---
### 字符串可变性
您可以通过将一个特定字符串分配给一个变量来对其进行修改,或者分配给一个常量来保证其不会被修改:
@ -99,32 +108,37 @@ variableString += " and carriage"
// variableString 现在为 "Horse and carriage"
let constantString = "Highlander"
constantString += " and another Highlander"
// 这会报告一个编译错误(compile-time error) - 常量不可以被修改。
// 这会报告一个编译错误 (compile-time error) - 常量不可以被修改。
```
> 注意:
>
> 在 Objective-C 和 Cocoa 中,您通过选择两个不同的类( NSString 和 NSMutableString )来指定该字符串是否可以被修改Swift中的字符串是否可以修改仅通过定义的是变量还是常量来决定实现了多种类型可变性操作的统一。
> 在 Objective-C 和 Cocoa 中,您通过选择两个不同的类( `NSString``NSMutableString` )来指定该字符串是否可以被修改Swift 中的字符串是否可以修改仅通过定义的是变量还是常量来决定,实现了多种类型可变性操作的统一。
---
### 字符串是值类型
Swift 的 **String** 类型是值类型。
如果您创建了一个新的字符串,那么当其进行常量、变量赋值操作或在函数/方法中传递时,会进行值拷贝。
任何情况下,都会对已有字符串值创建新副本,并对该新副本进行传递或赋值。
任何情况下,都会对已有字符串值创建新副本,并对该新副本进行传递或赋值操作
值类型在 [Structures and Enumerations Are Value Types](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ClassesAndStructures.html#//apple_ref/doc/uid/TP40014097-CH13-XID_104) 中进行了说明。
> 注意:
>
> 与 Cocoa 中的 NSString 不同,当您在 Cocoa 中创建了一个 NSString 实例,并将其传递给一个函数/方法,或者赋值给一个变量,您永远都是传递或赋值同一个 NSString 实例的一个引用,除非您特别要求进行值拷贝,否则字符串不会进行赋值新副本操作。
> 与 Cocoa 中的 NSString 不同,当您在 Cocoa 中创建了一个 NSString 实例,并将其传递给一个函数/方法,或者赋值给一个变量,您传递或赋值的是该 NSString 实例的一个引用,除非您特别要求进行值拷贝,否则字符串不会生成新的副本来进行赋值操作。
Swift 默认字符串拷贝的方式保证了在函数/方法中传递的是字符串的值,其明确了无论该值来自于哪里,都是您独自拥有的
Swift 默认字符串拷贝的方式保证了在函数/方法中传递的是字符串的值。
很明显无论该值来自于哪里,都是您独自拥有的。
您可以放心您传递的字符串本身不会被更改。
在实际编译时Swift编译器会优化字符串的使用使实际的复制只发生在绝对必要的情况下这意味着您始终可以将字符串作为值类型的同时获得极高的性能。
在实际编译时Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着您将字符串作为值类型的同时可以获得极高的性能。
#### 使用字符(Characters)
---
Swift 的 **String** 类型表示特定序列的字符值的集合。
### 使用字符(Characters)
Swift 的 **String** 类型表示特定序列的 **Character** (字符) 类型值的集合。
每一个字符值代表一个 Unicode 字符。
您可利用 for-in 循环来遍历字符串中的每一个字符:
@ -147,9 +161,11 @@ for-in 循环在[For Loops](https://developer.apple.com/library/prerelease/ios/d
let yenSign: Character = "¥"
```
---
### 计算字符数量
通过调用全局 `countElements` 函数并将字符串作为参数进行传递可以获取该字符串的字符数量。
通过调用全局 `countElements` 函数并将字符串作为参数进行传递可以获取该字符串的字符数量。
```
let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
@ -168,9 +184,11 @@ println("unusualMenagerie has \(countElements(unusualMenagerie)) characters")
> NSString 的 `length` 属性是基于利用 UTF-16 表示的十六位代码单元数字,而不是基于 Unicode 字符。
> 为了解决这个问题NSString 的 `length` 属性在被 Swift的 **String** 访问时会成为 `utf16count`。
---
### 连接字符串和字符
字符串和字符的值可以通过加法运算符 (+) 相加在一起并创建一个新的字符串值:
字符串和字符的值可以通过加法运算符 (`+`) 相加在一起并创建一个新的字符串值:
```
let string1 = "hello"
@ -200,15 +218,17 @@ welcome += character1
>
>您不能将一个字符串或者字符添加到一个已经存在的字符变量上,因为字符变量只能包含一个字符。
---
### 字符串插值
字符串插值是一种全新的构建字符串的方式,可以在其中包含常量、变量、字面量和表达式。
字符串插值是一种构建字符串的方式,可以在其中包含常量、变量、字面量和表达式。
您插入的字符串字面量的每一项都被包裹在以反斜线为前缀的圆括号中:
```
let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message is "3 times 2.5 is 7.5"
let message = "\(multiplier) 乘以 2.5 \(Double(multiplier) * 2.5)"
// message is "3 乘以 2.5 7.5"
```
在上面的例子中,`multiplier` 作为 `\(multiplier)` 被插入到一个字符串字面量中。
@ -220,7 +240,9 @@ let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
>注意:
>
>您插值字符串中写在括号中的表达式不能包含非转义双引号 (") 和反斜杠 (\\),并且不能包含回车或换行符。
>您插值字符串中写在括号中的表达式不能包含非转义双引号 (`"`) 和反斜杠 (`\`),并且不能包含回车或换行符。
---
### 比较字符串
@ -231,12 +253,12 @@ Swift 提供了三种方式来比较字符串的值:字符串相等,前缀
如果两个字符串以同一顺序包含完全相同的字符,则认为两者字符串相等:
```
let quotation = "We're a lot alike, you and I."
let sameQuotation = "We're a lot alike, you and I."
let quotation = "我们是一样一样滴."
let sameQuotation = "我们是一样一样滴."
if quotation == sameQuotation {
println("These two strings are considered equal")
println("这两个字符串被认为是相同的")
}
// prints "These two strings are considered equal"
// prints "这两个字符串被认为是相同的"
```
##### 前缀/后缀相等
@ -278,7 +300,7 @@ println("There are \(act1SceneCount) scenes in Act 1")
##### 大写和小写字符串
您可以通过字符串的 `uppercaseString``lowercaseString` 属性来访问一个字符串的大写/小写版本。
您可以通过字符串的 `uppercaseString``lowercaseString` 属性来访问大写/小写版本的字符串
```
let normal = "Could you help me, please?"
@ -288,12 +310,14 @@ let whispered = normal.lowercaseString
// whispered 值为 "could you help me, please?"
```
---
### Unicode
Unicode 是文本编码和表示的国际标准
Unicode 是一个国际标准,用于文本编码和表示。
它使您可以用标准格式表示来自任意语言几乎所有的字符,并能够对文本文件或网页这样的外部资源中的字符进行读写操作。
Swift 的字符串和字符类型是完全兼容 Unicode 的,它支持如下所述的一系列不同的 Unicode 编码。
Swift 的字符串和字符类型是完全兼容 Unicode 标准的,它支持如下所述的一系列不同的 Unicode 编码。
###### Unicode 术语(Terminology)
@ -315,7 +339,7 @@ Swift 提供了几种不同的方式来访问字符串的 Unicode 表示。
* UTF-16 代码单元集合 (利用字符串的 `utf16` 属性进行访问)
* 21位的 Unicode 标量值集合 (利用字符串的 `unicodeScalars` 属性进行访问)
下面由 `D` `o` `g` `!``🐶` (狗脸表情Unicode 标量为 `U+1F436`)组成的字符串中的每一个字符代表着一种不同的表示:
下面由 `D` `o` `g` `!``🐶` (`DOG FACE`Unicode 标量为 `U+1F436`)组成的字符串中的每一个字符代表着一种不同的表示:
```
let dogString = "Dog!🐶"
@ -324,7 +348,7 @@ let dogString = "Dog!🐶"
##### UTF-8
您可以通过遍历字符串的 `utf8` 属性来访问它的 `UTF-8` 表示。
其为 **UTF8View** 类型的属性,**UTF8View** 是无符号8位 (`UInt8`) 值的集合,每一个 `UIn8` 都是一个字符的 UTF-8 表示:
其为 **UTF8View** 类型的属性,**UTF8View** 是无符号8位 (`UInt8`) 值的集合,每一个 `UInt8` 都是一个字符的 UTF-8 表示:
```
for codeUnit in dogString.utf8 {
@ -335,7 +359,7 @@ print("\n")
```
上面的例子中前四个10进制代码单元值 (68, 111, 103, 33) 代表了字符 `D` `o` `g``!` ,他们的 UTF-8 表示与 ASCII 表示相同。
后四个代码单元值 (240, 159, 144, 182) 是 `狗脸表情` 的4位 UTF-8 表示。
后四个代码单元值 (240, 159, 144, 182) 是 `DOG FACE` 的4位 UTF-8 表示。
##### UTF-16
@ -352,7 +376,7 @@ print("\n")
同样,前四个代码单元值 (68, 111, 103, 33) 代表了字符 `D` `o` `g``!` ,他们的 UTF-16 代码单元和 UTF-8 完全相同。
第五和第六个代码单元值 (55357 and 56374) 是 `狗脸表情` 字符的UTF-16 表示。
第五和第六个代码单元值 (55357 and 56374) 是 `DOG FACE` 字符的UTF-16 表示。
第一个值为 `U+D83D` (十进制值为 55357),第二个值为 `U+DC36` (十进制值为 56374)。
##### Unicode 标量 (Scalars)
@ -373,7 +397,7 @@ print("\n")
同样,前四个代码单元值 (68, 111, 103, 33) 代表了字符 `D` `o` `g``!`
第五位数值128054是一个十六进制1F436的十进制表示。
其等同于 `狗脸表情` 的Unicode 标量 U+1F436。
其等同于 `DOG FACE` 的Unicode 标量 U+1F436。
作为查询字符值属性的一种替代方法,每个 `UnicodeScalar` 值也可以用来构建一个新的字符串值,比如在字符串插值中使用:

View File

@ -14,7 +14,7 @@ Swift统一的函数语法足够灵活可以用来表示任何函数包括
在Swift中每个函数都有一种类型包括函数的参数值类型和返回值类型。你可以把函数类型当做任何其他普通变量类型一样处理这样就可以更简单地把函数当做别的函数的参数也可以从其他函数中返回函数。函数的定义可以写在在其他函数定义中这样可以在嵌套函数范围内实现功能封装。
## 函数的定义与调用
## 函数的定义与调用Defining and Calling Functions
当你定义一个函数时你可以定义一个或多个有名字和类型的值作为函数的输入称为参数parameters也可以定义某种类型的值作为函数执行结束的输出称为返回类型
@ -50,4 +50,440 @@ Swift统一的函数语法足够灵活可以用来表示任何函数包括
println(sayHelloAgain("Anna"))
// prints "Hello again, Anna!
## 函数参数与返回值
## 函数参数与返回值Function Parameters and Return Values
函数参数与返回值在Swift中极为灵活。你可以定义任何类型的函数包括从只带一个未名参数的简单函数到复杂的带有表达性参数名和不同参数选项的复杂函数。
### 多重输入参数Multiple Input Parameters
函数可以有多个输入参数,写在圆括号中,用逗号分隔。
下面这个函数用一个半开区间的开始点和结束点,计算出这个范围内包含多少数字:
func halfOpenRangeLength(start: Int, end: Int) -> Int {
return end - start
}
println(halfOpenRangeLength(1, 10))
// prints "9
### 无参函数Functions Without Parameters
函数可以没有参数。下面这个函数就是一个无参函数,当被调用时,它返回固定的`String`消息:
func sayHelloWorld() -> String {
return "hello, world"
}
println(sayHelloWorld())
// prints "hello, world
尽管这个函数没有参数,但是定义中在函数名后还是需要一对圆括号。当被调用时,也需要在函数名后写一对圆括号。
### 无返回值函数Functions Without Return Values
函数可以没有返回值。下面是`sayHello`函数的另一个版本,叫`waveGoodbye`,这个函数直接输出`String`值,而不是返回它:
func sayGoodbye(personName: String) {
println("Goodbye, \(personName)!")
}
sayGoodbye("Dave")
// prints "Goodbye, Dave!
因为这个函数不需要返回值,所以这个函数的定义中没有返回箭头(->)和返回类型。
> 注意:
> 严格上来说,虽然没有返回值被定义,`sayGoodbye`函数依然返回了值。没有定义返回类型的函数会返回特殊的值,叫`Void`。它其实是一个空的元组tuple没有任何元素可以写成`()`。
被调用时,一个函数的返回值可以被忽略:
func printAndCount(stringToPrint: String) -> Int {
println(stringToPrint)
return countElements(stringToPrint)
}
func printWithoutCounting(stringToPrint: String) {
printAndCount(stringToPrint)
}
printAndCount("hello, world")
// prints "hello, world" and returns a value of 12
printWithoutCounting("hello, world")
// prints "hello, world" but does not return a value
第一个函数`printAndCount`,输出一个字符串并返回`Int`类型的字符数。第二个函数`printWithoutCounting`调用了第一个函数,但是忽略了它的返回值。当第二个函数被调用时,消息依然会由第一个函数输出,但是返回值不会被用到。
> 注意:
> 返回值可以被忽略但定义了有返回值的函数必须返回一个值如果在函数定义底部没有返回任何值这叫导致编译错误compile-time error
### 多重返回值函数Functions with Multiple Return Values
你可以用元组tuple类型让多个值作为一个复合值从函数中返回。
下面的这个例子中,`count`函数用来计算一个字符串中元音,辅音和其他字母的个数(基于美式英语的标准)。
func count(string: String) -> (vowels: Int, consonants: Int, others: Int) {
var vowels = 0, consonants = 0, others = 0
for character in string {
switch String(character).lowercaseString {
case "a", "e", "i", "o", "u":
++vowels
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
++consonants
default:
++others
}
}
return (vowels, consonants, others)
}
你可以用`count`函数来处理任何一个字符串,返回的值将是一个包含三个`Int`型值的元组tuple
let total = count("some arbitrary string!")
println("\(total.vowels) vowels and \(total.consonants) consonants")
// prints "6 vowels and 13 consonants
需要注意的是,元组的成员不需要在函数中返回时命名,因为它们的名字已经在函数返回类型有有了定义。
## 函数参数名Function Parameter Names
以上所有的函数都给它们的参数定义了`参数名parameter name`
func someFunction(parameterName: Int) {
// function body goes here, and can use parameterName
// to refer to the argument value for that parameter
}
但是,这些参数名仅在函数体中使用,不能在函数调用时使用。这种类型的参数名被称作`局部参数名local parameter name`,因为它们只能在函数体中使用。
### 外部参数名External Parameter Names
有时候,调用函数时,给每个参数命名是非常有用的,因为这些参数名可以指出各个实参的用途是什么。
如果你希望函数的使用者在调用函数时提供参数名字,那就需要给每个参数除了局部参数名外再定义一个`外部参数名`。外部参数名写在局部参数名之前,用空格分隔。
func someFunction(externalParameterName localParameterName: Int) {
// function body goes here, and can use localParameterName
// to refer to the argument value for that parameter
}
> 注意:
> 如果你提供了外部参数名,那么函数在被调用时,必须使用外部参数名。
以下是个例子,这个函数使用一个`结合者joiner`把两个字符串联在一起:
func join(s1: String, s2: String, joiner: String) -> String {
return s1 + joiner + s2
}
当你调用这个函数时,这三个字符串的用途是不清楚的:
join("hello", "world", ", ")
// returns "hello, world
为了让这些字符串的用途更为明显,我们为`join`函数添加外部参数名:
func join(string s1: String, toString s2: String, withJoiner joiner: String) -> String {
return s1 + joiner + s2
}
在这个版本的`join`函数中,第一个参数有一个叫`string`的外部参数名和`s1`的局部参数名,第二个参数有一个叫`toString`的外部参数名和`s2`的局部参数名,第三个参数有一个叫`withJoiner`的外部参数名和`joiner`的局部参数名。
现在,你可以使用这些外部参数名以一种清晰地方式来调用函数了:
join(string: "hello", toString: "world", withJoiner: ", ")
// returns "hello, world
使用外部参数名让第二个版本的`join`函数的调用更为有表现力,更为通顺,同时还保持了函数体是可读的和有明确意图的。
> 注意:
> 当其他人在第一次读你的代码,函数参数的意图显得不明显时,考虑使用外部参数名。如果函数参数名的意图是很明显的,那就不需要定义外部参数名了。
### 简写外部参数名Shorthand External Parameter Names
如果你需要提供外部参数名,但是局部参数名已经定义好了,那么你不需要写两次这些参数名。相反,只写一次参数名,并用`井号(#`作为前缀就可以了。这告诉Swift使用这个参数名作为局部和外部参数名。
下面这个例子定义了一个叫`containsCharacter`的函数,使用`井号(#`的方式定义了外部参数名:
func containsCharacter(#string: String, #characterToFind: Character) -> Bool {
for character in string {
if character == characterToFind {
return true
}
}
return false
}
这样定义参数名,使得函数体更为可读,清晰,同时也可以以一个不含糊的方式被调用:
let containsAVee = containsCharacter(string: "aardvark", characterToFind: "v")
// containsAVee equals true, because "aardvark" contains a "v”
### 默认参数值Default Parameter Values
你可以在函数体中为每个参数定义`默认值`。当默认值被定义后,调用这个函数时可以略去这个参数。
> 注意:
> 将带有默认值的参数放在函数参数表的最后。这样可以保证在函数调用时,非默认参数的顺序是一致的,同时使得相同的函数在不同情况下调用时显得更为清晰。
以下是另一个版本的`join`函数,其中`joiner`有了默认参数值:
func join(string s1: String, toString s2: String, withJoiner joiner: String = " ") -> String {
return s1 + joiner + s2
}
像第一个版本的`join`函数一样,如果`joiner`被幅值时,函数将使用这个字符串值来连接两个字符串:
join(string: "hello", toString: "world", withJoiner: "-")
// returns "hello-world
当这个函数被调用时,如果`joiner`的值没有被指定,函数会使用默认值(" "
join(string: "hello", toString: "world", withJoiner: "-")
// returns "hello-world
### 默认值参数的外部参数名External Names for Parameters with Default Values
在大多数情况下,给带默认值的参数起一个外部参数名是很有用的。这样可以保证当函数被调用且带默认值的参数被提供值时,实参的意图是明显的。
为了使定义外部参数名更加简单当你未给带默认值的参数提供外部参数名时Swift会自动提供外部名字。此时外部参数名与局部名字是一样的就像你已经在局部参数名前写了`井号(#`一样。
下面是`join`函数的另一个版本,这个版本中并没有为它的参数提供外部参数名,但是`joiner`参数依然有外部参数名:
func join(s1: String, s2: String, joiner: String = " ") -> String {
return s1 + joiner + s2
}
在这个例子中Swift自动为`joiner`提供了外部参数名。因此,当函数调用时,外部参数名必须使用,这样使得参数的用途变得清晰。
func join(s1: String, s2: String, joiner: String = " ") -> String {
return s1 + joiner + s2
}
> 注意:
> 你可以使用`下划线_`作为默认值参数的外部参数名,这样可以在调用时不用提供外部参数名。但是给带默认值的参数命名总是更加合适的。
### 可变参数Variadic Parameters
一个`可变参数variadic parameter`可以接受一个或多个值。函数调用时,你可以用可变参数来传入不确定数量的输入参数。通过在变量类型名后面加入`...`的方式来定义可变参数。
传入可变参数的值在函数体内当做这个类型的一个数组。例如,一个叫做`numbers``Double...`型可变参数,在函数体内可以当做一个叫`numbers``Double[]`型的数组常量。
下面的这个函数用来计算一组任意长度数字的算术平均数:
func arithmeticMean(numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8, 19)
// returns 10.0, which is the arithmetic mean of these three numbers
> 注意:
> 一个函数至多能有一个可变参数,而且它必须是参数表中最后的一个。这样做是为了避免函数调用时出现歧义。
如果函数有一个或多个带默认值的参数,而且还有一个可变参数,那么把可变参数放在参数表的最后。
### 常量参数和变量参数Constant and Variable Parameters
函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。这意味着你不能错误地更改参数值。
但是,有时候,如果函数中有传入参数的变量值副本将是很有用的。你可以通过指定一个或多个参数为变量参数,从而避免自己在函数中定义新的变量。变量参数不是常量,你可以在函数中把它当做新的可修改副本来使用。
通过在参数名前加关键字`var`来定义变量参数:
func alignRight(var string: String, count: Int, pad: Character) -> String {
let amountToPad = count - countElements(string)
for _ in 1...amountToPad {
string = pad + string
}
return string
}
let originalString = "hello"
let paddedString = alignRight(originalString, 10, "-")
// paddedString is equal to "-----hello"
// originalString is still equal to "hello”
这个例子中定义了一个新的叫做`alignRight`的函数,用来右对齐输入的字符串到一个长的输出字符串中。左侧空余的地方用指定的填充字符填充。这个例子中,字符串`"hello"`被转换成了`"-----hello"`
`alignRight`函数将参数`string`定义为变量参数。这意味着`string`现在可以作为一个局部变量,用传入的字符串值初始化,并且可以在函数体中进行操作。
该函数首先计算出多少个字符需要被添加到`string`的左边,以右对齐到总的字符串中。这个值存在局部常量`amountToPad`中。这个函数然后将`amountToPad`多的填充pad字符填充到`string`左边,并返回结果。它使用了`string`这个变量参数来进行所有字符串操作。
> 注意:
> 对变量参数所进行的修改在函数调用结束后变消息了,并且对于函数体外是不可见的。变量参数仅仅存在于函数调用的生命周期中。
### 输入输出参数In-Out Parameters
变量参数正如上面所述仅仅能在函数体内被更改。如果你想要一个函数可以修改参数的值并且想要在这些修改在函数调用结束后仍然存在那么就应该把这个参数定义为输入输出参数In-Out Parameters
定义一个输入输出参数时,在参数定以前加`inout`关键字。一个输入输出参数有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。
你只能传入一个变量作为输入输出参数。你不能传入常量或者字面量literal value因为这些量是不能被修改的。当传入的参数作为输入输出参数时需要在参数前加`&`符,表示这个值可以被函数修改。
> 注意:
> 输入输出参数不能有默认值,而且变量参数不能用`inout`标记。如果你用`inout`标记一个参数,这个参数不能别`var`或者`let`标记。
下面是例子,`swapTwoInts`函数,有两个分别叫做`a``b`的输出输出参数:
func swapTwoInts(inout a: Int, inout b: Int) {
let temporaryA = a
a = b
b = temporaryA
}
这个`swapTwoInts`函数仅仅交换`a``b`的值。该函数先将`a`的值存到一个暂时常量`temporaryA`中,然后将`b`的值赋给`a`,最后将`temporaryA`幅值给`b`
你可以用两个`Int`型的变量来调用`swapTwoInts`。需要注意的是,`someInt``anotherInt`在传入`swapTwoInts`函数前,都加了`&`的前缀:
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
println("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// prints "someInt is now 107, and anotherInt is now 3”
从上面这个例子中,我们可以看到`someInt``anotherInt`的原始值在`swapTwoInts`函数中被修改,尽管它们的定义在函数体外。
> 注意:
> 输出输出参数和返回值是不一样的。上面的`swapTwoInts`函数并没有定义任何返回值,但仍然修改了`someInt`和`anotherInt`的值。输入输出参数是函数对函数体外产生影响的另一种方式。
## 函数类型Function Types
每个函数都有种特定的函数类型,由函数的参数类型和返回类型组成。
例如:
func addTwoInts(a: Int, b: Int) -> Int {
return a + b
}
func multiplyTwoInts(a: Int, b: Int) -> Int {
return a * b
}
这个例子中定义了两个简单的数学函数:`addTwoInts``multiplyTwoInts`。这两个函数都传入两个`Int`类型, 返回一个合适的`Int`值。
这两个函数的类型是`(Int, Int) -> Int`,可以读作“这个函数类型,它有两个`Int`型的参数并返回一个`Int`型的值。”。
下面是另一个例子,一个没有参数,也没有返回值的函数:
func printHelloWorld() {
println("hello, world")
}
这个函数的类型是:`() -> ()`,或者叫“没有参数,并返回`Void`类型的函数。”。没有指定返回类型的函数总返回 `Void`。在Swift中`Void`与空的元组是一样的。
### 使用函数类型Using Function Types
在Swift中使用函数类型就像使用其他类型一样。例如你可以定义一个常量或变量它的类型是函数并且可以幅值为一个函数
var mathFunction: (Int, Int) -> Int = addTwoInts
这个可以读作:
“定义一个叫做`mathFunction`的变量,类型是‘一个有两个`Int`型的参数并返回一个`Int`型的值的函数’,并让这个新变量指向`addTwoInts`函数”。
`addTwoInts``mathFunction`有同样的类型所以这个赋值过程在Swift类型检查中是允许的。
现在,你可以用`mathFunction`来调用被赋值的函数了:
println("Result: \(mathFunction(2, 3))")
// prints "Result: 5
有相同匹配类型的不同函数可以被赋值给同一个变量,就像非函数类型的变量一样:
mathFunction = multiplyTwoInts
println("Result: \(mathFunction(2, 3))")
// prints "Result: 6"
就像其他类型一样当赋值一个函数给常量或变量时你可以让Swift来推测其函数类型
let anotherMathFunction = addTwoInts
// anotherMathFunction is inferred to be of type (Int, Int) -> Int
### 函数类型作为参数类型Function Types as Parameter Types
你可以用`(Int, Int) -> Int`这样的函数类型作为另一个函数的参数类型。这样你可以将函数的一部分实现交由给函数的调用者。
下面是另一个例子,正如上面的函数一样,同样是输出某种数学运算结果:
func printMathResult(mathFunction: (Int, Int) -> Int, a: Int, b: Int) {
println("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// prints "Result: 8”
这个例子定义了`printMathResult`函数,它有三个参数:第一个参数叫`mathFunction`,类型是`(Int, Int) -> Int`,你可以传入任何这种类型的函数;第二个和第三个参数叫`a``b`,它们的类型都是`Int`,这两个值作为已给的函数的输入值。
`printMathResult`被调用时,它被传入`addTwoInts`函数和整数`3``5`。它用传入`3``5`调用`addTwoInts`,并输出结果:`8`
`printMathResult`函数的作用就是输出另一个合适类型的数学函数的调用结果。它不关心传入函数是如何实现的,它只关心这个传入的函数类型是正确的。这使得`printMathResult`可以以一种类型安全type-safe的方式来保证传入函数的调用是正确的。
### 函数类型作为返回类型Function Type as Return Types
你可以用函数类型作为另一个函数的返回类型。你需要做的是在返回箭头(`->`)后写一个完整的函数类型。
下面的这个例子中定义了两个简单函数,分别是`stepForward``stepBackward``stepForward`函数返回一个比输入值大一的值。`stepBackward`函数返回一个比输入值小一的值。这两个函数的类型都是`(Int) -> Int`
func stepForward(input: Int) -> Int {
return input + 1
}
func stepBackward(input: Int) -> Int {
return input - 1
}
下面这个叫做`chooseStepFunction`的函数,它的返回类型是`(Int) -> Int`的函数。`chooseStepFunction`根据布尔值`backwards`来返回`stepForward`函数或`stepBackward`函数:
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
return backwards ? stepBackward : stepForward
}
你现在可以用`chooseStepFunction`来获得一个函数,不管是那个方向:
var currentValue = 3
let moveNearerToZero = chooseStepFunction(currentValue > 0)
// moveNearerToZero now refers to the stepBackward() function
上面这个例子中计算出从`currentValue`逐渐接近到`0`是需要向正数走还是向负数走。`currentValue`的初始值是`3`,这意味着`currentValue > 0`是真的(`true`),这将使得`chooseStepFunction`返回`stepBackward`函数。一个指向返回的函数的引用保存在了`moveNearerToZero`常量中。
现在,`moveNearerToZero`指向了正确的函数,它可以被用来数到`0`
println("Counting to zero:")
// Counting to zero:
while currentValue != 0 {
println("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
println("zero!")
// 3...
// 2...
// 1...
// zero!
## 嵌套函数Nested Functions
这章中你所见到的所有函数都叫全局函数global functions它们定义在全局域中。你也可以把函数定义在别的函数体中称作嵌套函数nested functions
默认情况下嵌套函数是对外界不可见的但是可以被他们封闭函数enclosing function来调用。一个封闭函数也可以返回它的某一个嵌套函数使得这个函数可以在其他域中被使用。
你可以用返回嵌套函数的方式重写`chooseStepFunction`函数:
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backwards ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(currentValue > 0)
// moveNearerToZero now refers to the nested stepForward() function
while currentValue != 0 {
println("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
println("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!

View File

@ -0,0 +1,208 @@
# 枚举
本页内容包含:
- 枚举语法
- 匹配枚举值与`Swith`语句
- 实例值associated values
- 原始值raw values
枚举enumeration定义了一个通用类型的一组相关的值使你可以在你的代码中以一个安全的方式来使用这些值。
如果你熟悉C语言你就会知道在C语言中枚举指定相关名称为一组整型值。Swift中的枚举更加灵活不必给每一个枚举成员enumeration member提供一个值。如果一个值被认为是“原始”值被提供给每个枚举成员则该值可以是一个字符串一个字符或是一个整型值或浮点值。
此外枚举成员可以指定任何类型的实例值存储到枚举成员值中就像其他语言中的联合体unions和变体variants。你可以定义一组通用的相关成员作为枚举的一部分每一组都有不同的一组与它相关的适当类型的数值。
在Swift中枚举类型是一等first-class类型。它们采用了很多传统上只被类class)所支持的特征例如计算型属性computed properties)用于提供关于枚举当前值的附加信息 实例方法instance methods用于提供和枚举所代表的值相关联的功能。枚举也可以定义构造函数initializers来提供一个初始成员值可以在原始的实现基础上扩展它们的功能可以遵守协议protocols来提供标准的功能。
欲了解更多相关功能请参见属性Properties方法Methods构造过程Initialization扩展Extensions和协议Protocols
## 枚举语法
使用`enum`关键词并且把它们的整个定义放在一对大括号内:
enum SomeEumeration {
// enumeration definition goes here
}
以下是指南针四个方向的一个例子:
enum CompassPoint {
case North
case South
case East
case West
}
一个枚举中被定义的值(例如 `North``South``East``West`)是枚举的***成员值***(或者***成员***)。`case`关键词表明新的一行成员值将被定义。
> 注意:
> 不像C和Objective-C一样Swift的枚举成员在被创建时不会被赋予一个默认的整数值。在上面的`CompassPoints`例子中,`North``South``East`和`West`不是隐式得等于`0``1``2`和`3`。相反的,这些不同的枚举成员在`CompassPoint`的一种显示定义中拥有各自不同的值。
多个成员值可以出现在同一行上,用逗号隔开:
enum Planet {
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Nepturn
}
每个枚举定义了一个全新的类型。像Swift中其他类型一样它们的名字例如`CompassPoint``Planet`)必须以一个大写字母开头。给枚举类型起一个单数名字而不是复数名字,以便于读起来更加容易理解:
var directionToHead = CompassPoint.West
`directionToHead`的类型被推断当它被`CompassPoint`的一个可能值初始化。一旦`directionToHead`被声明为一个`CompassPoint`,你可以使用更短的点(.)语法将其设置为另一个`CompassPoint`的值:
directionToHead = .East
`directionToHead`的类型已知时,当设定它的值时,你可以不再写类型名。使用显示类型的枚举值可以让代码具有更好的可读性。
## 匹配枚举值和`Switch`语句
你可以匹配单个枚举值和`switch`语句:
directionToHead = .South
switch directionToHead {
case .North:
println("Lots of planets have a north")
case .South:
println("Watch out for penguins")
case .East:
println("Where the sun rises")
case .West:
println("Where the skies are blue")
}
// prints "Watch out for penguins”
你可以如此理解这段代码:
“考虑`directionToHead`的值。当它等于`.North`,打印`“Lots of planets have a north”`。当它等于`.South`,打印`“Watch out for penguins”`。”
等等依次类推。
正如在控制流Control Flow中介绍当考虑一个枚举的成员们时一个`switch`语句必须全面。如果忽略了`.West`这种情况,上面那段代码将无法通过编译,因为它没有考虑到`CompassPoint`的全部成员。全面性的要求确保了枚举成员不会被意外遗漏。
当不需要匹配每个枚举成员的时候,你可以提供一个默认`default`分支来涵盖所有未明确被提出的任何成员:
let somePlanet = Planet.Earth
switch somePlanet {
case .Earth:
println("Mostly harmless")
default:
println("Not a safe place for humans")
}
// prints "Mostly harmless”
## 实例值Associated Values
上一小节的例子演示了一个枚举的成员是如何被定义(分类)的。你可以为`Planet.Earth`设置一个常量或则变量,并且在之后查看这个值。然而,有时候会很有用如果能够把其他类型的实例值和成员值一起存储起来。这能让你随着成员值存储额外的自定义信息,并且当每次你在代码中利用该成员时允许这个信息产生变化。
你可以定义Swift的枚举存储任何类型的实例值如果需要的话每个成员的数据类型可以是各不相同的。枚举的这种特性跟其他语言中的可辨识联合discriminated unions标签联合tagged unions或者变体variants相似。
例如假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有UPC-A格式的一维码它使用数字0到9.每一个条形码都有一个代表“数字系统”的数字该数字后接10个代表“标识符”的数字。最后一个数字是“检查”位用来验证代码是否被正确扫描
<img width="252" height="120" a"" src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/barcode_UPC_2x.png">
其他商品上标有QR码格式的二维码它可以使用任何ISO8859-1字符并且可以编码一个最多拥有2,953字符的字符串:
<img width="169" height="169" alt="" src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/barcode_QR_2x.png">
对于库存跟踪系统来说能够把UPC-A码作为三个整型值的元组和把QR码作为一个任何长度的字符串存储起来是方便的。
在Swift中用来定义两种商品条码的枚举是这样子的
enum Barcode {
case UPCA(Int, Int, Int)
case QRCode(String)
}
以上代码可以这么理解:
“定义一个名为`Barcode`的枚举类型,它可以是`UPCA`的一个实例值(`Int``Int``Int`),或者`QRCode`的一个字符串类型(`String`)实例值。”
这个定义不提供任何`Int``String`的实际值,它只是定义了,当`Barcode`常量和变量等于`Barcode.UPCA``Barcode.QRCode`时,实例值的类型。
然后可以使用任何一种条码类型创建新的条码,如:
var productBarcode = Barcode.UPCA(8, 85909_51226, 3)
以上例子创建了一个名为`productBarcode`的新变量,并且赋给它一个`Barcode.UPCA`的实例元组值`(8, 8590951226, 3)`。提供的“标识符”值在整数字中有一个下划线,使其便于阅读条形码。
同一个商品可以被分配给一个不同类型的条形码,如:
productBarcode = .QRCode("ABCDEFGHIJKLMNOP")
这时,原始的`Barcode.UPCA`和其整数值被新的`Barcode.QRCode`和其字符串值所替代。条形码的常量和变量可以存储一个`.UPCA`或者一个`.QRCode`(连同它的实例值),但是在任何指定时间只能存储其中之一。
像以前那样不同的条形码类型可以使用一个switch语句来检查然而这次实例值可以被提取作为switch语句的一部分。你可以在`switch`的case分支代码中提取每个实例值作为一个常量`let`前缀)或者作为一个变量(用`var`前缀)来使用:
switch productBarcode {
case .UPCA(let numberSystem, let identifier, let check):
println("UPC-A with value of \(numberSystem), \(identifier), \(check).")
case .QRCode(let productCode):
println("QR code with value of \(productCode).")
}
// prints "QR code with value of ABCDEFGHIJKLMNOP.”
如果一个枚举成员的所有实例值被提取为常量,或者它们全部被提取为变量,为了简洁,你可以只放置一个`var`或者`let`标注在成员名称前:
switch productBarcode {
case let .UPCA(numberSystem, identifier, check):
println("UPC-A with value of \(numberSystem), \(identifier), \(check).")
case let .QRCode(productCode):
println("QR code with value of \(productCode).")
}
// prints "QR code with value of ABCDEFGHIJKLMNOP."
## 原始值Raw Values
在实例值小节的条形码例子中演示了一个枚举的成员如何声明它们存储不同类型的实例值。作为实例值的替代,枚举成员可以被默认值(称为原始值)预先填充,其中这些原始值具有相同的类型。
这里是一个枚举成员存储原始ASCII值的例子
enum ASCIIControlCharacter: Character {
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
}
在这里,称为`ASCIIControlCharacter`的枚举的原始值类型被定义为字符型`Character`并被设置了一些比较常见的ASCII控制字符。字符值的描述请详见字符串和字符`Strings and Characters`部分。
注意原始值和实例值是不相同的。当你开始在你的代码中定义枚举的时候原始值是被预先填充的值像上述三个ASCII码。对于一个特定的枚举成员它的原始值始终是相同的。实例值是当你在创建一个基于枚举成员的新常量或变量时才会被设置并且每次当你这么做得时候它的值可以是不同的。
原始值可以是字符串,字符,或者任何整型值或浮点型值。每个原始值在它的枚举声明中必须是唯一的。当整型值被用于原始值,如果其他枚举成员没有值时,它们会自动递增。
下面的枚举是对之前`Planet`这个枚举的一个细化利用原始整型值来表示每个planet在太阳系中的顺序
enum Planet: Int {
case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
自动递增意味着`Planet.Venus`的原始值是`2`,依次类推。
使用枚举成员的`toRaw`方法可以访问该枚举成员的原始值:
let earthsOrder = Planet.Earth.toRaw()
// earthsOrder is 3
使用枚举的`fromRaw`方法来试图找到具有特定原始值的枚举成员。这个例子通过原始值`7`识别`Uranus`
let possiblePlanet = Planet.fromRaw(7)
// possiblePlanet is of type Planet? and equals Planet.Uranus
然而,并非所有可能的`Int`值都可以找到一个匹配的行星。正因为如此,`fromRaw`方法可以返回一个***可选***的枚举成员。在上面的例子中,`possiblePlanet``Planet?`类型,或“可选的`Planet`”。
如果你试图寻找一个位置为9的行星通过`fromRaw`返回的可选`Planet`值将是`nil`
let positionToFind = 9
if let somePlanet = Planet.fromRaw(positionToFind) {
switch somePlanet {
case .Earth:
println("Mostly harmless")
default:
println("Not a safe place for humans")
}
} else {
println("There isn't a planet at position \(positionToFind)")
}
// prints "There isn't a planet at position 9
这个范例使用可选绑定optional binding通过原始值`9`试图访问一个行星。`if let somePlanet = Planet.fromRaw(9)`语句获得一个可选`Planet`,如果可选`Planet`可以被获得,把`somePlanet`设置成该可选`Planet`的内容。在这个范例中,无法检索到位置为`9`的行星,所以`else`分支被执行。

View File

@ -0,0 +1,296 @@
# 方法(Methods)
**方法**是与某些特定类型相关联的功能/函数。类、结构体、枚举都可以定义实例方法;实例方法为指定类型的实例封装了特定的任务与功能。类、结构体、枚举也可以定义类(型)方法(type itself)类型方法与类型自身相关联。类型方法与Objective-C中的类方法(class methods)相似。
在Swift中,结构体和枚举能够定义方法事实上这是Swift与C/Objective-C的主要区别之一。在Objective-C中,类是唯一能定义方法的类型。在Swift中你能够选择是否定义一个类/结构体/枚举,并且你仍然享有在你创建的类型(类/结构体/枚举)上定义方法的灵活性。
### 实例方法(Instance Methods)
**实例方法**是某个特定类、结构体或者枚举类型的实例的方法。实例方法支撑实例的功能: 或者提供方法,以访问和修改实例属性;或者提供与实例的目的相关的功能。实例方法的语法与函数完全一致,参考[函数说明](functions.md "函数说明")。
实例方法要写在它所属的类型的前后括号之间。实例方法能够访问他所属类型的所有的其他实例方法和属性。实例方法只能被它所属的类的特定实例调用。实例方法不能被孤立于现存的实例而被调用。
下面是定义一个很简单的类`Counter`的例子(`Counter`能被用来对一个动作发生的次数进行计数):
```
class Counter {
var count = 0
func increment() {
count++
}
func incrementBy(amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
```
`Counter`类定理了三个实例方法:
- `increment`让计数器按一递增;
- `incrementBy(amount: Int)`让计数器按一个指定的整数值递增;
- `reset`将计数器重置为0。
`Counter`这个类还声明了一个可变属性`count`,用它来保持对当前计数器值的追踪。
和调用属性一样,用点语法(dot syntax)调用实例方法:
```
let counter = Counter()
// the initial counter value is 0
counter.increment()
// the counter's value is now 1
counter.incrementBy(5)
// the counter's value is now 6
counter.reset()
// the counter's value is now 0
```
### 方法的局部参数名称和外部参数名称(Local and External Parameter Names for Methods)
函数参数有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用),参考[External Parameter Names](external_parameter_names.md)。对于方法参数也是这样,因为方法就是函数(只是这个函数与某个类型相关联了)。但是,方法和函数的局部名称和外部名称的默认行为是不一样的。
Swift中的方法和Objective-C中的方法极其相似。像在Objective-C中一样Swift中方法的名称通常用一个介词指向方法的第一个参数比如`with`,`for`,`by`等等。前面的`Counter`类的例子中`incrementBy`方法就是这样的。介词的使用让方法在被调用时能像一个句子一样被解读。Swift这种方法命名约定很容易落实,因为它是用不同的默认处理方法参数的方式,而不是用函数参数(来实现的)。
具体来说Swift默认仅给方法的第一个参数名称一个局部参数名称;但是默认同时给第二个和后续的参数名称局部参数名称和外部参数名称。
这个约定与典型的命名和调用约定相匹配这与你在写Objective-C的方法时很相似。这个约定还让expressive method调用不需要再检查/限定参数名。
看看下面这个`Counter`的替换版本(它定义了一个更复杂的`incrementBy`方法):
```
class Counter {
var count: Int = 0
func incrementBy(amount: Int, numberOfTimes: Int) {
count += amount * numberOfTimes
}
}
```
`incrementBy`方法有两个参数: `amount``numberOfTimes`。默认地Swift只把`amount`当作一个局部名称,但是把`numberOfTimes`即看作本地名称又看作外部名称。下面调用这个方法:
```
let counter = Counter()
counter.incrementBy(5, numberOfTimes: 3)
// counter value is now 15
```
你不必为第一个参数值再定义一个外部变量名:因为从函数名`incrementBy`已经能很清楚地看出它的目的/作用。但是第二个参数,就要被一个外部参数名称所限定,以便在方法被调用时让他目的/作用明确。
这种默认的行为能够有效的检查方法比如你在参数numberOfTimes前写了个井号( `#` )时:
```
func incrementBy(amount: Int, #numberOfTimes: Int) {
count += amount * numberOfTimes
}
```
这种默认行为使上面代码意味着在Swift中定义方法使用了与Objective-C同样的语法风格并且方法将以自然表达式的方式被调用。
### 修改外部参数名称(Modifying External Parameter Name Behavior for Methods)
有时为方法的第一个参数提供一个外部参数名称是非常有用的,尽管这不是默认的行为。你可以自己添加一个明确的外部名称;你也可以用一个hash符号作为第一个参数的前缀然后用这个局部名字作为外部名字。
相反,如果你不想为方法的第二个及后续的参数提供一个外部名称,你可以通过使用下划线(`_`)作为该参数的显式外部名称来覆盖默认行为。
### `self`属性(The self Property)
类型的每一个实例都有一个隐含属性叫做`self`,它完全等同于这个实力变量本身。你可以在一个实例的实例方法中使用这个隐含的`self`属性来引用当前实例。
上面例子中的`increment`方法可以被写成这样:
```
func increment() {
self.count++
}
```
实际上,你不必在你的代码里面经常写`self`。不论何时,在一个方法中使用一个已知的属性或者方法名称,如果你没有明确的写`self`Swift假定你是指当前实例的属性或者方法。这种假定在上面的`Counter`中已经示范了:`Counter`中的三个实例方法中都使用的是`count`(而不是`self.count`)
这条规则的主要例外发生在当实例方法的某个参数名称与实例的某个属性名称相同时。
在这种情况下,参数名称享有优先权,并且在引用属性时必须使用一种更恰当(被限定更严格)的方式。
你可以使用隐藏的`self`属性来区分参数名称和属性名称。
下面的例子演示了`self`消除方法参数`x`和实例属性`x`之间的歧义:
```
struct Point {
var x = 0.0, y = 0.0
func isToTheRightOfX(x: Double) -> Bool {
return self.x > x
}
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOfX(1.0) {
println("This point is to the right of the line where x == 1.0")
}
// prints "This point is to the right of the line where x == 1.0"
```
如果不使用`self`前缀Swift就认为两次使用的`x`都指的是名称为`x`的函数参数。
### 在实例方法中修改值类型(Modifying Value Types from Within Instance Methods)
结构体和枚举是**值类型**[Structures and Enumerations Are Value Types]("#")。一般情况下,值类型的属性不能在他的实例方法中被修改。
但是,如果你确实需要在某个具体的方法中修改结构体或者枚举的属性,你可以选择`变异(mutating)`这个方法。方法可以从内部变异它的属性;并且它做的任何改变在方法结束时都会回写到原始结构。方法会给它隐含的`self`属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例。
`变异`方法, 将关键字`mutating` 放到方法的`func`关键字之前就可以了:
```
struct Point {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveByX(2.0, y: 3.0)
println("The point is now at (\(somePoint.x), \(somePoint.y))")
// prints "The point is now at (3.0, 4.0)"
```
上面的Point结构体定义了一个变异方法(mutating method)`moveByX``moveByX`用来移动一个point。`moveByX`方法在被调用时修改了这个point,而不是返回一个新的point。方法定义是加上那个了`mutating`关键字,所以方法可以修改值类型的属性了。
注意:不能在结构体类型的常量上调用变异方法,因为常量的属性不能被改变,就算你想改变的是常量的可变属性也不行,参考[Stored Properties of Constant Structure Instances]("#")
```
let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveByX(2.0, y: 3.0)
// this will report an error
```
### 在变异方法中给self赋值(Assigning to self Within a Mutating Method)
变异方法能够赋给隐含属性`self`一个全新的实例。上面`Point`的例子可以用下面的方式改写:
```
struct Point {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}
```
新版的变异方法`moveByX`创建了一个新的分支结构(他的x和y的值都被设定为目标值了)。调用这个版本的方法和调用上个版本的最终结果是一样的。
枚举的变异方法可以让`self`从相同的枚举设置为不同的成员。
```
enum TriStateSwitch {
case Off, Low, High
mutating func next() {
switch self {
case Off:
self = Low
case Low:
self = High
case High:
self = Off
}
}
}
var ovenLight = TriStateSwitch.Low
ovenLight.next()
// ovenLight is now equal to .High
ovenLight.next()
// ovenLight is now equal to .Off
```
上面的例子中定义了一个三态开关的枚举。每次调用`next`方法时,开关在不同的电源状态(`Off`,`Low`,`High`)之前循环切换。
### 类型方法(Type Methods)
实例方法是被类型的某个实例调用的方法。你也可以定义类列本身调用的方法,这种方法就叫做**类型方法**。声明类的类型方法,在方法的`func`关键字之前加上关键字`class`;声明结构体和枚举的类型方法,在方法的`func`关键字之前加上关键字`static`
> 注意:
> 在Objective-C里面你只能为Objective-C的类定义类型方法(type-level methods)。在Swift中你可以为所有的类、结构体和枚举定义类型方法Each type method is explicitly scoped to the type it supports.
类型方法和实例方法一样用点语法调用。但是你是在类型上调用这个方法而不是在实例上调用。下面是如何在SomeClass类上调用类型方法的例子
```
class SomeClass {
class func someTypeMethod() {
// type method implementation goes here
}
}
SomeClass.someTypeMethod()
```
在类型方法的方法体(body)中,`self`指向这个类型本身,而不是类型的某个实例。对于结构体和枚举来说,这意味着你可以用`self`来消除静态属性和静态方法参数之间的二意性(类似于我们在前面处理实例属性和实例方法参数时做的那样)。
一般地,在类型方法里面所使用的任何未限定的方法和属性名称,将会指向其他的类型级别的方法和属性。一个类型方法可以用另一个类型方法的名称调用踏,而无需在方法名称前面加上类型名称的前缀。同样,结构体和枚举的类型方法也能够直接通过静态属性的名称访问静态属性,而不需要类型名称前缀。
下面的例子定义了一个名为`LevelTracker`结构体。它监测玩家的发展情况(游戏的不同层次或阶段)。这是一个单人游戏,但也可以用作多玩家游戏中单个设备上的信息存储。
游戏初始时,所有的游戏等级(除了等级1)都被锁定。每次有玩家完成一个等级,这个等级就对这个设备上的所有玩家解锁。`LevelTracker`结构体用静态属性和方法监测游戏的哪个等级已经被解锁。他还监测每个玩家的当前等级。
```
struct LevelTracker {
static var highestUnlockedLevel = 1
static func unlockLevel(level: Int) {
if level > highestUnlockedLevel { highestUnlockedLevel = level }
}
static func levelIsUnlocked(level: Int) -> Bool {
return level <= highestUnlockedLevel
}
var currentLevel = 1
mutating func advanceToLevel(level: Int) -> Bool {
if LevelTracker.levelIsUnlocked(level) {
currentLevel = level
return true
} else {
return false
}
}
}
```
`LevelTracker`监测玩家的已解锁的最高等级。这个值被存储在静态属性`highestUnlockedLevel`中。
`LevelTracker`还定义了两个类型方法与`highestUnlockedLevel`配合工作。第一个类型方法是`unlockLevel`:一旦新等级被解锁,它会更新`highestUnlockedLevel`的值。第二个类型方法是`levelIsUnlocked`:如果某个给定的等级已经被解锁,他返回`true`。(注意:我们没用使用`LevelTracker.highestUnlockedLevel`,这个类型方法还是能够访问静态属性`highestUnlockedLevel`)
除了静态属性和类型方法,`LevelTracker`还监测每个玩家的进度。它用实例属性`currentLevel`来监测玩家当前正在进行的等级。
为了便于管理`currentLevel`属性,`LevelTracker`定义了实例方法`advanceToLevel`。这个方法会在更新`currentLevel`之前检查所请求的新等级是否已经解锁。`advanceToLevel`方法返回布尔值以指示是否确实能够设置`currentLevel`了。
下面,`Player`类使用`LevelTracker`来监测和更新每个玩家的发展进度:
```
class Player {
var tracker = LevelTracker()
let playerName: String
func completedLevel(level: Int) {
LevelTracker.unlockLevel(level + 1)
tracker.advanceToLevel(level + 1)
}
init(name: String) {
playerName = name
}
}
```
`Player`类创建一个新的`LevelTracker`实例来检测这个用户的发展进度。他提供了`completedLevel`方法:一旦玩家完成某个指定等级就调用它。这个方法为所有玩家解锁下一等级,并且将当前玩家的进度更新为下一等级。(我们忽略了`advanceToLevel`返回的布尔值,因为之前调用`LevelTracker.unlockLevel`时就知道了这个等级已经被解锁了)
你还可以为一个新的玩家创建一个`Player`的实例,然后看这个玩家完成等级一时发生了什么:
```
var player = Player(name: "Argyrios")
player.completedLevel(1)
println("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
// prints "highest unlocked level is now 2"
```
如果你创建了第二个玩家,并尝试让他开始一个没有被任何玩家解锁的等级,你试图去设置玩家当前等级时会失败的:
```
player = Player(name: "Beto")
if player.tracker.advanceToLevel(6) {
println("player is now on level 6")
} else {
println("level 6 has not yet been unlocked")
}
// prints "level 6 has not yet been unlocked"
```

View File

@ -0,0 +1,161 @@
# 下标 (Subscripts)
下标可以定义在类(Class)、结构体(structures)和枚举(enumerations)这些目标中,可以认为是访问对象、集合或序列的快捷方式。举例来说,用下标访问一个数组(Array)实例中的元素可以这样写 `someArray[index]` ,访问字典(Dictionary)实例中的元素可以这样写 `someDictionary[key]`,而不需要再调用实例的某个方法来获得元素的值。
对于同一个目标可以定义多个下标,通过索引值类型的不同来进行重载,而且索引值的个数可以是多个。
> 译者:这里下标重载在本小节中原文并没有任何演示
## 下标语法
下标允许你通过在实例后面的方括号中传入一个或者多个的索引值来对实例进行访问和赋值。语法类似于实例方法和实例属性的混合。与定义实例方法类似,定义下标使用`subscript`关键字显式声明入参一个或多个和返回类型。与实例方法不同的是下标可以设定为读写或只读。这种方式又有点像实例属性的getter和setter
```
subscript(index: Int) -> Int {
get {
// 返回与入参匹配的Int类型的值
}
set(newValue) {
// 执行赋值操作
}
}
```
`newValue`的类型必须和下标定义的返回类型相同。与实例属性相同的是set的入参声明`newValue`就算不写在set代码块中依然可以使用`newValue`这个变量来访问新赋的值。
与只读实例属性一样可以直接将原本应该写在get代码块中的代码写在subscript中即可
```
subscript(index: Int) -> Int {
// 返回与入参匹配的Int类型的值
}
```
下面代码演示了一个在TimesTable结构体中使用只读下标的用法该结构体用来展示传入整数的N倍。
```
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
println("3的6倍是\(threeTimesTable[6])")
// 输出 "3的6倍是18"
```
在上例中通过TimesTable结构体创建了一个用来表示索引值三倍的实例。数值3作为结构体构造函数入参表示这个值将成为实例成员multiplier的值。
你可以通过下标来来得到结果,比如`threeTimesTable[6]`。这句话访问了threeTimesTable的第六个元素返回18或者6的3倍。
> <b>提示</b>
>
> TimesTable例子是基于一个固定的数学公式。它并不适合开放写权限来对threeTimesTable[someIndex]进行赋值操作,这也是为什么下标只定义为只读的原因。
## 下标用法
下标根据使用场景不同也具有不同的含义。通常下标是用来访问集合(collection),列表(list)或序列(sequence)中元素的快捷方式。你可以为特定的类或结构体中自由的实现下标来提供合适的功能。
例如Swift的字典(Dictionary)实现了通过下标来对其实例中存放的值进行存取操作。在字典中设值可以通过给字典提供一个符合字典索引类型的索引值的表达式赋一个与字典存放值类型匹配的值来做到:
```
var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
numberOfLegs["bird"] = 2
```
上例定义一个名为numberOfLegs的变量并用一个字典表达式初始化出了包含三对键值的字典实例。numberOfLegs的字典存放值类型推断为`Dictionary<String, Int>`。字典实例创建完成之后通过下标的方式将整型值`2`赋值到字典实例的索引为`bird`的位置中。
更多关于字典(Dictionary)下标的信息请参考[字典的访问与修改](#)
> <b>提示</b>
>
> Swift中Dictionary的下标实现中在get部分返回值是`Int?`,也就是说不是每个字典的索引都能得到一个整型值,对于没有设过值的索引的访问返回的结果就是`nil`;同样想要从字典实例中删除某个索引下的值也只需要给这个索引赋值为`nil`即可。
## 下标选项
下标允许任意数量的入参索引并且每个入参类型也没有限制。下标的返回值也可以是任何类型。下标可以使用变量参数和可变参数但使用in-out参数或给参数设置默认值都是不允许的。
一个类或结构体可以根据自身需要提供多个下标实现,在定义下标时通过入参个类型进行区分,使用下标时会自动匹配合适的下标实现运行,这就是下标的重载。
一个下标入参是最常见的情况但只要有合适的场景也可以定义多个下标入参。如下例定义了一个Matrix结构体将呈现一个Double类型的二维数组。Matrix结构体的下标需要两个整型参数
```
struct Matrix {
let rows: Int, columns: Int
var grid: Double[]
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
grid = Array(count: rows * columns, repeatedValue: 0.0)
}
func indexIsValidForRow(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}
subscript(row: Int, column: Int) -> Double {
get {
assert(indexIsValidForRow(row, column: column), "Index out of range")
return grid[(row * columns) + column]
}
set {
assert(indexIsValidForRow(row, column: column), "Index out of range")
grid[(row * columns) + columns] = newValue
}
}
}
```
Matrix提供了一个两个入参的构造方法入参分别是`rows``columns`创建了一个足够容纳rows * columns个数的Double类型数组。为了存储将数组的大小和数组每个元素初始值0.0,都传入数组的构造方法中来创建一个正确大小的新数组。关于数组的构造方法和析构方法请参考[Creating and Initializing an Array](#)。
你可以通过传入合适的row和column的数量来构造一个新的Matrix实例
```
var matrix = Matrix(rows: 2, columns: 2)
```
上例中创建了一个新的两行两列的Matrix实例。在阅读顺序从左上到右下的Matrix实例中的数组实例grid是矩阵二维数组的扁平化存储
```
// 示意图
grid = [0.0, 0.0, 0.0, 0.0]
col0 col1
row0 [0.0, 0.0,
row1 0.0, 0.0]
```
将值赋给带有row和column下标的matrix实例表达式可以完成赋值操作下标入参使用逗号分割
```
matrix[0, 1] = 1.5
matrix[1, 0] = 3.2
```
上面两句话分别让matrix的右上值为1.5坐下值为3.2
```
[0.0, 1.5,
3.2, 0.0]
```
Matrix下标的getter和setter中同时调用了下标入参的row和column是否有效的判断。为了方便进行断言Matrix包含了一个名为indexIsValid的成员方法用来确认入参的row或column值是否会造成数组越界
```
func indexIsValidForRow(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}
```
断言在下标越界时触发:
```
let someValue = matrix[2, 2]
// 断言将会触发,因为 [2, 2] 已经超过了matrix的最大长度
```
> 译者这里有个词Computed Properties 这里统一翻译为实例属性了 微软术语引擎里没有这个词

View File

@ -0,0 +1,247 @@
# 继承
一个类可以继承另一个类的方法属性和其它特性。当一个类继承其它类继承类叫子类被继承类叫超类或父类。在Swift中继承是区分「类」与其它类型的一个基本特征。
在Swift中类可以调用和访问超类的方法属性和下标并且可以重写override这些方法属性和下标来优化或修改它们的行为。Swift会检查你的重写定义在超类中是否有匹配的定义以此确保你的重写行为是正确的。
可以为类中继承来的属性添加属性观察器property observer这样一来当属性值改变时类就会被通知到。可以为任何属性添加属性观察器无论它原本被定义为存储型属性stored property还是计算型属性computed property
## 定义一个基类
不继承于其它类的类,称之为基类。
> 注意Swift中的类并不是从一个通用的基类继承而来。如果你不为你定义的类指定一个超类的话这个类就自动成为基类。
下面的例子定义了一个叫`Vehicle`的基类。这个基类声明了两个对所有车辆都通用的属性(`numberOfWheels``maxPassengers`)。这些属性在`description`方法中使用,这个方法返回一个`String`类型的,对车辆特征的描述:
```
class Vehicle {
var numberOfWheels: Int
var maxPassengers: Int
func description() -> String {
return "\(numberOfWheels) wheels; up to \(maxPassengers) passengers"
}
init() {
numberOfWheels = 0
maxPassengers = 1
}
}
```
`Vehicle`类定义了初始化器initializer来设置属性的值。初始化器会在[构造函数]()一节中详细介绍,这里我们做一下简单介绍,以便于讲解子类中继承来的属性可以如何被修改。
初始化器用于创建某个类型的一个新实例。尽管初始化器并不是方法,但在语法上,两者很相似。初始化器的工作是准备新实例以供使用,并确保实例中的所有属性都拥有有效的初始化值。
初始化器的最简单形式就像一个没有参数的实例方法,使用`init`关键字:
```
init() {
// perform some initialization here
}
```
如果要创建一个`Vehicle`类的新实例,使用初始化器语法调用上面的初始化器,即类名后面跟一个空的小括号:
```
let someVehicle = Vehicle()
```
这个`Vehicle`类的初始化器为任意的一辆车设置一些初始化属性值(`numberOfWheels = 0 ``maxPassengers = 1`)。
`Vehicle`类定义了车辆的共同特性,但这个类本身并没太大用处。为了使它更为实用,你需要进一步细化它来描述更具体的车辆。
## Subclassing子类化待定
subclassing指的是在一个已有类的基础上创建一个新的类。子类继承超类的特性并且你可以优化或改变它。你还可以为子类添加新的特性。
为了指明某个类的超类,将超类名写在子类名的后面,用冒号分隔:
```
class SomeClass: SomeSuperclass {
// class definition goes here
}
```
下一个例子,定义一个更具体的车辆类叫`Bicycle`。这个新类是在`Vehicle`类的基础上创建起来。因此你需要将`Vehicle`类放在`Bicycle`类后面,用冒号分隔。
我们可以将这读作:
“定义一个新的类叫`Bicycle`,它继承了`Vehicle`的特性”;
```
class Bicycle: Vehicle {
init() {
super.init()
numberOfWheels = 2
}
}
```
`Bicycle``Vehicle`的子类,`Vehicle``Bicycle`的超类。新的`Bicycle`类自动获得`Vehicle`类的特性,比如`maxPassengers``numberOfWheels`属性。你可以在子类中定制这些特性,或添加新的特性来更好地描述`Bicycle`类。
`Bicycle`类定义了一个初始化器来设置它定制的特性自行车只有2个轮子`Bicycle`的初始化器调用了它父类`Vehicle`的初始化器`super.init()`,以此确保在`Bicycle`类试图修改那些继承来的属性前,`Vehicle`类已经初始化过它们了。
> 注意不像Objective-C在Swift中初始化器默认是不继承的见[初始化器的继承与重写]()
`Vehicle`类中`maxPassengers`的默认值对自行车来说已经是正确的,因此在`Bicycle`的初始化器中并没有改变它。而`numberOfWheels`原来的值对自行车来说是不正确的因此在初始化器中将它更改为2。
`Bicycle`不仅可以继承`Vehicle`的属性,还可以继承它的方法。如果你创建了一个`Bicycle`类的实例,你就可以调用它继承来的`description`方法,并且可以看到,它输出的属性值已经发生了变化:
```
let bicycle = Bicycle()
println("Bicycle: \(bicycle.description())")
// Bicycle: 2 wheels; up to 1 passengers
```
子类还可以继续被其它类继承:
```
class Tandem: Bicycle {
init() {
super.init()
maxPassengers = 2
}
}
```
上面的例子创建了`Bicycle`的一个子类双人自行车tandem`Tandem``Bicycle`继承了两个属性,而这两个属性是`Bicycle``Vehicle`继承而来的。`Tandem`并不修改轮子的数量因为它仍是一辆自行车有2个轮子。但它需要修改`maxPassengers`的值,因为双人自行车可以坐两个人。
> 注意:子类只允许修改从超类继承来的变量属性,而不能修改继承来的常量属性。
创建一个`Tandem`类的实例,打印它的描述,即可看到它的属性已被更新:
```
let tandem = Tandem()
println("Tandem: \(tandem.description())")
// Tandem: 2 wheels; up to 2 passengers
```
注意,`Tandem`类也继承了`description`方法。一个类的实例方法会被这个类的所有子类继承。
## 重写Overriding
子类可以为继承来的实例方法instance method类方法class method实例属性instance property或下标subscript提供自己定制的实现implementation。我们把这种行为叫重写overriding
如果要重写某个特性,你需要在重写定义的前面加上`override`关键字。这么做,你就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少`override`关键字的重写都会在编译时被诊断为错误。
`override`关键字会提醒Swift编译器去检查该类的超类或其中一个父类是否有匹配重写版本的声明。这个检查可以确保你的重写定义是正确的。
### 访问超类的方法,属性及下标
当你在子类中重写超类的方法,属性或下标时,有时在你的重写版本中使用已经存在的超类实现会大有裨益。比如,你可以优化已有实现的行为,或在一个继承来的变量中存储一个修改过的值。
在合适的地方,你可以通过使用`super`前缀来访问超类版本的方法,属性或下标:
* 在方法`someMethod`的重写实现中,可以通过`super.someMethod()`来调用超类版本的`someMethod`方法。
* 在属性`someProperty`的getter或setter的重写实现中可以通过`super.someProperty`来访问超类版本的`someProperty`属性。
* 在下标的重写实现中,可以通过`super[someIndex]`来访问超类版本中的相同下标。
### 重写方法
在子类中,你可以重写继承来的实例方法或类方法,提供一个定制或替代的方法实现。
下面的例子定义了`Vehicle`的一个新的子类,叫`Car`,它重写了从`Vehicle`类继承来的'description'方法:
```
class Car: Vehicle {
var speed: Double = 0.0
init() {
super.init()
maxPassengers = 5
numberOfWheels = 4
}
override func description() -> String {
return super.description() + "; "
+ "traveling at \(speed) mph"
}
}
```
`Car`声明了一个新的存储型属性`speed`,它是`Double`类型的,默认值是`0.0`表示“时速是0英里”。'Car'有自己的初始化器它将乘客的最大数量设为5轮子数量设为4。
`Car`重写了继承来的`description`方法,它的声明与`Vehicle`中的`description`方法一致,声明前面加上了`override`关键字。
`Car`中的`description`方法并非完全自定义,而是通过`super.description`使用了超类`Vehicle`中的`description`方法,然后再追加一些额外的信息,比如汽车的当前速度。
如果你创建一个`Car`的新实例,并打印`description`方法的输出,你就会发现描述信息已经发生了改变:
```
let car = Car()
println("Car: \(car.description())")
// Car: 4 wheels; up to 5 passengers; traveling at 0.0 mph
```
### 重写属性
你可以重写继承来的实例属性或类属性提供自己定制的getter和setter或添加属性观察器使重写的属性观察属性值什么时候发生改变。
#### 重写属性的Getters和Setters
你可以提供定制的getter或setter来重写任意继承来的属性无论继承来的属性是存储型的还是计算型的属性。子类并不知道继承来的属性是存储型的还是计算型的它只知道继承来的属性会有一个名字和类型。你在重写一个属性时必需将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。
你可以将一个继承来的只读属性重写为一个读写属性只需要你在重写版本的属性里提供getter和setter即可。但是你不可以将一个继承来的读写属性重写为一个只读属性。
> 注意如果你在重写属性中提供了setter那么你也一定要提供getter。如果你不想在重写版本中的getter里修改继承来的属性值你可以直接返回`super.someProperty`来返回继承来的值。正如下面的`SpeedLimitedCar`的例子所示。
以下的例子定义了一个新类,叫`SpeedLimitedCar`,它是`Car`的子类。类`SpeedLimitedCar`表示安装了限速装置的车它的最高速度只能达到40mph。你可以通过重写继承来的`speed`属性来实现这个速度限制:
```
class SpeedLimitedCar: Car {
override var speed: Double {
get {
return super.speed
}
set {
super.speed = min(newValue, 40.0)
}
}
}
```
当你设置一个`SpeedLimitedCar`实例的`speed`属性时属性setter的实现会去检查新值与限制值40mph的大小它会将超类的`speed`设置为`newValue``40.0`中较小的那个。这两个值哪个较小由`min`函数决定它是Swift标准库中的一个全局函数。`min`函数接收两个或更多的数,返回其中最小的那个。
如果你尝试将`SpeedLimitedCar`实例的`speed`属性设置为一个大于40mph的数然后打印`description`函数的输出你会发现速度被限制在40mph
```
let limitedCar = SpeedLimitedCar()
limitedCar.speed = 60.0
println("SpeedLimitedCar: \(limitedCar.description())")
// SpeedLimitedCar: 4 wheels; up to 5 passengers; traveling at 40.0 mph
```
#### 重写属性观察器Property Observer
你可以在属性重写中为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会被通知到,无论那个属性原本是如何实现的。关于属性观察器的更多内容,请看[属性观察器]()。
> 注意你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的所以为它们提供willSet或didSet实现是不恰当。此外还要注意你不可以同时提供重写的setter和重写的属性观察器。如果你想观察属性值的变化并且你已经为那个属性提供了定制的setter那么你在setter中就可以观察到任何值变化了。
下面的例子定义了一个新类叫`AutomaticCar`,它是`Car`的子类。`AutomaticCar`表示自动挡汽车,它可以根据当前的速度自动选择合适的挡位。`AutomaticCar`也提供了定制的`description`方法,可以输出当前挡位。
```
class AutomaticCar: Car {
var gear = 1
override var speed: Double {
didSet {
gear = Int(speed / 10.0) + 1
}
}
override func description() -> String {
return super.description() + " in gear \(gear)"
}
}
```
当你设置`AutomaticCar``speed`属性属性的didSet观察器就会自动地设置`gear`属性为新的速度选择一个合适的挡位。具体来说就是属性观察器将新的速度值除以10然后向下取得最接近的整数值最后加1来得到档位`gear`的值。例如速度为10.0时挡位为1速度为35.0时挡位为4
```
let automatic = AutomaticCar()
automatic.speed = 35.0
println("AutomaticCar: \(automatic.description())")
// AutomaticCar: 4 wheels; up to 5 passengers; traveling at 35.0 mph in gear 4
```
## 防止重写
你可以通过把方法,属性或下标标记为`final`来防止它们被重写,只需要在声明关键字前加上`@final`特性即可。(例如:`@final var`, `@final func`, `@final class func`, 以及 `@final subscript`
如果你重写了final方法属性或下标在编译时会报错。在扩展中你添加到类里的方法属性或下标也可以在扩展的定义里标记为final。
你可以通过在关键字`class`前添加`@final`特性(`@final class`来将整个类标记为final的这样的类是不可被继承的否则会报编译错误。

View File

@ -0,0 +1,88 @@
#析构过程
在一个类的实例被释放之前析构函数被立即调用。用关键字deinit来标示析构函数类似于初始化函数用init来标示。析构函数只适用于类类型。
##析构过程原理
Swift会自动释放不再需要的实例以释放资源。如自动引用计数那一章描述Swift通过自动引用计数ARC处理实例的内存管理。通常当你的实例被释放时不需要手动的去清理。但是当使用自己的资源时你可能需要进行一些额外的清理。例如如果创建了一个自定义的类来打开一个文件并写入一些数据你可能需要在类实例被释放之前关闭该文件。
在类的定义中,每个类最多只能有一个析构函数。析构函数不带任何参数,在写法上不带括号:
1 deinit {
2 // 执行析构过程
3 }
析构函数是在实例释放发生前一步被自动调用。不允许主动调用自己的析构函数。子类继承了父类的析构函数,并且在子类析构函数实现的最后,父类的析构函数被自动调用。即使子类没有提供自己的析构函数,父类的析构函数也总是被调用。
因为直到实例的析构函数被调用时,实例才会被释放,所以析构函数可以访问所有请求实例的属性,并且根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件的名称)。
##析构函数操作
这里是一个析构函数操作的例子。这个例子是一个简单的游戏定义了两种新类型Bank和Player。Bank结构体管理一个虚拟货币的流通在这个流通中Bank永远不可能拥有超过10,000的硬币。在这个游戏中有且只能有一个Bank存在因此Bank由带有静态属性和静态方法的结构体实现从而存储和管理其当前的状态。
1 struct Bank {
2 static var coinsInBank = 10_000
3 static func vendCoins(var numberOfCoinsToVend: Int) -> Int {
4 numberOfCoinsToVend = min(numberOfCoinsToVend, coinsInBank)
5 coinsInBank -= numberOfCoinsToVend
6 return numberOfCoinsToVend
7 }
8 static func receiveCoins(coins: Int) {
9 coinsInBank += coins
10 }
11 }
Bank根据它的coinsInBank属性来跟踪当前它拥有的硬币数量。银行还提供两个方法—vendCoins和receiveCoins用来处理硬币的分发和收集。
vendCoins方法在bank分发硬币之前检查是否有足够的硬币。如果没有足够多的硬币bank返回一个比请求时小的数字(如果没有硬币留在bank中就返回0)。vendCoins方法声明numberOfCoinsToVend为一个变量参数这样就可以在方法体的内部修改数字而不需要定义一个新的变量。vendCoins方法返回一个整型值表明了提供的硬币的实际数目。
receiveCoins方法只是将bank的硬币存储和接收到的硬币数目相加再保存回bank。
Player类描述了游戏中的一个玩家。每一个player在任何时刻都有一定数量的硬币存储在他们的钱包中。这通过player的coinsInPurse属性来体现:
1 class Player {
2 var coinsInPurse: Int
3 init(coins: Int) {
4 coinsInPurse = Bank.vendCoins(coins)
5 }
6 func winCoins(coins: Int) {
7 coinsInPurse += Bank.vendCoins(coins)
8 }
9 deinit {
10 Bank.receiveCoins(coinsInPurse)
11 }
12 }
每个Player实例都由一个指定数目硬币组成的启动额度初始化这些硬币在bank初始化的过程中得到。如果没有足够的硬币可用Player实例可能收到比指定数目少的硬币。
Player类定义了一个winCoins方法该方法从bank获取一定数量的硬币并把它们添加到player的钱包。Player类还实现了一个析构函数这个析构函数在Player实例释放前一步被调用。这里析构函数只是将player的所有硬币都返回给bank:
1 var playerOne: Player? = Player(coins: 100)
2 println("A new player has joined the game with \ (playerOne!.coinsInPurse) coins")
3 // 输出 "A new player has joined the game with 100 coins"
4 println("There are now \(Bank.coinsInBank) coins left in the bank")
5 // 输出 "There are now 9900 coins left in the bank"
一个新的Player实例随着一个100个硬币(如果有)的请求而被创建。这个Player实例存储在一个名为playerOne的可选Player变量中。这里使用一个可选变量是因为players可以随时离开游戏。设置为可选使得你可以跟踪当前是否有player在游戏中。
因为playerOne是可选的所以由一个感叹号(!)来修饰每当其winCoins方法被调用时coinsInPurse属性被访问并打印出它的默认硬币数目。
1 playerOne!.winCoins(2_000)
2 println("PlayerOne won 2000 coins & now has \ (playerOne!.coinsInPurse) coins")
3 // 输出 "PlayerOne won 2000 coins & now has 2100 coins"
4 println("The bank now only has \(Bank.coinsInBank) coins left")
5 // 输出 "The bank now only has 7900 coins left"
这里player已经赢得了2,000硬币。player的钱包现在有2,100硬币bank只剩余7,900硬币。
1 playerOne = nil
2 println("PlayerOne has left the game")
3 // 输出 "PlayerOne has left the game"
4 println("The bank now has \(Bank.coinsInBank) coins")
5 // 输出 "The bank now has 10000 coins"
player现在已经离开了游戏。这表明是要将可选的playerOne变量设置为nil意思是"没有Player实例"。当这种情况发生的时候playerOne变量对Player实例的引用被破坏了。没有其它属性或者变量引用Player实例因此为了清空它占用的内存从而释放它。在这发生前一步其析构函数被自动调用其硬币被返回到bank。

View File

@ -0,0 +1,270 @@
#Optional Chaining
可选链Optional Chaining是一种可以请求和调用属性、方法及子脚本的过程它的自判断性体现于请求或调用的目标当前可能为空`nil`)。如果自判断的目标有值,那么调用就会成功;相反,如果选择的目标为空(`nil`),则这种调用将返回空(`nil`)。多次请求或调用可以被链接在一起形成一个链,如果任何一个节点为空(`nil`)将导致整个链失效。
> 笔记:
`Swift`的自判断链和Objective-C中的消息为空有些相像但是`Swift`可以使用在任意类型中,并且失败与否可以被检测到。
## 可选链可替代强制解析
通过在想调用的属性、方法、或子脚本的自判断值(`optional value`)(非空)后面放一个问号,可以定义一个可选链。这一点很像在自判断值后面放一个声明符号来强制拆得其封包内的值。他们的主要的区别在于当自判断值为空时可选链即刻失败,然而一般的强制解析将会引发运行时错误。
为了反映可选链可以调用空(`nil`不论你调用的属性、方法、子脚本等返回的值是不是自判断值它的返回结果都是一个自判断值。你可以利用这个返回值来检测你的可选链是否调用成功有返回值即成功返回nil则失败。
调用可选链的返回结果与原本的返回结果具有相同的类型,但是原本的返回结果被包装成了一个自判断值,当可选链调用成功时,一个应该返回`Int`的属性将会返回`Int`
下面几段代码将解释可选链和强制解析的不同。
首先定义两个类`Person``Residence`
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
`Residence`具有一个`Int`类型的`numberOfRooms`其值为1。`Person`具有一个自判断`residence`属性,它的类型是`Residence`
如果你创建一个新的Person实例它的residence属性由于是被定义为自判断型的此属性将默认初始化为空
let john = Person()
如果你想使用声明符!强制解析获得这个人`residence`属性`numberOfRooms`属性值,将会引发运行时错误,因为这时没有可以供解析的`residence`值。
let roomCount = john.residence!.numberOfRooms
// this triggers a runtime error”
//将导致运行时错误
`john.residence`不是`nil`时,会运行通过,且会将`roomCount` 设置为一个`int`类型的合理值。然而,如上所述,当`residence`为空时,这个代码将会导致运行时错误。
可选链提供了一种另一种获得numberOfRooms的方法。利用可选链使用问号来代替原来``的位置:
if let roomCount = john.residence?.numberOfRooms {
println("John's residence has \(roomCount) room(s).")
} else {
println("Unable to retrieve the number of rooms.")
}
// 打印 "Unable to retrieve the number of rooms.
这告诉Swift来链接自判断`residence`属性,如果`residence`存在则取回`numberOfRooms`的值。
因为这种尝试获得`numberOfRooms`的操作有可能失败,可选链会返回`Int`类型值,或者称作“自判断`Int`”。当`residence`是空的时候(上例),选择`Int`将会为空,因此会出先无法访问`numberOfRooms`的情况。
要注意的是即使numberOfRooms是非自判断`Int``Int`)时这一点也成立。只要是通过可选链的请求就意味着最后`numberOfRooms`总是返回一个`Int`而不是`Int`
你可以自己定义一个`Residence`实例给`john.residence`,这样它就不再为空了:
john.residence = Residence()
`john.residence` 现在有了实际存在的实例而不是nil了。如果你想使用和前面一样的可选链来获得`numberOfRoooms`它将返回一个包含默认值1的`Int`
if let roomCount = john.residence?.numberOfRooms {
println("John's residence has \(roomCount) room(s).")
} else {
println("Unable to retrieve the number of rooms.")
}
// 打印 "John's residence has 1 room(s)"。
##为可选链定义模型类
你可以使用可选链来多层调用属性,方法,和子脚本。这让你可以利用它们之间的复杂模型来获取更底层的属性,并检查是否可以成功获取此类底层属性。
后面的代码定义了四个将在后面使用的模型类,其中包括多层可选链。这些类是由上面的`Person``Residence`模型通过添加一个`Room`和一个`Address`类拓展来。
`Person`类定义与之前相同。
class Person {
var residence: Residence?
}
`Residence`类比之前复杂些。这次,它定义了一个变量 `rooms`,它被初始化为一个`Room[]`类型的空数组:
class Residence {
var rooms = Room[]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
println("The number of rooms is \(numberOfRooms)")
}
var address: Address?
}
因为`Residence`存储了一个`Room`实例的数组,它的`numberOfRooms`属性值不是一个固定的存储值,而是通过计算而来的。`numberOfRooms`属性值是由返回`rooms`数组的`count`属性值得到的。
为了能快速访问`rooms`数组,`Residence`定义了一个只读的子脚本,通过插入数组的元素角标就可以成功调用。如果该角标存在,子脚本则将该元素返回。
`Residence`中也提供了一个`printNumberOfRooms`的方法,即简单的打印房间个数。
最后,`Residence`定义了一个自判断属性叫`address``address`)。`Address`类的属性将在后面定义。
用于`rooms`数组的`Room`类是一个很简单的类,它只有一个`name`属性和一个设定`room`名的初始化器。
class Room {
let name: String
init(name: String) { self.name = name }
}
这个模型中的最终类叫做`Address`。它有三个自判断属性他们额类型是`String`。前面两个自判断属性`buildingName``buildingNumber`作为地址的一部分,是定义某个建筑物的两种方式。第三个属性`street`,用于命名地址的街道名:
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if buildingName {
return buildingName
} else if buildingNumber {
return buildingNumber
} else {
return nil
}
}
}
`Address`类还提供了一个`buildingIdentifier`的方法,它的返回值类型为`String`。这个方法检查`buildingName``buildingNumber`的属性,如果`buildingName`有值则将其返回,或者如果`buildingNumber`有值则将其返回,再或如果没有一个属性有值,返回空。
##通过可选链调用属性
正如上面“ [可选链可替代强制解析]()”中所述,你可以利用可选链的自判断值获取属性,并且检查属性是否获取成功。然而,你不能使用可选链为属性赋值。
使用上述定义的类来创建一个人实例,并再次尝试后去它的`numberOfRooms`属性:
let john = Person()
if let roomCount = john.residence?.numberOfRooms {
println("John's residence has \(roomCount) room(s).")
} else {
println("Unable to retrieve the number of rooms.")
}
// 打印 "Unable to retrieve the number of rooms。
由于`john.residence`是空,所以这个可选链和之前一样失败了,但是没有运行时错误。
##通过可选链调用方法
你可以使用可选链的来调用自判断值的方法并检查方法调用是否成功。即使这个方法没有返回值,你依然可以使用可选链来达成这一目的。
Residence的printNumberOfRooms方法会打印numberOfRooms的当前值。方法如下
func printNumberOfRooms(){
println(“The number of rooms is \(numberOfRooms)”)
}
这个方法没有返回值。但是,没有返回值类型的函数和方法有一个隐式的返回值类型`Void`参见Function Without Return Values
如果你利用可选链调用此方法,这个方法的返回值类型将是`Void`,而不是`Void`因为当通过可选链调用方法时返回值总是自判断类型optional type即使是这个方法本是没有定义返回值你也可以使用`if`语句来检查是否能成功调用`printNumberOfRooms`方法:如果方法通过可选链调用成功,`printNumberOfRooms`的隐式返回值将会是`Void`,如果没有成功,将返回`nil`
if john.residence?.printNumberOfRooms() {
println("It was possible to print the number of rooms.")
} else {
println("It was not possible to print the number of rooms.")
}
// 打印 "It was not possible to print the number of rooms."。
##使用可选链调用子脚本
你可以使用可选链来尝试从子脚本获取值并检查子脚本的调用是否成功,然而,你不能通过可选链来设置子代码。
> 注意:
当你使用可选链来获取子脚本的时候,你应该将问号放在子脚本括号的前面而不是后面。可选链的问号一般直接跟在自判断表达语句的后面。
下面这个例子用在`Residence`类中定义的子脚本来获取`john.residence`数组中第一个房间的名字。因为`john.residence`现在是`nil`,子脚本的调用失败了。
if let firstRoomName = john.residence?[0].name {
println("The first room name is \(firstRoomName).")
} else {
println("Unable to retrieve the first room name.")
}
// 打印 "Unable to retrieve the first room name."。
在子代码调用中可选链的问号直接跟在`john.residence`的后面,在子脚本括号的前面,因为`john.residence`是可选链试图获得的自判断值。
如果你创建一个`Residence`实例给`john.residence`,且在他的`rooms`数组中有一个或多个`Room`实例,那么你可以使用可选链通过`Residence`子脚本来获取在`rooms`数组中的实例了:
let johnsHouse = Residence()
johnsHouse.rooms += Room(name: "Living Room")
johnsHouse.rooms += Room(name: "Kitchen")
john.residence = johnsHouse
if let firstRoomName = john.residence?[0].name {
println("The first room name is \(firstRoomName).")
} else {
println("Unable to retrieve the first room name.")
}
// 打印 "The first room name is Living Room."。
##连接多层链接
你可以将多层可选链连接在一起,可以掘取模型内更下层的属性方法和子脚本。然而多层可选链不能再添加比已经返回的自判断值更多的层。
也就是说:
如果你试图获得的类型不是自判断类型,由于使用了可选链它将变成自判断类型。
如果你试图获得的类型已经是自判断类型,由于可选链它也不会提高自判断性。
因此:
如果你试图通过可选链获得`Int`值,不论使用了多少层链接返回的总是`Int`
相似的,如果你试图通过可选链获得`Int`值,不论使用了多少层链接返回的总是`Int`
下面的例子试图获取`john``residence`属性里的`address``street`属性。这里使用了两层可选链来联系`residence``address`属性,他们两者都是自判断类型:
if let johnsStreet = john.residence?.address?.street {
println("John's street name is \(johnsStreet).")
} else {
println("Unable to retrieve the address.")
}
// 打印 "Unable to retrieve the address.”。
`john.residence`的值现在包含一个`Residence`实例,然而`john.residence.address`现在是`nil`,因此`john.residence?.address?.street`调用失败。
从上面的例子发现,你试图获得`street`属性值。这个属性的类型是`String`。因此尽管在自判断类型属性前使用了两层可选链,`john.residence?.address?.street`的返回值类型也是`String`
如果你为`Address`设定一个实例来作为`john.residence.address`的值,并为`address``street`属性设定一个实际值,你可以通过多层可选链来得到这个属性值。
let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence!.address = johnsAddress
if let johnsStreet = john.residence?.address?.street {
println("John's street name is \(johnsStreet).")
} else {
println("Unable to retrieve the address.")
}
// 打印 "John's street name is Laurel Street."。
值得注意的是,“`!`”符的在定义`address`实例时的使用(`john.residence.address`)。`john.residence`属性是一个自判断类型,因此你需要在它获取`address`属性之前使用``解析以获得它的实际值。
##链接自判断返回值的方法
前面的例子解释了如何通过可选链来获得自判断类型属性值。你也可以通过调用返回自判断类型值的方法并按需链接方法的返回值。
下面的例子通过可选链调用了`Address`类中的`buildingIdentifier` 方法。这个方法的返回值类型是`String?`。如上所述,这个方法在可选链调用后最终的返回值类型依然是`String`
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
println("John's building identifier is \(buildingIdentifier).")
}
// 打印 "John's building identifier is The Larches."。
如果你还想进一步对方法返回值执行可选链,将可选链问号符放在方法括号的后面:
if let upper = john.residence?.address?.buildingIdentifier()?.uppercaseString {
println("John's uppercase building identifier is \(upper).")
}
// 打印 "John's uppercase building identifier is THE LARCHES."。
> 注意:
在上面的例子中,你将可选链问号符放在括号后面是因为你想要链接的自判断值是`buildingIdentifier`方法的返回值,不是`buildingIdentifier`方法本身。

View File

@ -0,0 +1,293 @@
#类型检查Type Casting
ps为了方便各位检验所以保留了英文可删。
_类型检查_是一种检查类实例的方式并且哦或者也是让实例作为它的父类或者子类的一种方式。
_Type casting_ is a way to check the type of an instance, and/or to treat that instance as if it is a different superclass or subclass from somewhere else in its own class hierarchy.
类型检查在Swift中使用`is``as`操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。
Type casting in Swift is implemented with the `is` and `as` operators. These two operators provide a simple and expressive way to check the type of a value or cast a value to a different type.
你也可以用来检查一个类是否实现了某个协议,就像在 [Protocols》Checking for Protocol Conformance](Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-XID_363)部分讲述的一样。
You can also use type casting to check whether a type conforms to a protocol, as described in <span class="x-name">[Checking for Protocol Conformance](Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-XID_363)</span>.
### 定义一个类层次作为例子Defining a Class Hierarchy for Type Casting
你可以将它用在类和子类的层次结构上检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。这下面的三个代码段定义了一个类层次和一个array包含了几个这些类的实例作为类型检查的例子。
You can use type casting with a hierarchy of classes and subclasses to check the type of a particular class instance and to cast that instance to another class within the same hierarchy. The three code snippets below define a hierarchy of classes and an array containing instances of those classes, for use in an example of type casting.
第一个代码片段定义了一个新的基础类`MediaItem`。这个类为任何出现在数字媒体库的项提供基础功能。特别的,它声明了一个 `String` 类型的 `name` 属性,和一个`init name`初始化器。(它假定所有的媒体项都有个名称。)
The first snippet defines a new base class called `MediaItem`. This class provides basic functionality for any kind of item that appears in a digital media library. Specifically, it declares a `name` property of type `String`, and an `init name` initializer. (It is assumed that all media items, including all movies and songs, will have a name.)
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
下一个代码段定义了 `MediaItem` 的两个子类。第一个子类`Movie`,在父类(或者说基类)的基础上增加了一个 `director`(导演) 属性,和相应的初始化器。第二个类在父类的基础上增加了一个 `artist`(艺术家) 属性,和相应的初始化器:
The next snippet defines two subclasses of `MediaItem`. The first subclass, `Movie`, encapsulates additional information about a movie or film. It adds a `director` property on top of the base `MediaItem` class, with a corresponding initializer. The second subclass, `Song`, adds an `artist` property and initializer on top of the base class:
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}
class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.artist = artist
super.init(name: name)
}
}
最后一个代码段创建了一个array常量 `library` ,包含两个`Movie`实例和三个`Song`实例。`library`的类型是在它被初始化时根据它的array标记符和里面的内容ps: literal 标记符其实就是指“[”和“]”虽然苹果官方的翻译里翻译为字面当总感觉不好理解有点奇怪。不如翻译为标记符推断来的。Swift的类型检测器能够演绎出`Movie``Song` 有共同的父类 `MediaItem` ,所以它推断出 `MediaItem[]` 类作为 `library` 的类型。
The final snippet creates a constant array called `library`, which contains two `Movie` instances and three `Song` instances. The type of the `library` array is inferred by initializing it with the contents of an array literal. Swifts type checker is able to deduce that `Movie` and `Song` have a common superclass of `MediaItem`, and so it infers a type of `MediaItem[]` for the `library` array:
let library = [
Movie(name: "Casablanca", director: "Michael Curtiz"),
Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
Movie(name: "Citizen Kane", director: "Orson Welles"),
Song(name: "The One And Only", artist: "Chesney Hawkes"),
Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]
// the type of "library" is inferred to be MediaItem[]
在幕后`library` 里存储的项依然是 `Movie``Song` 类型的,但是,若你迭代它,取出的实例会是 `MediaItem` 类型的,而不是 `Movie``Song` 类型的。为了让它们作为它们本来的类型工作,你需要检查它们的类型或者向下转换它们的类型到其它类型,就像下面描述的一样。
The items stored in `library` are still `Movie` and `Song` instances behind the scenes. However, if you iterate over the contents of this array, the items you receive back are typed as `MediaItem`, and not as `Movie` or `Song`. In order to work with them as their native type, you need to _check_ their type, or _downcast_ them to a different type, as described below.
### 检查类型 Checking Type
用类型检查操作符(`is`)来检查一个实例是否属于特定子类型。类型检查操作符返回 `true` 若实例属于那个子类型,若不属于返回 `false`
Use the _type check operator_ (`is`) to check whether an instance is of a certain subclass type. The type check operator returns `true` if the instance is of that subclass type and `false` if it is not.
下面的例子定义了连个变量,`movieCount``songCount`,用来计算数组`library``Movie``Song` 类型的实例数量。
The example below defines two variables, `movieCount` and `songCount`, which count the number of `Movie` and `Song` instances in the `library` array:
var movieCount = 0
var songCount = 0
for item in library {
if item is Movie {
++movieCount
} else if item is Song {
++songCount
}
}
println("Media library contains \(movieCount) movies and \(songCount) songs")
// prints "Media library contains 2 movies and 3 songs"
示例迭代了数组 `library` 中的所有项。每一次, `for`-`in` 循环设置
`item` 常量的值为数组中的下一个 `MediaItem`
This example iterates through all items in the `library` array. On each pass, the `for`-`in` loop sets the `item` constant to the next `MediaItem` in the array.
若当前 `MediaItem` 是一个 `Movie` 类型的实例, `item is Movie` 返回 `true`,相反返回 `false`。同样的,`item is Song`检查item是否为`Song`类型的实例。在循环末尾,`movieCount``songCount`的值就是被找到属于各自的类型的实例数量。
`item is Movie` returns `true` if the current `MediaItem` is a `Movie` instance and `false` if it is not. Similarly, `item is Song` checks whether the item is a `Song` instance. At the end of the `for`-`in` loop, the values of `movieCount` and `songCount` contain a count of how many `MediaItem` instances were found of each type.
### 向下转型(简称下转) Downcasting
某类型的一个常量或变量可能在幕后实际上属于一个子类。你可以相信,上面就是这种情况。你可以尝试向下转到它的子类型,用类型检查操作符(`as`)
A constant or variable of a certain class type may actually refer to an instance of a subclass behind the scenes. Where you believe this is the case, you can try to _downcast_ to the subclass type with the _type cast operator_ (`as`).
因为向下转型可能会失败,类型检查操作符带有两种不同形式。可选形式( optional form `as?` 返回一个你试图下转成的类型的可选值optional value。强制形式 `as` 把试图向下转型和强制解包force-unwraps结果作为一个混合动作。
Because downcasting can fail, the type cast operator comes in two different forms. The optional form, `as?`, returns an optional value of the type you are trying to downcast to. The forced form, `as`, attempts the downcast and force-unwraps the result as a single compound action.
当你不确定下转可以成功时,用类型检查的可选形式(`as?`)。可选形式的类型检查总是返回一个可选值optional value并且若下转是不可能的可选值将是 `nil` 。这使你能够检查下转是否成功。
Use the optional form of the type cast operator (`as?`) when you are not sure if the downcast will succeed. This form of the operator will always return an optional value, and the value will be `nil` if the downcast was not possible. This enables you to check for a successful downcast.
只有你可以确定下转一定会成功时才使用强制形式。当你试图下转为一个不正确的类型时强制形式的类型检查会触发一个runtime error。
Use the forced form of the type cast operator (`as`) only when you are sure that the downcast will always succeed. This form of the operator will trigger a runtime error if you try to downcast to an incorrect class type.
下面的例子,迭代了`library`里的每一个 `MediaItem` 并打印出适当的描述。要这样做item需要真正作为`Movie``Song`的类型来使用。不仅仅是作为 `MediaItem`。为了能够使用`Movie``Song``director``artist`属性,这是必要的。
The example below iterates over each `MediaItem` in `library`, and prints an appropriate description for each item. To do this, it needs to access each item as a true `Movie` or `Song`, and not just as a `MediaItem`. This is necessary in order for it to be able to access the `director` or `artist` property of a `Movie` or `Song` for use in the description.
在这个示例中数组中的每一个item可能是 `Movie``Song`。 事前你不知道每个item的真实类型所以这里使用可选形式的类型检查 (`as?`)去检查循环里的每次下转。
In this example, each item in the array might be a `Movie`, or it might be a `Song`. You dont know in advance which actual class to use for each item, and so it is appropriate to use the optional form of the type cast operator (`as?`) to check the downcast each time through the loop:
for item in library {
if let movie = item as? Movie {
println("Movie: '\(movie.name)', dir. \(movie.director)")
} else if let song = item as? Song {
println("Song: '\(song.name)', by \(song.artist)")
}
}
// Movie: 'Casablanca', dir. Michael Curtiz
// Song: 'Blue Suede Shoes', by Elvis Presley
// Movie: 'Citizen Kane', dir. Orson Welles
// Song: 'The One And Only', by Chesney Hawkes
// Song: 'Never Gonna Give You Up', by Rick Astley
示例首先试图将 `item` 下转为 `Movie`。因为 `item` 是一个 `MediaItem` 类型的实例,它可能是一个`Movie`;同样,它可能是一个 `Song`,或者仅仅是基类 `MediaItem`。因为不确定,`as?`形式试图下转时返还一个可选值。 `item as Movie` 的返回值是`Movie?`类型或 “optional `Movie`”。
The example starts by trying to downcast the current `item` as a `Movie`. Because `item` is a `MediaItem` instance, its possible that it _might_ be a `Movie`; equally, its also possible that it might a `Song`, or even just a base `MediaItem`. Because of this uncertainty, the `as?` form of the type cast operator returns an _optional_ value when attempting to downcast to a subclass type. The result of `item as Movie` is of type `Movie?`, or “optional `Movie`”.
当应用在两个`Song`实例时,下转为 `Movie` 失败。为了处理这种情况上面的实例使用了可选绑定optional binding来检查optional `Movie`真的包含一个值(这个是为了判断下转是否成功。)可选绑定是这样写的“`if let movie = item as? Movie`”,可以这样解读:
Downcasting to `Movie` fails when applied to the two `Song` instances in the library array. To cope with this, the example above uses optional binding to check whether the optional `Movie` actually contains a value (that is, to find out whether the downcast succeeded.) This optional binding is written “`if let movie = item as? Movie`”, which can be read as:
“尝试将 `item` 转为 `Movie`类型。若成功,设置一个新的临时常量 `movie` 来存储返回的optional `Movie`
“Try to access `item` as a `Movie`. If this is successful, set a new temporary constant called `movie` to the value stored in the returned optional `Movie`.”
若下转成功,然后`movie`的属性将用于打印一个`Movie`实例的描述,包括它的导演的名字`director`。当`Song`被找到时,一个相近的原理被用来检测 `Song` 实例和打印它的描述。
If the downcasting succeeds, the properties of `movie` are then used to print a description for that `Movie` instance, including the name of its `director`. A similar principle is used to check for `Song` instances, and to print an appropriate description (including `artist` name) whenever a `Song` is found in the library.
注意
转换没有真的改变实例或它的值。潜在的根本的实例保持不变;只是简单地把它作为它被转换成的类来使用。
NOTE
Casting does not actually modify the instance or change its values. The underlying instance remains the same; it is simply treated and accessed as an instance of the type to which it has been cast.
### Any和AnyObject的转换 Type Casting for Any and AnyObject
Swift为不确定类型提供了两种特殊类型别名
* `AnyObject`可以代表任何class类型的实例。
* `Any`可以表示任何类型除了方法类型function types
Swift provides two special type aliases for working with non-specific types:
* `AnyObject` can represent an instance of any class type.
* `Any` can represent an instance of any type at all, apart from function types.
注意
只有当你明确的需要它的行为和功能时才使用Any和AnyObject。在你的代码里使用你期望的明确的类型总是更好的。
NOTE
Use Any and AnyObject only when you explicitly need the behavior and capabilities they provide. It is always better to be specific about the types you expect to work with in your code.
### AnyObject类型
当需要在工作中使用Cocoa APIs它一般接收一个`AnyObject[]`类型的数组或者说“一个任何对象类型的数组”。这是因为OC没有明确的类型化数组。但是你常常可以确定包含在仅从你知道的API信息提供的这样一个数组中的对象的类型。
When working with Cocoa APIs, it is common to receive an array with a type of `AnyObject[]`, or “an array of values of any object type”. This is because Objective-C does not have explicitly typed arrays. However, you can often be confident about the type of objects contained in such an array just from the information you know about the API that provided the array.
在这些情况下,你可以使用强制形式的类型检查(`as`)来下转在数组中的每一项到比 `AnyObject` 更明确的类型不需要可选解包optional unwrapping
In these situations, you can use the forced version of the type cast operator (`as`) to downcast each item in the array to a more specific class type than `AnyObject`, without the need for optional unwrapping.
下面的示例定义了一个 `AnyObject[]` 类型的数组并填入三个`Movie`类型的实例:
The example below defines an array of type `AnyObject[]` and populates this array with three instances of the `Movie` class:
let someObjects: AnyObject[] = [
Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"),
Movie(name: "Moon", director: "Duncan Jones"),
Movie(name: "Alien", director: "Ridley Scott")
]
因为知道这个数组只包含 `Movie` 实例,你可以直接用(`as`)下转并解包到不可选的`Movie`类型ps其实就是我们常用的正常类型这里是为了和可选类型相对比
Because this array is known to contain only `Movie` instances, you can downcast and unwrap directly to a non-optional `Movie` with the forced version of the type cast operator (`as`):
for object in someObjects {
let movie = object as Movie
println("Movie: '\(movie.name)', dir. \(movie.director)")
}
// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
// Movie: 'Moon', dir. Duncan Jones
// Movie: 'Alien', dir. Ridley Scott
为了变为一个更短的形式,下转`someObjects`类型成功 `Movie[]` 类型代替下转每一项。
For an even shorter form of this loop, downcast the `someObjects` array to a type of `Movie[]` instead of downcasting each item:
for movie in someObjects as Movie[] {
println("Movie: '\(movie.name)', dir. \(movie.director)")
}
// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
// Movie: 'Moon', dir. Duncan Jones
// Movie: 'Alien', dir. Ridley Scott
### Any类型
这里有个示例,使用 `Any` 类型来和混合的不同类型一起工作包括非class类型。它创建了一个可以存储`Any`类型的数组 `things`
Heres an example of using `Any` to work with a mix of different types, including non-class types. The example creates an array called `things`, which can store values of type `Any`:
var things = Any[]()
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
`things` 数组包含两个 `Int`2个 `Double`1个 `String` 值,一个元组 `(Double, Double)` Ivan Reitman导演的电影“Ghostbusters”。
The `things` array contains two `Int` values, two `Double` values, a `String` value, a tuple of type `(Double, Double)`, and the movie “Ghostbusters”, directed by Ivan Reitman.
你可以在 `switch` `cases`里用`is``as` 操作符来发觉只知道是 `Any``AnyObject`的常量或变量的类型。 下面的示例迭代 `things`数组中的每一项的并用`switch`语句查找每一项的类型。这几种`switch`语句的情形绑定它们匹配的值到一个规定类型的常量,让它们可以打印它们的值:
You can use the `is` and `as` operators in a `switch` statements cases to discover the specific type of a constant or variable that is known only to be of type `Any` or `AnyObject`. The example below iterates over the items in the `things` array and queries the type of each item with a `switch` statement. Several of the `switch` statements cases bind their matched value to a constant of the specified type to enable its value to be printed:
for thing in things {
switch thing {
case 0 as Int:
println("zero as an Int")
case 0 as Double:
println("zero as a Double")
case let someInt as Int:
println("an integer value of \(someInt)")
case let someDouble as Double where someDouble > 0:
println("a positive double value of \(someDouble)")
case is Double:
println("some other double value that I don't want to print")
case let someString as String:
println("a string value of \"\(someString)\"")
case let (x, y) as (Double, Double):
println("an (x, y) point at \(x), \(y)")
case let movie as Movie:
println("a movie called '\(movie.name)', dir. \(movie.director)")
default:
println("something else")
}
}
// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of "hello"
// an (x, y) point at 3.0, 5.0
// a movie called 'Ghostbusters', dir. Ivan Reitman
注意
在一个switch语句的case中使用强制形式的类型检查操作符(as, 而不是 as?) 来检查和转换到一个规定的类型。在switch case 语句的内容中这种检查总是安全的。
NOTE
The cases of a switch statement use the forced version of the type cast operator (as, not as?) to check and cast to a specific type. This check is always safe within the context of a switch case statement.

View File

@ -0,0 +1,279 @@
#扩展Extensions
----
本页包含内容:
- 扩展语法Extension Syntax
- 计算属性Computed Properties
- 构造器Initializers
- 方法Methods
- 下标Subscripts
- 嵌套类型Nested Types
*扩展*就是向一个已有的类、结构体或枚举类型添加新功能functionality。这包括在没有权限获取原始源代码的情况下扩展类型的能力即*逆向建模*。扩展和Objective-C中的分类(categories)类似。不过与Objective-C不同的是Swift的扩展没有名字。
Swift中的扩展可以
- 添加计算属性和计算静态属性
- 定义实例方法和类型方法
- 提供新的构造器
- 定义下标
- 定义和使用新的嵌套类型
- 使一个已有类型符合某个接口
>注意
如果你定义了一个扩展向一个已有类型添加新功能,那么这个新功能对该类型的所有已有实例中都是可用的,即使它们是在你的这个扩展的前面定义的。
##扩展语法
声明一个扩展使用关键字`extension`
```javascript
extension SomeType{
// new functionality to add to SomeType goes here
}
```
一个扩展可以扩展一个已有类型,使其能够适配一个或多个接口。当这种情况发生时,接口的名字应该完全按照类或结构体的名字的方式进行书写:
```javascript
extension SomeType: SomeProtocol, AnotherProctocol{
// implementation of protocol requirments goes here
}
```
按照这种方式添加的接口一致性被称之为**给扩展添加接口一致性(Protocal Conformance)**
##计算属性
扩展可以向已有类型添加计算实例属性和计算类型属性。下面的例子向Swift的内建`Double`类型添加了5个计算实例属性从而提供与距离单位协作的基本支持。
```javascript
extension Double{
var km: Double { return self * 1_000.0 }
var m : Double { return self }
var cm: Double { return self / 100.0 }
var mm: Double { return self / 1_000.0 }
var ft: Double { return self / 3.28084 }
}
let oneInch = 25.4.mm
println("One inch is \(oneInch) meters")
// prints "One inch is 0.0254 meters"
let threeFeet = 3.ft
println("Three feet is \(threeFeet) meters")
// prints "Three feet is 0.914399970739201 meters"
```
这些计算属性表达的含义是把一个`Double`型的值看作是某单位下的长度值。即使它们被实现为计算属性但这些属性仍可以接一个带有dot语法的浮点型字面值而这恰恰是使用这些浮点型字面量实现距离转换的方式。
在上述例子中,一个`Double`型的值`1.0`被用来表示“1米”。这就是为什么`m`计算属性返回`self`——表达式`1.m`被认为是计算`1.0``Double`值。
其它单位则需要一些转换来表示在米下测量的值。1千米等于1,000米所以`km`计算属性要把值乘以`1_000.00`来转化成单位米下的数值。类似地1米有3.28024英尺,所以`ft`计算属性要把对应的`Double`值除以`3.28024`来实现英尺到米的单位换算。
这些属性是只读的计算属性,所有从简考虑它们不用`get`关键字表示。它们的返回值是`Double`型,而且可以用于所有接受`Double`的数学计算中:
```javascript
let aMarathon = 42.km + 195.m
println("A marathon is \(aMarathon) meters long")
// prints "A marathon is 42495.0 meters long"
```
>注意
扩展可以添加新的计算属性,但是不可以添加存储属性,也不可以向已有属性添加属性观测器(property observers)。
##构造器
扩展可以向已有类型添加新的构造器。这可以让你扩展其它类型,将你自己的定制类型作为构造器参数,或者提供该类型的原始实现中没有包含的额外初始化选项。
>注意
如果你使用扩展向一个值类型添加一个构造器该构造器向所有的存储属性提供默认值而且没有定义任何定制构造器custom initializers那么对于来自你的扩展构造器中的值类型你可以调用默认构造器(default initializers)和成员级构造器(memberwise initializers)。
正如在值类型的构造器授权中描述的,如果你已经把构造器写成值类型原始实现的一部分,上述规则不再适用。
下面的例子定义了一个用于描述几何矩形的定制结构体`Rect`。这个例子同时定义了两个辅助结构体`Size``Point`,它们都把`0.0`作为所有属性的默认值:
```javascript
struct Size{
var width = 0.0, height = 0.0
}
struct Point{
var x = 0.0, y = 0.0
}
struct Rect{
var origin = Point()
var size = Size()
}
```
因为结构体`Rect`提供了其所有属性的默认值,所以正如默认构造器中描述的,它可以自动接受一个默认的构造器和一个成员级构造器。这些构造器可以用于构造新的`Rect`实例:
```javascript
let defaultRect = Rect()
let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
```
你可以提供一个额外的使用特殊中心点和大小的构造器来扩展`Rect`结构体:
```javascript
extension Rect{
init(center: Point, size: Size){
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
```
这个新的构造器首先根据提供的`center``size`值计算一个合适的原点。然后调用该结构体自动的成员构造器`init(origin:size:)`,该构造器将新的原点和大小存到了合适的属性中:
```javascript
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)
```
>注意
如果你使用扩展提供了一个新的构造器,你依旧有责任保证构造过程能够让所有实例完全初始化。
##方法
扩展可以向已有类型添加新的实例方法和类型方法。下面的例子向`Int`类型添加一个名为`repetitions`的新实例方法:
```javascript
extension Int{
func repetitions(task: () -> ()){
for i in 0..self{
task()
}
}
}
```
这个`repetitions`方法使用了一个`() -> ()`类型的单参数single argument表明函数没有参数而且没有返回值。
定义该扩展之后,你就可以对任意整数调用`repetitions`方法,实现的功能则是多次执行某任务:
```javascript
3.repetitions({
println("Hello!")
})
// Hello!
// Hello!
// Hello!
```
可以使用trailing闭包使调用更加简洁
```javascript
3.repetitions{
println("Goodbye!")
}
// Goodbye!
// Goodbye!
// Goodbye!
```
###修改实例方法
通过扩展添加的实例方法也可以修改该实例本身。结构体和枚举类型中修改`self`或其属性的方法必须将该实例方法标注为`mutating`,正如来自原始实现的修改方法一样。
下面的例子向Swift的`Int`类型添加了一个新的名为`square`的修改方法,来实现一个原始值的平方计算:
```javascript
extension Int{
mutating func square(){
self = self * self
}
}
var someInt = 3
someInt.square()
// someInt is now 9
```
##下标
扩展可以向一个已有类型添加新下标。这个例子向Swift内建类型`Int`添加了一个整型下标。该下标`[n]`返回十进制数字从右向左数的第n个数字
- 123456789[0]返回9
- 123456789[1]返回8
等等
```javascript
extension Int{
subscript(digitIndex: Int) -> Int {
var decimalBase = 1
for _ in 1...digitIndex{
decimalBase *= 10
}
return (self / decimalBase) % 10
}
}
746381295[0]
// returns 5
746381295[1]
// returns 9
746381295[2]
// returns 2
746381295[8]
// returns 7
```
如果该`Int`值没有足够的位数即下标越界那么上述实现的下标会返回0因为它会在数字左边自动补0
```javascript
746381295[9]
//returns 0, 即等同于:
0746381295[9]
```
##嵌套类型
扩展可以向已有的类、结构体和枚举添加新的嵌套类型:
```javascript
extension Character {
enum Kind {
case Vowel, Consonant, Other
}
var kind: Kind {
switch String(self).lowercaseString {
case "a", "e", "i", "o", "u":
return .Vowel
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
return .Consonant
default:
return .Other
}
}
}
```
该例子向`Character`添加了新的嵌套枚举。这个名为`Kind`的枚举表示特定字符的类型。具体来说,就是表示一个标准的拉丁脚本中的字符是元音还是辅音(不考虑口语和地方变种),或者是其它类型。
这个类子还向`Character`添加了一个新的计算实例属性,即`kind`,用来返回合适的`Kind`枚举成员。
现在,这个嵌套枚举可以和一个`Character`值联合使用了:
```javascript
func printLetterKinds(word: String) {
println("'\\(word)' is made up of the following kinds of letters:")
for character in word {
switch character.kind {
case .Vowel:
print("vowel ")
case .Consonant:
print("consonant ")
case .Other:
print("other ")
}
}
print("\n")
}
printLetterKinds("Hello")
// 'Hello' is made up of the following kinds of letters:
// consonant vowel consonant consonant vowel
```
函数`printLetterKinds`的输入是一个`String`值并对其字符进行迭代。在每次迭代过程中,考虑当前字符的`kind`计算属性,并打印出合适的类别描述。所以`printLetterKinds`就可以用来打印一个完整单词中所有字母的类型,正如上述单词`"hello"`所展示的。
>注意
由于已知`character.kind``Character.Kind`型,所以`Character.Kind`中的所有成员值都可以使用`switch`语句里的形式简写,比如使用 `.Vowel`代替`Character.Kind.Vowel`

View File

@ -0,0 +1,115 @@
# 特性
特性提供了关于声明和类型的更多信息。在Swift中有两类特性用于修饰声明的以及用于修饰类型的。例如`required`特性,当应用于一个类的指定或便利初始化器声明时,表明它的每个子类都必须实现那个初始化器。再比如`noreturn`特性,当应用于函数或方法类型时,表明该函数或方法不会返回到它的调用者。
通过以下方式指定一个特性:符号`@`后面跟特性名,如果包含参数,则把参数带上:
```
@attribute name
@attribute name(attribute arguments)
```
有些声明特性通过接收参数来指定特性的更多信息以及它是如何修饰一个特定的声明的。这些特性的参数写在小括号内,它们的格式由它们所属的特性来定义。
## 声明特性
声明特性只能应用于声明。然而,你也可以将`noreturn`特性应用于函数或方法类型。
`assignment`
该特性用于修饰重载了复合赋值运算符的函数。重载了复合赋值运算符的函数必需将它们的初始输入参数标记为`inout`。如何使用`assignment`特性的一个例子,请见:[复合赋值运算符]()。
`class_protocol`
该特性用于修饰一个协议表明该协议只能被类类型采用[待改adopted]。
如果你用`objc`特性修饰一个协议,`class_protocol`特性就会隐式地应用到该协议,因此无需显式地用`class_protocol`特性标记该协议。
`exported`
该特性用于修饰导入声明,以此来导出已导入的模块,子模块,或当前模块的声明。如果另一个模块导入了当前模块,那么那个模块可以访问当前模块的导出项。
`final`
该特性用于修饰一个类或类中的属性,方法,以及下标成员。如果用它修饰一个类,那么这个类则不能被继承。如果用它修饰类中的属性,方法或下标,则表示在子类中,它们不能被重写。
`lazy`
该特性用于修饰类或结构体中的存储型变量属性,表示该属性的初始值最多只被计算和存储一次,且发生在第一次访问它时。如何使用`lazy`特性的一个例子,请见:[惰性存储型属性]()。
`noreturn`
该特性用于修饰函数或方法声明,表明该函数或方法的对应类型,`T`,是`@noreturn T`。你可以用这个特性修饰函数或方法的类型,这样一来,函数或方法就不会返回到它的调用者中去。
对于一个没有用`noreturn`特性标记的函数或方法,你可以将它重写(override)为用该特性标记的。相反,对于一个已经用`noreturn`特性标记的函数或方法你则不可以将它重写为没使用该特性标记的。相同的规则试用于当你在一个comforming类型中实现一个协议方法时。
`NSCopying`
该特性用于修饰一个类的存储型变量属性。该特性将使属性的setter与属性值的一个副本合成`copyWithZone`方法返回,而不是属性本身的值。该属性的类型必需遵循`NSCopying`协议。
`NSCopying`特性的行为与Objective-C中的`copy`特性相似。
`NSManaged`
该特性用于修饰`NSManagedObject`子类中的存储型变量属性表明属性的存储和实现由Core Data在运行时基于相关实体描述动态提供。
`objc`
该特性用于修饰任意可以在Objective-C中表示的声明比如非嵌套类协议类和协议中的属性和方法包含getter和setter初始化器析构器以下下标。`objc`特性告诉编译器该声明可以在Objective-C代码中使用。
如果你将`objc`特性应用于一个类或协议,它也会隐式地应用于那个类或协议的成员。对于标记了`objc`特性的类,编译器会隐式地为它的子类添加`objc`特性。标记了`objc`特性的协议不能继承自没有标记`objc`的协议。
`objc`特性有一个可选的参数,由标记符组成。当你想把`objc`所修饰的实体以一个不同的名字暴露给Objective-C你就可以使用这个特性参数。你可以使用这个参数来命名类协议方法getterssetters以及初始化器。下面的例子把`ExampleClass``enabled`属性的getter暴露给Objective-C名字是`isEnabled`,而不是它原来的属性名。
```
@objc
class ExampleClass {
var enabled: Bool {
@objc(isEnabled) get {
// Return the appropriate value
}
}
}
```
`optional`
用该特性修饰协议的属性方法或下标成员表示实现这些成员并不需要一致性类型conforming type
你只能用`optional`特性修饰那些标记了`objc`特性的协议。因此只有类类型可以adopt和comform to那些包含可选成员需求的协议。更多关于如何使用`optional`特性以及如何访问可选协议成员的指导例如当你不确定一个conforming类型是否实现了它们请见[可选协议需求]()。
`required`
用该特性修饰一个类的指定或便利初始化器,表示该类的所有子类都必需实现该初始化器。
加了该特性的指定初始化器必需显式地实现,而便利初始化器既可显式地实现,也可以在子类实现了超类所有指定初始化器后继承而来(或者当子类使用便利初始化器重写了指定初始化器)。
### Interface Builder使用的声明特性
Interface Builder特性是Interface Builder用来与Xcode同步的声明特性。Swift提供了以下的Interface Builder特性`IBAction``IBDesignable``IBInspectable`,以及`IBOutlet`。这些特性与Objective-C中对应的特性在概念上是相同的。
`IBOutlet``IBInspectable`用于修饰一个类的属性声明;`IBAction`特性用于修饰一个类的方法声明;`IBDesignable`用于修饰类的声明。
## 类型特性
类型特性只能用于修饰类型。然而,你也可以用`noreturn`特性去修饰函数或方法声明。
`auto_closure`
这个特性通过自动地将表达式封闭到一个无参数闭包中来延迟表达式的求值。使用该特性修饰无参的函数或方法类型,返回表达式的类型。一个如何使用`auto_closure`特性的例子,见[函数类型]()
`noreturn`
该特性用于修饰函数或方法的类型,表明该函数或方法不会返回到它的调用者中去。你也可以用它标记函数或方法的声明,表示函数或方法的相应类型,`T`,是`@noreturn T`
> 特性的语法
> attribute -> @ [attribute-name]() [attribute-argument-clause]()opt
> attribute-name -> [identifier]()
> attribute-argument-clause -> ( [balanced-tokens]()opt )
> attributes -> [attribute]() [attributes]()opt
> balanced-tokens -> [balanced-token]() [balanced-tokens]()opt
> balanced-token -> ( [balanced-tokens]()opt )
> balanced-token -> [ [balanced-tokens]()opt ]
> balanced-token -> { [balanced-tokens]()opt }
> balanced-token -> 任意标识符,关键字,字面量,或运算符
> balanced-token -> 任意标点符号,除了(, ), [, ], {, 或 }

View File