20_Extensions (#822)

* 4.2更新

* 校对之后的修改
This commit is contained in:
灰s
2018-11-27 23:40:10 +08:00
committed by Jie Liang
parent 884f50e5dc
commit 18d24826d2

View File

@ -1,99 +1,94 @@
# 扩展 # 扩展
*扩展* 就是为一个有的类结构体枚举类型或者协议类型添加新功能。这包括在没有权限获取原始源代码的情况下扩展类型的能力(即 *逆向建模* )。扩展和 Objective-C 的分类似。(与 Objective-C 不同的是Swift 扩展没有名字。) *扩展*可以给一个有的类结构体枚举,还有协议添加新功能。它还拥有不需要访问被扩展类型源代码就能完成扩展的能力(即*逆向建模*)。扩展和 Objective-C 的分类很相似。(与 Objective-C 分类不同的是Swift 扩展没有名字。)
Swift 中的扩展可以: Swift 中的扩展可以:
- 添加计算型属性和计算型类属性 - 添加计算型实例属性和计算型类属性
- 定义实例方法和类方法 - 定义实例方法和类方法
- 提供新的构造器 - 提供新的构造器
- 定义下标 - 定义下标
- 定义和使用新的嵌套类型 - 定义和使用新的嵌套类型
- 使一个已有类型符合某个协议 - 使已经存在的类型遵循conform个协议
在 Swift 中,你甚至可以对协议进行扩展,提供协议要求的实现,或者添加额外功能,从而可以让符合协议的类型拥有这些功能。你可以从[协议扩展](./21_Protocols.html#protocol_extensions)获取更多细节。 在 Swift 中,你甚至可以扩展协议以提供其需要的实现,或者添加额外功能给遵循的类型所使用。你可以从 [协议扩展](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID521) 获取更多细节。
> 注意 > 注意
> >
> 扩展可以一个类型添加新的功能,但是不能重写已的功能。 > 扩展可以一个类型添加新的功能,但是不能重写已经存在的功能。
<a name="extension_syntax"></a> ## 扩展的语法
## 扩展语法 使用 **extension** 关键字声明扩展:
使用关键字 `extension` 来声明扩展:
```swift ```swift
extension SomeType { extension SomeType {
// SomeType 添加的新功能写到这里 // 在这里给 SomeType 添加新的功能
} }
``` ```
可以通过扩展来扩展一个已有类型,使其采纳一个或多个协议。在这种情况下,无论是类还是结构体,协议名字的书写方式完全一样: 扩展可以扩充一个现有的类型,给它添加一个或多个协议。协议名称的写法和类或者结构体一样:
```swift ```swift
extension SomeType: SomeProtocol, AnotherProctocol { extension SomeType: SomeProtocol, AnotherProtocol {
// 协议实现写这里 // 协议所需要的实现写这里
} }
``` ```
通过这种方式添加协议一致性的详细描述请参阅[利用扩展添加协议一致性](./21_Protocols.html#adding_protocol_conformance_with_an_extension)。 这种遵循协议的方式在 [使用扩展遵循协议](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID277) 中有描述。
扩展可以使用在现有范型类型上,就像 [扩展范型类型](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID185) 中描述的一样。你还可以使用扩展给泛型类型有条件的添加功能,就像 [扩展一个带有 Where 字句的范型](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID553) 中描述的一样。
> 注意 > 注意
> >
> 如果你通过扩展为一个已有类型添加新功能,那么新功能对该类型的所有已有实例都是可用的,即使它们是在这个扩展定义之前创建的。 > 对一个现有的类型,如果你定义了一个扩展来添加新功能,那么这个类型的所有实例都可以使用这个新功能,包括那些在扩展定义之前就存在的实例。
<a name="computed_properties"></a>
## 计算型属性 ## 计算型属性
扩展可以给现有类型添加计算型实例属性和计算型类属性。这个例子给 Swift 内建的 **Double** 类型添加了五个计算型实例属性,从而提供与距离单位相关工作的基本支持:
扩展可以为已有类型添加计算型实例属性和计算型类型属性。下面的例子为 Swift 的内建 `Double` 类型添加了五个计算型实例属性,从而提供与距离单位协作的基本支持:
```swift ```swift
extension Double { extension Double {
var km: Double { return self * 1_000.0 } var km: Double { return self * 1_000.0 }
var m : Double { return self } var m: Double { return self }
var cm: Double { return self / 100.0 } var cm: Double { return self / 100.0 }
var mm: Double { return self / 1_000.0 } var mm: Double { return self / 1_000.0 }
var ft: Double { return self / 3.28084 } var ft: Double { return self / 3.28084 }
} }
let oneInch = 25.4.mm let oneInch = 25.4.mm
print("One inch is \(oneInch) meters") print("One inch is \(oneInch) meters")
// 打印 “One inch is 0.0254 meters” // 打印“一英寸是 0.0254 米”
let threeFeet = 3.ft let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters") print("Three feet is \(threeFeet) meters")
// 打印 “Three feet is 0.914399970739201 meters // 打印“三英尺是 0.914399970739201
``` ```
这些计算型属性表的含义是把一个 `Double` 值看作是某单位下的长度值。即使它们被实现为计算型属性,但这些属性的名字仍可紧接一个浮点型字面值,从而通过点语法来使用,并以此实现距离转换。 这些计算型属性表的含义是把一个 **Double** 值看作是某单位下的长度值。即使它们被实现为计算型属性,但这些属性的名字仍可紧接一个浮点型字面值,从而通过点语法来使用,并以此实现距离转换。
在上述例子中,`Double``1.0` 用来表示“1米”。这就是为什么计算型属性 `m` 返回 `self`,即表达式 `1.m` 被认为是计算 `Double``1.0` 在上述例子中,**Double** 类型的 **1.0** 代表的是“一米”。这就是为什么计算型属性 **m** 返回的是 **self** - 表达式 **1.m** 被认为是计算一个 **Double** 类型的 **1.0**
其它单位则需要一些单位换算。一千米等于 1,000 米,所以计算型属性 `km` 要把值乘以 `1_000.00` 来实现千米到米的单位换算。类似地,一米有 3.28024 英尺,所以计算型属性 `ft` 要把对应的 `Double` 值除以 `3.28024` 来实现英尺到米的单位换算。 其它单位则需要一些单位换算。一千米等于 1,000 米,所以计算型属性 **km** 要把值乘以 **1_000.00** 来实现千米到米的单位换算。类似地,一米有 3.28084 英尺,所以计算型属性 **ft** 要把对应的 **Double** 值除以 **3.28084**来实现英尺到米的单位换算。
这些属性是只读的计算型属性,为了更简洁,省略了 `get` 关键字。它们的返回值是 `Double`,而且可以用于所有接受 `Double`的数学计算中: 这些属性是只读的计算型属性,所以为了简便,它们的表达式里面都不包含 **get** 关键字。它们使用 **Double** 作为返回值类型,并可用于所有接受 **Double** 类型的数学计算中:
```swift ```swift
let aMarathon = 42.km + 195.m let aMarathon = 42.km + 195.m
print("A marathon is \(aMarathon) meters long") print("A marathon is \(aMarathon) meters long")
// 打印 “A marathon is 42195.0 meters long // 打印“马拉松赛跑全长 42195.0 米。
``` ```
> 注意 > 注意
> >
> 扩展可以添加新的计算属性,但是不可以添加存储属性,也不可以为已有属性添加属性观察器。 > 扩展可以添加新的计算属性,但是它们不能添加存储属性,或向现有的属性添加属性观察者。
<a name="initializers"></a>
## 构造器 ## 构造器
扩展可以给现有的类型添加新的构造器。它使你可以把自定义类型作为参数来供其他类型的构造器使用,或者在类型的原始实现上添加额外的构造选项。
扩展可以为已有类型添加新的构造器。这可以让你扩展其它类型,将你自己的定制类型作为其构造器参数,或者提供该类型的原始实现中未提供的额外初始化选项。 扩展可以给一个类添加新的便利构造器,但是它们不能给类添加新的指定构造器或者析构器。指定构造器和析构器必须始终由类的原始实现提供。
扩展能为类添加新的便利构造器,但是它们不能为类添加新的指定构造器或析构器。指定构造器和析构器必须总是由原始的类实现来提供。 如果你使用扩展给一个值类型添加构造器只是用于给所有的存储属性提供默认值,并且没有定义任何自定义构造器,那么你可以在该值类型扩展的构造器中使用默认构造器和成员构造器。如果你把构造器写到了值类型的原始实现中,就像 [值类型的构造器委托](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID215) 中所描述的,那么就不属于在扩展中添加构造器。
> 注意 如果你使用扩展给另一个模块中定义的结构体添加构造器,那么新的构造器直到定义模块中使用一个构造器之前,不能访问 **self**
>
> 如果你使用扩展为一个值类型添加构造器,同时该值类型的原始实现中未定义任何定制的构造器且所有存储属性提供了默认值,那么我们就可以在扩展中的构造器里调用默认构造器和逐一成员构造器。
正如在[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)中描述的,如果你把定制的构造器写在值类型的原始实现中,上述规则将不再适用。
下面的例子定义了一个用于描述几何矩形的结构体 `Rect`。这个例子同时定义了两个辅助结构体 `Size``Point`,它们都把 `0.0` 作为所有属性的默认值: 下面的例子中,自定义了一个 **Rect** 结构体用来表示一个几何矩形。这个例子中还定义了两个给予支持的结构体 **Size** **Point**,它们都把属性的默认值设置为 **0.0**
```swift ```swift
struct Size { struct Size {
@ -108,7 +103,7 @@ struct Rect {
} }
``` ```
因为结构体 `Rect` 未提供定制的构造器,因此它会获得一个逐一成员构造器。又因为它为所有存储型属性提供了默认值,它又会获得一个默认构造器。详情请参阅[默认构造器](./14_Initialization.html#default_initializers)。这些构造器可以用于构造新的 `Rect` 实例: 因为 **Rect** 结构体给所有的属性提供了默认值,所以它自动获得一个默认构造器和一个成员构造器,就像 [默认构造器](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID213) 中描述的一样。这些构造器可以用来创建新的 **Rect** 实例:
```swift ```swift
let defaultRect = Rect() let defaultRect = Rect()
@ -116,7 +111,7 @@ let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0)) size: Size(width: 5.0, height: 5.0))
``` ```
你可以提供一个额外的接受指定中心点和大小的构造器来扩展 `Rect` 结构体: 你可以通过扩展 **Rect** 结构体来提供一个允许指定 point size 的构造器:
```swift ```swift
extension Rect { extension Rect {
@ -128,22 +123,20 @@ extension Rect {
} }
``` ```
这个新的构造器首先根据提供的 `center``size` 的值计算一个适的原点。然后调用结构体的逐一成员构造器 `init(origin:size:)`,该构造器将新的原点和大小的值保存到了相应的属性中: 这个新的构造器首先根据提供的 **center****size** 计算一个适的原点。然后这个构造器调用结构体自带的成员构造器 **init(origin:size:)**,它会将新的 origin 和 size 值储存在适当的属性中:
```swift ```swift
let centerRect = Rect(center: Point(x: 4.0, y: 4.0), let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0)) size: Size(width: 3.0, height: 3.0))
// centerRect 的原点是 (2.5, 2.5),大小是 (3.0, 3.0) // centerRect 的 origin 是 (2.5, 2.5) 并且它的 size 是 (3.0, 3.0)
``` ```
> 注意 > 注意
> >
> 如果你使用扩展提供一个新的构造器,你依旧有责任确保构造过程能够让实例完全初始化。 > 如果你通过扩展提供一个新的构造器,你有责任确保每个通过该构造器创建的实例都是初始化完整的。
<a name="methods"></a>
## 方法 ## 方法
扩展可以给现有类型添加新的实例方法和类方法。在下面的例子中,给 **Int** 类型添加了一个新的实例方法叫做 **repetitions**
扩展可以为已有类型添加新的实例方法和类型方法。下面的例子为 `Int` 类型添加了一个名为 `repetitions` 的实例方法:
```swift ```swift
extension Int { extension Int {
@ -155,26 +148,23 @@ extension Int {
} }
``` ```
这个 `repetitions(task:)` 方法接受一个 `() -> Void` 类型的参数,表示没有参数没有返回值的函数。 **repetitions(task:)** 方法仅接收一个 **() -> Void** 类型的参数,表示一个没有参数没有返回值的方法。
定义扩展之后,你可以对任意整数调用 `repetitions(task:)` 方法,将闭包中的任务执行整数对应次数 定义了这个扩展之后,你可以对任意整形数值调用 **repetitions(task:)** 方法,来执行对应次数的任务:
```swift ```swift
3.repetitions({ 3.repetitions {
print("Hello!") print("Hello!")
}) }
// Hello! // Hello!
// Hello! // Hello!
// Hello! // Hello!
``` ```
<a name="mutating_instance_methods"></a>
### 可变实例方法 ### 可变实例方法
通过扩展添加的实例方法同样也可以修改(或 *mutating(改变)*)实例本身。结构体和枚举的方法,若是可以修改 **self** 或者它自己的属性,则必须将这个实例方法标记为 **mutating**,就像是改变了方法的原始实现。
通过扩展添加的实例方法也可以修改该实例本身。结构体和枚举类型中修改 `self` 或其属性的方法必须将该实例方法标注为 `mutating`,正如来自原始实现的可变方法一样。 在下面的例子中,对 Swift **Int** 类型添加了一个新的 mutating 方法,叫做 **square**,它将原始值求平方:
下面的例子为 Swift 的 `Int` 类型添加了一个名为 `square` 的可变方法,用于计算原始值的平方值:
```swift ```swift
extension Int { extension Int {
@ -184,19 +174,16 @@ extension Int {
} }
var someInt = 3 var someInt = 3
someInt.square() someInt.square()
// someInt 的值现在是 9 // someInt 现在是 9
``` ```
<a name="subscripts"></a>
## 下标 ## 下标
扩展可以给现有的类型添加新的下标。下面的例子中,对 Swift **Int** 类型添加了一个整数类型的下标。下标 **[n]** 从数字右侧开始,返回小数点后的第 **n** 位:
扩展可以为已有类型添加新下标。这个例子为 Swift 内建类型 `Int` 添加了一个整型下标。该下标 `[n]` 返回十进制数字从右向左数的第 `n` 个数字: - **123456789[0]** returns **9**
- **123456789[1]** returns **8**
- `123456789[0]` 返回 `9`
- `123456789[1]` 返回 `8`
……以此类推。
……以此类推:
```swift ```swift
extension Int { extension Int {
subscript(digitIndex: Int) -> Int { subscript(digitIndex: Int) -> Int {
@ -208,27 +195,25 @@ extension Int {
} }
} }
746381295[0] 746381295[0]
// 返回 5 // returns 5
746381295[1] 746381295[1]
// 返回 9 // returns 9
746381295[2] 746381295[2]
// 返回 2 // returns 2
746381295[8] 746381295[8]
// 返回 7 // returns 7
``` ```
如果`Int` 值没有足够的位数,即下标越界,那么上述下标实现会返回 `0`,犹如在数字左边自动补 `0` 如果操作的 **Int** 值没有足够的位数满足所请求的下标,那么下标的现实将返回 **0**,将好像在数字左边补上了 0
```swift ```swift
746381295[9] 746381295[9]
// 返回 0即等同于 // 返回 0就好像你进行了这个请求
0746381295[9] 0746381295[9]
``` ```
<a name="nested_types"></a>
## 嵌套类型 ## 嵌套类型
扩展可以给现有的类,结构体,还有枚举添加新的嵌套类型:
扩展可以为已有的类、结构体和枚举添加新的嵌套类型:
```swift ```swift
extension Int { extension Int {
@ -248,11 +233,11 @@ extension Int {
} }
``` ```
该例子为 `Int` 添加了嵌套枚举。这个名为 `Kind` 的枚举表示特定整数的类型。具体来说,就是表示整数是正数、零或者负数。 这个例子给 **Int** 添加了一个新的嵌套枚举。这个枚举叫做 **Kind**,表示特定整数所代表的数字类型。具体来说,它表示数字是负的、零的还是正的。
这个例子还为 `Int` 添加了一个计算型实例属性,`kind`,用来根据整数返回适当的 `Kind` 枚举成员。 这个例子同样给 **Int** 添加了一个新的计算型实例属性,叫做 **kind**,它返回被操作整数所对应的 **Kind** 枚举 case 分支。
现在,这个嵌套枚举可以和任意 `Int` 值一起使用了: 现在,任意 **Int** 的值都可以使用这个嵌套类型:
```swift ```swift
func printIntegerKinds(_ numbers: [Int]) { func printIntegerKinds(_ numbers: [Int]) {
@ -269,11 +254,13 @@ func printIntegerKinds(_ numbers: [Int]) {
print("") print("")
} }
printIntegerKinds([3, 19, -27, 0, -6, 0, 7]) printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
// 打印 + + - 0 - 0 + // 打印 "+ + - 0 - 0 + "
``` ```
函数 `printIntegerKinds(_:)` 接受一个 `Int` 数组,然后对该数组进行迭代。在每次迭代过程中,对当前整数的计算型属性 `kind` 的值进行评估,并打印适当的描述。 方法 **printIntegerKinds(_:)**,使用一个 **Int** 类型的数组作为输入,然后依次迭代这些值。对于数组中的每一个整数,方法会检查它的 **kind** 计算型属性,然后打印适当的描述。
> 注意 > 注意
> >
> 由于已知 `number.kind` 是 `Int.Kind` 类型,因此在 `switch` 语句中`Int.Kind` 中的所有成员值都可以使用简写形式,例如使用 `.negative` 而不是 `Int.Kind.negative` > **number.kind** 已经被认为是 **Int.Kind** 类型。所以,在 **switch** 语句中所有的 **Int.Kind** case 分支可以被缩写,就像使用 **.negative** 替代 **Int.Kind.negative.**