12.1. 实例方法(Instance Methods)

实例方法是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。实例方法的语法与函数完全一致,详情参见 第七章:函数

实例方法要写在它所属的类型的前后大括号之间。实例方法能够隐式访问它所属类型的所有的其他实例方法和属性。实例方法只能被它所属的类的某个特定实例调用。实例方法不能脱离于现存的实例而被调用。

下面的例子,定义一个很简单的 Counter 类,Counter 能被用来对一个动作发生的次数进行计数:

class Counter {
    var count = 0
    func increment() {
        count += 1
    }
    func increment(by amount: Int) {
        count += amount
    }
    func reset() {
        count = 0
    }
}

Counter 类定义了三个实例方法:

  • increment 让计数器按一递增;
  • increment(by: Int) 让计数器按一个指定的整数值递增;
  • reset 将计数器重置为0。

Counter 这个类还声明了一个可变属性 count,用它来保持对当前计数器值的追踪。

和调用属性一样,用点语法(dot syntax)调用实例方法

let counter = Counter()
// 初始计数值是0
counter.increment()
// 计数值现在是1
counter.increment(by: 5)
// 计数值现在是6
counter.reset()
// 计数值现在是0

函数参数可以同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用),详情参见 指定外部参数名。方法参数也一样,因为方法就是函数,只是这个函数与某个类型相关联了。

self 属性

类型的每一个实例都有一个隐含属性叫做 selfself 完全等同于该实例本身。你可以在一个实例的实例方法中使用这个隐含的 self 属性来引用当前实例。

上面例子中的 increment 方法还可以这样写:

func increment() {
    self.count += 1
}

实际上,你不必在你的代码里面经常写 self。不论何时,只要在一个方法中使用一个已知的属性或者方法名称,如果你没有明确地写 self,Swift 假定你是指当前实例的属性或者方法。这种假定在上面的 Counter 中已经示范了:Counter 中的三个实例方法中都使用的是 count(而不是 self.count)。

使用这条规则的主要场景是实例方法的某个参数名称与实例的某个属性名称相同的时候。在这种情况下,参数名称享有优先权,并且在引用属性时必须使用一种更严格的方式。这时你可以使用 self 属性来区分参数名称和属性名称。

下面的例子中,self 消除方法参数 x 和实例属性 x 之间的歧义:

struct Point {
    var x = 0.0, y = 0.0
    func isToTheRightOf(x: Double) -> Bool {
        return self.x > x
    }
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOf(x: 1.0) {
    print("This point is to the right of the line where x == 1.0")
}
// 打印“This point is to the right of the line where x == 1.0”

如果不使用 self 前缀,Swift会认为 x 的两个用法都引用了名为 x 的方法参数。

在实例方法中修改值类型

结构体和枚举是值类型。默认情况下,值类型的属性不能在它的实例方法中被修改。

但是,如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以为这个方法选择 可变(mutating)行为,然后就可以从其方法内部改变它的属性;并且这个方法做的任何改变都会在方法执行结束时写回到原始结构中。方法还可以给它隐含的 self 属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例。

要使用 可变方法,将关键字 mutating 放到方法的 func 关键字之前就可以了:

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// 打印“The point is now at (3.0, 4.0)”

上面的 Point 结构体定义了一个可变方法 moveBy(x:y :) 来移动 Point 实例到给定的位置。该方法被调用时修改了这个点,而不是返回一个新的点。方法定义时加上了 mutating 关键字,从而允许修改属性。

注意,不能在结构体类型的常量(a constant of structure type)上调用可变方法,因为其属性不能被改变,即使属性是变量属性,详情参见 常量结构体实例的存储

let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// 这里将会报告一个错误

在可变方法中给 self 赋值

可变方法能够赋给隐含属性 self 一个全新的实例。上面 Point 的例子可以用下面的方式改写:

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        self = Point(x: x + deltaX, y: y + deltaY)
    }
}

新版的可变方法 moveBy(x:y:) 创建了一个新的结构体实例,它的 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 现在等于 .high
ovenLight.next()
// ovenLight 现在等于 .off

上面的例子中定义了一个三态切换的枚举。每次调用 next() 方法时,开关在不同的电源状态(off, low, high)之间循环切换。