16.1 扩展

在面向对象分析与设计方法学(OOAD)中,为了增强一个类的新功能,我们可以通过继承机制从父类继承下来一些成员,然后再根据自己的需要在子类中添加一些成员,这样我们就可以得到增强功能的新类了,但是这种方式受到了一些限制,继承过程比较繁琐,类继承性可能被禁止,有些功能也可能无法继承。

在Swift中可以使用一种扩展机制,在原有类型(类、结构体和枚举)的基础上添加新功能。扩展是一种“轻量级”的继承机制,即使原有类型被限制继承,我们仍然可以通过扩展机制“继承”原有类型的功能。

扩展机制还有另外一个优势:它扩展的类型可以是类、结构体和枚举,而继承只能是类,不能是结构体和枚举。

提示 对于扩展这种“轻量级”继承机制,只有Objective-C中的分类机制与此类似,其他面向对象的语言中均没有,因此很多Java程序员在使用Swift语言时,不擅长使用扩展机制,而是保守地使用继承机制。在设计基于Swift语言程序时,我们要优先考虑使用扩展机制是否能够满足我们的需求,如果不能再考虑使用继承机制。

16.1.1 声明扩展

声明扩展的语法格式如下:

  1. extension 类型名 {
  2. //添加新功能
  3. }

声明扩展的关键字是extension,“类型名”是Swift中已有的类型,包括类、结构体和枚举,但是我们仍然可以扩展整型、浮点型、布尔型、字符串等基本数据类型,这是因为这些类型本质上也是结构体类型。打开Int的定义如下:

  1. struct Int : SignedInteger {
  2. init()
  3. init(_ value: Int)
  4. static func convertFromIntegerLiteral(value: Int) -> Int
  5. typealias ArrayBoundType = Int
  6. func getArrayBoundValue() -> Int
  7. static var max: Int { get }
  8. static var min: Int { get }
  9. }

从定义可见Int是结构体类型。不仅是Int类型,我们熟悉的整型、浮点型、布尔型、字符串等数据类型本质上都是结构体类型。

具体而言,Swift中的扩展机制可以在原类型中添加的新功能包括:

  • 实例计算属性和静态计算属性

  • 实例方法和静态方法

  • 构造器

  • 下标

此外,还有嵌套类型等内容也可以扩展。下面我们将重点介绍扩展计算属性、扩展方法、扩展构造器和扩展下标。

16.1.2 扩展计算属性

我们可以在原类型上扩展计算属性,包括实例计算属性和静态计算属性。这些添加计算属性的定义,与普通的计算属性的定义是一样的。

下面先看一个实例计算属性示例。我们在网络编程的时候,为了减少流量,从服务器端返回的不是错误信息描述,而是错误编码,然后在本地再将错误编码转换为错误描述信息。为此我们定义了如下Int类型扩展:

  1. extension Int {
  2. var errorMessage : String {
  3. var errorStr = ""
  4. switch (self) {
  5. case -7:
  6. errorStr = "没有数据。"
  7. case -6:
  8. errorStr = "日期没有输入。"
  9. case -5:
  10. errorStr = "内容没有输入。"
  11. case -4:
  12. errorStr = "ID没有输入。"
  13. case -3:
  14. errorStr = "据访问失败。"
  15. case -2:
  16. errorStr = "您的账号最多能插入10条数据。"
  17. case -1:
  18. errorStr = "用户不存在,请到http://iosbook3.com注册。"
  19. default:
  20. errorStr = ""
  21. }
  22. return errorStr
  23. }
  24. }
  25. let message = (-7).errorMessage
  26. println("Error Code : -7 , Error Message : \(message)")

上述代码第①行定义Int类型的扩展,第②行代码定义只读计算属性errorMessage,第③行和第④行代码是switch分支语言,switch表达式是self,即当前实例,然后通过switchcase判断是哪个分支,并返回错误描述信息。我们在扩展中经常使用self获得当前实例。

第⑤行代码(-7).errorMessage是获得-7编码对应的错误描述信息。注意整个-7包括负号是一个完整的实例,因此调用它的属性时需要将-7作为一个整体用小括号括起来。然而,如果是7则不需要括号。

