2.22 扩展

原文链接: https://docs.swift.org/latest/documentation/the-swift-programming-language/extensions/

2.22 扩展

为已有的类型添加功能。

扩展为已有的类、结构体、枚举或协议类型添加新功能。这包括为那些你无法访问其原始源代码的类型添加功能(称为追溯建模)。扩展与 Objective-C 中的分类(category)相似。(与 Objective-C 的分类不同,Swift 扩展没有名字。)

Swift 中的扩展可以:

  • 添加计算实例属性和计算类型属性
  • 定义实例方法和类型方法
  • 提供新的构造器
  • 定义下标
  • 定义并使用新的嵌套类型
  • 让已有类型遵循某个协议

在 Swift 中,你甚至可以扩展协议,为它的要求提供实现,或者添加遵循类型可以利用的额外功能。更多细节参见协议扩展。

注意:扩展可以为类型添加新功能, 但不能覆盖已有的功能。

扩展语法

用 extension 关键字声明扩展:

1
2
3
extension SomeType {
    // 要添加到 SomeType 的新功能写在这里
}

扩展可以扩展已有类型,让它采纳一个或多个协议。要添加协议遵循性,协议名的写法和为类或结构体写协议名的方式相同:

1
2
3
extension SomeType: SomeProtocol, AnotherProtocol {
    // 协议要求的实现写在这里
}

用这种方式添加协议遵循性在用扩展添加协议遵循性中介绍。

扩展可以用来扩展已有的泛型类型,详见扩展泛型类型。你也可以扩展泛型类型,有条件地添加功能,详见带泛型 where 子句的扩展。

注意:如果你通过定义扩展为已有类型添加新功能, 那么该类型所有已有的实例都能使用这些新功能, 即使这些实例是在扩展定义之前创建的。

计算属性

扩展可以为已有类型添加计算实例属性和计算类型属性。下面这个例子为 Swift 内置的 Double 类型添加了五个计算实例属性,为处理距离单位提供基本支持:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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
print("One inch is \(oneInch) meters")
// 输出 "One inch is 0.0254 meters"。
let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters")
// 输出 "Three feet is 0.914399970739201 meters"。

这些计算属性表示某个 Double 值应被视为某种长度单位。虽然它们是以计算属性实现的,但这些属性的名字可以用点语法附加到浮点字面量后面,从而用该字面量进行距离换算。

在这个例子里,Double 值 1.0 被认为表示"一米"。这就是 m 计算属性返回 self 的原因——表达式 1.m 被视为计算出一个值为 1.0 的 Double。

其他单位需要经过一些换算才能表示为以米为单位的数值:1 千米等于 1,000 米,因此 km 计算属性把该值乘以 1_000.00,换算成以米表示的数值;类似地,1 米等于 3.28084 英尺,因此 ft 计算属性把底层 Double 值除以 3.28084,把英尺换算成米。

这些属性是只读计算属性,因此为了简洁,书写时省略了 get 关键字。它们的返回值类型是 Double,凡是能接受 Double 的数学计算中都可以使用它们:

1
2
3
let aMarathon = 42.km + 195.m
print("A marathon is \(aMarathon) meters long")
// 输出 "A marathon is 42195.0 meters long"。

注意:扩展可以添加新的计算属性,但不能添加存储属性, 也不能为已有属性添加属性观察器。

构造器

扩展可以为已有类型添加新的构造器。这让你能够扩展其他类型,让它们接受你自己的自定义类型作为构造器参数,或者提供该类型原始实现中未包含的额外初始化方式。

扩展可以为类添加新的便利构造器,但不能为类添加新的指定构造器或反初始化器。指定构造器和反初始化器必须始终由类的原始实现提供。

如果你用扩展为一个值类型添加构造器,而该值类型为所有存储属性都提供了默认值、并且没有定义任何自定义构造器,那么你可以在扩展的构造器中调用该值类型的默认构造器和逐成员构造器。如果这个构造器写在值类型的原始实现中,情况就不是这样了,详见值类型的构造器委托。

如果你用扩展为一个在另一个模块中声明的结构体添加构造器,那么新构造器在调用该模块定义的构造器之前不能访问 self。

