扩展(Extensions)

1,469 阅读8分钟

原文

扩展给已存在的类,结构体,枚举,或者协议类型增加的新的功能。包括为你不能使用访问原始资源代码的类型的扩展的能力(回溯建模)。扩展和Objective-C中的代理很相似。(不想Objective-C分类,swift扩展没有名字。)

swift中的扩展可以:、

  • 增加计算实例属性和计算类型属性
  • 定义实例方法和类型方法
  • 提供新的初始化器
  • 定义下标
  • 定义和使用新的内嵌类型
  • 使一个已存在的类型遵守一个协议

swift中,甚至可以扩展一个协议来提供它的必要条件的实现或者增加遵守的类型可以使用的附加的功能。更多的信息,查看Protocol Extensions

扩展可以给一个类型增加新的功能,但是他们不能重写已经存在的功能。

扩展语法(Extension Syntax)

使用extension关键字声明扩展:

extension SomeType {
    // new functionality to add to SomeType goes here
}

一个扩展可以对已存在的类型进行扩展使他采用一个或者多个协议。要增加遵循协议,和你在一个类或者结构体中写的一样写协议的名字:

extension SomeType: SomeProtocol, AnotherProtocol {
    // implementation of protocol requirements goes here
}

这种方式添加协议一致性在Adding Protocol Conformance with an Extension中描述。

扩展可以用来扩展一个已存在的通用类型,像Extending a Generic Type中描述的。也可以把通用类型扩展为有条件的增加功能,像Extensions with a Generic Where Clause中描述的。

如果你定义了一个扩展给已存在的类型增加新的功能,新的功能会在这个类型全部已存在的实例上可用,即使他们在扩展定义之前创建。

计算属性(Computed Properties)

扩展可以给已存在类型增加计算实例属性和计算类型属性。这个例子给swift的built-in Double类型增加五个计算实例属性,

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")
// Prints "One inch is 0.0254 meters"
let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters")
// Prints "Three feet is 0.914399970739201 meters"

这些计算属性表示应该把一个Double值当做一个确定的长度单位。即使他们作为计算属性实现,这些属性的名字可以用点语法追加给一个浮点类型的字面量,像是用字面量执行距离转化的一个方式。

这个例子中,1.0的Double值认为代表“one meter”。这是为什么计算属性m返回self--表达式1.m认为是计算一个1.0的Double值。

其他单位需要一些按米估算的值来表示的转换。One kilometer和1000meters一样,所以km计算属性用1_000.00乘以值来转换为一个用meter表示的数字。相似的,1meter中有3.28084英尺,所以ft计算属性使下面的Double值被3.28084整除,将它从feet转换为meters。

这些属性是只读计算属性,所以他们没有get关键字表示,为了简洁。他们的返回值是Double类型,他们可以用在任何接受Double的数学计算中:

let aMarathon = 42.km + 195.m
print("A marathon is \(aMarathon) meters long")
// Prints "A marathon is 42195.0 meters long"

扩展可以增加计算属性,但是他们不能增加存储属性,或者给已经存在的属性增加观察者。

初始化器(Initializers)

扩展可以给已存在的类型增加新的初始化器。这使你可以扩展其他的类型来作为初始化参数接受你自己定义的类型,或者提供额外的在原始实现中没有的初始化选项。

扩展可以给类增加新的遍历初始化器,但是他们不能给类提供新的设计初始化器或者增加deinitializer。设计初始化器和deinitializer必须有原始类实现提供。

如果你使用扩展给一个为全部属性提供默认值并没有提供任何自定义初始化器的值类型提供初始化器,你可以在你的扩展初始化器中为那个值类型调用默认初始化器和成员初始化器。如果你在值类型的原始实现中写了初始化器的话这样就不可以了,像Initializer Delegation for Value Types中描述的。

如果你使用扩展给一个在其他模块中声明的结构体增加初始化器,新的初始化器在它调用定义模块中的初始化器之前新初始化器不能访问self。

下面的例子定义了一个自定义Rect结构体来表示一个集合矩形。这个例子也定义了两个名为Size和Point的辅助的结构体,两个为他们全部的属性都提供了默认值0.0:

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结构体为他们全部的属性提供了默认值,它自动接收一个默认初始化器和一个成员初始化器,像在Default Initializers中描述的。这写初始化化器可以用来创建新的Rect实例:

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

你可以扩展Rect结构体来提供一个额外的接受一个指定center点和size的初始化器:

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:),把新的原点和尺寸的值存在合适的属性中:

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)

如果你用扩展提供一个新的初始化器,你仍然负责确保每个实例在初始化完成的时候完全初始化。

方法(Methods)

扩展可以给已存在的类型增加新的实例方法和类型方法。下面的例子给Int类型增加新的名为repetitons的实例方法:

extension Int {
    func repetitions(task: () -> Void) {
        for _ in 0..<self {
            task()
        }
    }
}

repetitions(task:)方法接受一个单一的()->Void类型的参数,指定一个没有参数和返回值的函数。

定义了这个扩展之后,你可以在任何整型上调用repetitions(task:)方法来多次执行一个任务:

3.repetitions {
    print("Hello!")
}
// Hello!
// Hello!
// Hello!

可变实例方法(Mutating Instance Methods)

用扩展增加的实例方法也可以修改实例自己。Structure和enumeration的修改自己或者他的属性的方法必须要把方法标记为mutating,就像原始实现中的mutating 方法。

下面的例子给swift的Int类型增加了一个名为square的mutating方法,对原始值进行平方:

extension Int {
    mutating func square() {
        self = self * self
    }
}
var someInt = 3
someInt.square()
// someInt is now 9

下标(Subscripts)

扩展给已存在的类型增加新的下标。这个例子给swift的内建类型增加了一个整型下标。这个下标[n]返回在数字右边n上放置的十进制数字:

  • 123456789[0] returns 9
  • 123456789[1] returns 8

。。等等:

extension Int {
    subscript(digitIndex: Int) -> Int {
        var decimalBase = 1
        for _ in 0..<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,就像数字用零向左填补:

746381295[9]
// returns 0, as if you had requested:
0746381295[9]

内嵌类型(Nested Types)

扩展可以给存在的类,结构体和枚举增加新的内嵌类型:

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,表示特定整型表示的数字种类。特别的是,他表示数字是否是负数,0,或者正数。

内嵌函数现在可以用任何Int值使用了:

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])
// Prints "+ + - 0 - 0 + "

这个函数,printIntegerKinds(_:),接受一个输入的Int值的数组并且逐个遍历这些值。对数组中的每个整型,函数为整型分析kind计算属性,打印一个合适的描述。

number.kind已经知道是Int.Kind类型。为此,全部的Int.Kind值可以在switch语句中用速写形式写,例如.negative而不是Int.Kind.negaive。