下面再看一个静态属性的示例:

  1. struct Account {
  2. var amount : Double = 0.0 //账户金额
  3. var owner : String = "" //账户名
  4. }
  5. extension Account {
  6. static var interestRate : Double { //利率 ③
  7. return 0.668
  8. }
  9. }
  10. println(Account.interestRate)

上述代码第①行是定义Account结构体,第②行代码是定义Account结构体的扩展类型,其中第③行代码是定义静态只读计算属性interestRateinterestRate是利率,对于所有账户都是一样的,所以它被定义为静态属性。

第④行代码是打印输出interestRate属性,访问方式与其他的静态计算属性一样,通过“类型名”加“.”来访问静态计算属性。

此外,在扩展中不仅可以定义只读计算属性,还可以定义读写计算属性、实例计算属性和静态计算属性。它们的定义方式与在原类型中定义的是一样的,这里不再赘述。

16.1.3 扩展方法

我们可以在原类型上扩展方法,包括实例方法和静态方法。这些添加方法的定义与普通方法的定义是一样的。

下面先看一个示例:

  1. extension Double {
  2. static var interestRate : Double = 0.668 //利率
  3. func interestBy1() -> Double {
  4. return self * Double.interestRate
  5. }
  6. mutating func interestBy2() {
  7. self = self Double.interestRate
  8. }
  9. static func interestBy3(amount : Double) -> Double {
  10. return interestRate amount
  11. }
  12. }
  13. let interest1 = (10_000.00).interestBy1()
  14. println("利息1 : \(interest1)")
  15. var interest2 = 10_000.00
  16. interest2.interestBy2()
  17. println("利息2 : \(interest2)")
  18. var interest3 = Double.interestBy3(10_000.00)
  19. println("利息3 : \(interest3)")

上述代码定义Double类型的扩展,其中第①行代码是定义实例方法interestBy1,该方法第②行代码self * Double.interestRate是计算利息,其中self是当前实例,Double.interestRate是静态属性利率。

第③行代码是定义实例方法interestBy2,它也可以计算利息,但是没有返回值,而是通过第④行代码self = self * Double.interestRate,把计算结果直接赋值给当前实例self。在结构体和枚举类型中给self赋值会有编译错误,需要在方法前面加上mutating关键字,表明这是变异方法。

第⑤行代码是定义静态方法interestBy3,它也可以计算利息,参数有返回值,参数是计算利息的金额,第⑥行代码是返回值是计算利息结果。

这3个方法在调用时是不同的,第⑦行代码是调用interestBy1方法计算利息,调用它的实例10_000.00,它的返回值被赋值给interest1常量,这是很常见的调用过程。

第⑧行代码是调用interestBy2方法计算利息,我们不能使用10_000.00实例调用,而是需要一个Double类型的变量interest2interestBy2是变异方法,它会直接改变变量interest2的值,因此第⑨行的interest2.interestBy2()语句调用完成后,变量interest2的值就改变了。

第⑩行代码是调用interestBy3方法计算利息,它是静态方法,调用它需要以“类型名.”的方式即“Double.”的方式调用。

16.1.4 扩展构造器

扩展类型的时候,也可以添加新的构造器。值类型与引用类型扩展有所区别。值类型包括了除类以外的其他类型,主要是枚举类型和结构体类型。

下列代码是扩展结构体类型中定义构造器的示例:

  1. struct Rectangle {
  2. var width : Double
  3. var height : Double
  4. init(width : Double, height : Double) {
  5. self.width = width
  6. self.height = height
  7. }
  8. }
  9. extension Rectangle {
  10. init(length : Double) {
  11. self.init(width : length, height : length)
  12. }
  13. }
  14. var rect = Rectangle(width : 320.0, height : 480.0)
  15. println("长方形:\(rect.width) x \(rect.height)")
  16. var square = Rectangle(length: 500.0)
  17. println("正方形:\(square.width) x \(square.height)")