下面的例子定义了一个自定义 Rect 结构体来表示几何矩形,还定义了两个辅助结构体 Size 和 Point,它们的属性都有默认值 0.0:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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 实例:

1
2
3
let defaultRect = Rect()
let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
    size: Size(width: 5.0, height: 5.0))

你可以扩展 Rect 结构体,提供一个额外的构造器,接受指定的中心点和尺寸:

1
2
3
4
5
6
7
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:),把新的原点和尺寸值存进相应的属性:

1
2
3
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
    size: Size(width: 3.0, height: 3.0))
// centerRect 的原点是 (2.5, 2.5),尺寸是 (3.0, 3.0)

注意:如果你用扩展提供新的构造器, 你仍然有责任确保构造器结束后每个实例都已被完整初始化。

方法

扩展可以为已有类型添加新的实例方法和类型方法。下面的例子为 Int 类型添加了一个名为 repetitions 的新实例方法:

1
2
3
4
5
6
7
extension Int {
    func repetitions(task: () -> Void) {
        for _ in 0..<self {
            task()
        }
    }
}

repetitions(task:) 方法接受一个 () -> Void 类型的实参,表示一个没有参数、也不返回值的函数。

定义这个扩展之后,你可以在任何整数上调用 repetitions(task:) 方法,把某项任务执行相应次数:

1
2
3
4
5
6
3.repetitions {
    print("Hello!")
}
// Hello!
// Hello!
// Hello!

可变的实例方法

用扩展添加的实例方法也可以修改(或变更)实例本身。会修改 self 或其属性的结构体与枚举方法,必须像原始实现中的可变方法那样把该实例方法标记为 mutating。

下面的例子为 Swift 的 Int 类型添加了一个名为 square 的新可变方法,把原来的值平方:

1
2
3
4
5
6
7
8
extension Int {
    mutating func square() {
        self = self * self
    }
}
var someInt = 3
someInt.square()
// someInt 现在是 9

下标

扩展可以为已有类型添加新的下标。这个例子为 Swift 内置的 Int 类型添加了一个整数下标,这个下标 [n] 返回从右边数第 n 位的十进制数字:

  • 123456789[0] 返回 9
  • 123456789[1] 返回 8

……依此类推:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
extension Int {
    subscript(digitIndex: Int) -> Int {
        var decimalBase = 1
        for _ in 0..<digitIndex {
            decimalBase *= 10
        }
        return (self / decimalBase) % 10
    }
}
746381295[0]
// 返回 5
746381295[1]
// 返回 9
746381295[2]
// 返回 2
746381295[8]
// 返回 7

如果 Int 值的位数不足以满足请求的下标,下标的实现会返回 0,就好像这个数在左边补了零一样:

1
2
3
746381295[9]
// 返回 0,就好像你请求的是:
0746381295[9]

嵌套类型

扩展可以为已有的类、结构体和枚举添加新的嵌套类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
extension Int {
    enum Kind {
        case negative, zero, positive
    }
    var kind: Kind {
        switch self {
        case 0:
            return .zero
        case let x where x > 0:
            return .positive
        default:
            return .negative
        }
    }
}

这个例子为 Int 添加了一个新的嵌套枚举。这个名为 Kind 的枚举表示某个整数属于哪一类数,具体来说就是表示它是负数、零还是正数。

这个例子还为 Int 添加了一个名为 kind 的新计算实例属性,返回该整数对应的 Kind 枚举成员。

现在这个嵌套枚举可以用于任何 Int 值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func printIntegerKinds(_ numbers: [Int]) {
    for number in numbers {
        switch number.kind {
        case .negative:
            print("- ", terminator: "")
        case .zero:
            print("0 ", terminator: "")
        case .positive:
            print("+ ", terminator: "")
        }
    }
    print("")
}
printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
// 输出 "+ + - 0 - 0 + "。

这个 printIntegerKinds(_:) 函数接受一个 Int 值数组作为输入,依次遍历这些值。对数组中的每个整数,函数考察它的 kind 计算属性,并打印出相应的描述。

注意:已经知道 number.kind 的类型是 Int.Kind。 因此,switch 语句中所有 Int.Kind 成员值 都可以写成简写形式, 例如用 .negative 而不是 Int.Kind.negative。