上述代码第①行是定义结构体Rectangle,然后在第②行定义了Rectangle的扩展类型。其中第③行定义构造器init(length : Double),只有一个参数。然后在第④行调用self.init(width : length, height : length)语句,self.init是调用了原类型的两个参数的构造器。

第⑤行代码调用两个参数的构造器创建Rectangle实例,这个构造器是原类型提供的,这时候的Rectangle类型已经是第②行定义的扩展类型了。

第⑥行代码调用一个参数的构造器创建Rectangle实例,这个构造器是扩展类型提供的。

下面我们讨论一下引用类型扩展中定义构造器。引用类型只包含一个类型,即类类型。在类中,由于考虑到继承问题,类中构造器分为指定构造器和便利构造器。扩展类的时候能向类中添加新的便利构造器,但不能添加新的指定构造器或析构器。指定构造器和析构器只能由原类型提供。

下列代码是扩展类中定义构造器的示例:

  1. class Person {
  2. var name : String
  3. var age : Int
  4. func description() -> String {
  5. return "\(name) 年龄是: \(age)"
  6. }
  7. init (name : String, age : Int) {
  8. self.name = name
  9. self.age = age
  10. }
  11. }
  12. extension Person {
  13. convenience init (name : String) {
  14. self.init(name : name, age : 8)
  15. }
  16. }
  17. let p1 = Person(name : "Mary")
  18. println("Person1 : \(p1.description())")
  19. let p2 = Person(name : "Tony", age : 28)
  20. println("Person2 : \(p2.description())")

第①行代码是定义类Person,其中代码第②行提供了两个参数的构造器。第③行代码是定义Person类的扩展类型,其中代码第④行提供了一个参数的构造器,它是便利构造器。在这个构造器中,第⑤行代码self.init(name : name, age : 8)调用指定构造器代理部分构造任务。

第⑥行代码调用两个参数的构造器创建Person实例,这个构造器是原类型提供的,这时候的Person类型已经是第②行定义的扩展类型了。

第⑦行代码调用一个参数的构造器创建Person实例,这个构造器是扩展类型提供的。

16.1.5 扩展下标

我们可以把下标认为是特殊的属性,可以实现索引访问属性。我们可以在原有类型的基础上扩展下标功能。

字符串本身没有提供按照下标访问字符的功能。下面我们扩展字符串,实现下标访问字符功能,代码如下:

  1. extension String {
  2. subscript(index : Int) ->String {
  3. if index > countElements(self) {
  4. return ""
  5. }
  6. var c : String = ""
  7. var i = 0
  8. for character in self {
  9. if (i == index) {
  10. c = String(character)
  11. break
  12. }
  13. i++
  14. }
  15. return c
  16. }
  17. }
  18. let s = "The quick brown fox jumps over the lazy dog"
  19. println(s[0])
  20. println("ABC"[2])

上述代码是扩展字符串String类型,添加一个下标。第①行定义下标,Int类型参数是下标索引,返回值是String类型,是要访问的字符。第②行代码是判断下标是否越界,如果越界则返回空字符串。第③行代码是使用for in循环遍历字符串,第④行代码判断当前循环变量i是否等于index参数,如果相等,则通过第⑤行代码c = String(character)将字符character赋值字符串c变量。直接采用c = character语句赋值会发生错误,这是因为c是字符串类型,character是字符类型。

第⑦行代码声明并初始化字符串常量s,使用经典英语全字母句"The quick brown fox jumps over the lazy dog"1来初始化s常量。第⑧行代码s[0]是通过下标访问,结果输出为“T”。第⑨行代码"ABC"[2]是通过下标访问"ABC"字符串的内容,结果输出为“C”。

1“The quick brown fox jumps over the lazy dog”(中译为“敏捷的棕毛狐狸从懒狗身上跃过”)是一个著名的英语全字母句,常被用于测试字体的显示效果和键盘有没有故障。此句也常以“quick brown fox”作为指代简称。——引自于维基百科(http://zh.wikipedia.org/wiki/The_quick_brown_fox_jumps_over_the_lazy_dog