方法(Methods)

603 阅读9分钟

原文

方法是和特定类型关联的函数。类,结构体和枚举都可以定义实例方法,为了使用用给定类型的实例封装特定的任务和功能。类,结构体,和枚举也可以定义类型方法,和他们类型自己关联在一起。类型方法和Objective-C中的类方法相似。

事实上结构体和枚举可以在swift中定义方法和C与Objective-C的区别很大。在Objective-C中,类是可以定义类型的唯一类型。在swift中,你可以选择定义类,结构体,后者枚举,仍然可以灵活的在你创建的类型中定义方法。

实例方法(Instance Methods)

实例方法是属于特定类,结构体,或者枚举的实例的函数。他们支持这些实例的功能,通过提供访问和修改实例属性的方式,或者通过与实例的用途相关的功能。实例方法有和函数一样的语法,像 Functions中描述的。

在它属于的类型的开和闭花括号中写实例的方法。一个实例方法有多这个类型的全部其他实例方法和属性的隐式访问。一个实例方法只能在它属于的类型的特定的实例上调用。不能没有存在的实例而单独调用。

这里是一个定义了简单Counter类的例子,可以用来记录一个动作发生的次数的数字:

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

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

  • increment()给计数器加1
  • increment(by:Int)给计数器增加指定的整型数目
  • reset()将计数器重置为0

Counter类也声明了一个可变的属性,count,来保持记录当前计数器的值。

用和属性一样的点语法来调用实例方法:

let counter = Counter()
// the initial counter value is 0
counter.increment()
// the counter's value is now 1
counter.increment(by: 5)
// the counter's value is now 6
counter.reset()
// the counter's value is now 0

函数参数可以有一个名称(在函数体中使用)和一个形参标签(当调用函数的时候使用),像在Function Argument Labels and Parameter Names中描述的。方法参数也一样,因为方法就是关联了一个类型的函数。

属性self(The self Property)

类型的每一个实例有一个名为self的属性,完全等于实例自己。在他自己的实例方法中使用self属性来引用当前的实例。

上面例子中的increment()方法可以写成这样:

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

练习中,不需要经常在你的代码中写self。如果你没有明确写self,无论何时在方法中使用一个已知的属性或者方法名字swift假设你引用的是一个属性或者方法。这个假设在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")
}
// Prints "This point is to the right of the line where x == 1.0"

没有前缀self,swift将会假设两个x都是指向方法参数x。

在实例方法中修改值类型(Modifying Value Types from Within Instance Method)

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

不过,如果你需要在特定的方法中修改你结构体或者枚举的属性,你可以给那个方法选择mutation性质。这个方法可以在方法中修改它的属性(就是更改),当方法结束的时候它造成的改变写会原来的结构体。方法也可以分配一个完全新的实例给他的self属性,当方法结束的时候这个新的实例将会取代已经存在的那个。

你可以给那个方法在func关键字前面放一个mutation关键字来选择这个性质:

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))")
// Prints "The point is now at (3.0, 4.0)"

上面Point结构体定义了一个mutating moveBy(x:y:)方法,通过一个确定的数量移动一个Point实例。替代了返回一个新的point,这个方法实际上修改了调用它的点。mutating关键字加到它的定义中使他可以修改他的属性。

注意不能在结构体类型的常量调用mutating方法,因为它的属性不能改变,即使他们是可变的属性,像在Stored Properties of Constant Structure Instances中描述的:

let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// this will report an error

在Mutating方法中分配self(Assigning to self Within a MUtating Method)

mutating方法可以分配一个完全的新实例给隐式的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)
    }
}

mutating moveBy(x:y:)方法的版本创建了一个新的x和y被设置成了目标地点的结构体。调用这个方法的这个版本的结果和之前版本的调用完全一样

枚举的mutating方法可以将隐式self参数设置为同一个枚举中的不同case:

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 is now equal to .high
ovenLight.next()
// ovenLight is now equal to .off

这个例子为一个三个状态切换的枚举。它的next()方法每次被调用switch在三个不同的power 状态之间循环(off,low和high)。

类型方法(Type Methods)

实例方法,像上面描述的,是你在特定类型的实例上调用的方法。你也可以定义在类型上调用的方法。这类方法称为类型方法。通过在方法的func关键字前面写static关键字来指定类型方法。类可以使用class关键字代替,来允许子类重写父类那个方法的实现。

在Objective-C中,你值可以在Objective-C类中定义类型层级的方法。在swift中,你可以为类,结构体,和枚举定义类型层级的方法。每个类型方法限制于他们支持的特定的类型。


类型方法可以用点语法调用,像实例方法一样。不过,在类型上调用类型方法,不是那个类型的实例上。这里是如歌在一个名为SomeClass的类上调用类型方法:

class SomeClass {
    class func someTypeMethod() {
        // type method implementation goes here
    }
}
SomeClass.someTypeMethod()

在类型方法的主体中,隐式self属性指向类型自己,而不是那个类的实例。这意味着你可以用self来消除类型属性和类型方法参数之间的歧义,跟你在实例属性和实例方法参数做的一样。

通常情况下,任何你在类型方法体中使用的未限制的方法和属性会指向其他类型层级的方法和属性。一个类型方法可以用其他方法名字调用其他类型方法,不需要在他前面写类型名称。相似的,结构体和枚举上的类型方法可以通过使用类型属性名称为不用类型前面写类型名称来访问类型属性。

下面的例子定义了一个名为LevelTracker的结构体,追踪一个玩家的通过游戏不同水平或者阶段的进度。它是单人游戏,但是可以存储在一个设备商多个玩家的信息。

全部的游戏等级在游戏第一次玩的时候是锁定的(apart from level one)。每次玩家完成一个等级,设备上全部玩家都解锁这一等级。LevelTracker结构体使用类型属性和方法来保持记录游戏的哪一等级还没解锁。他也记录单独玩家的当前等级。

struct LevelTracker {
    static var highestUnlockedLevel = 1
    var currentLevel = 1

    static func unlock(_ level: Int) {
        if level > highestUnlockedLevel { highestUnlockedLevel = level }
    }

    static func isUnlocked(_ level: Int) -> Bool {
        return level <= highestUnlockedLevel
    }

    @discardableResult
    mutating func advance(to level: Int) -> Bool {
        if LevelTracker.isUnlocked(level) {
            currentLevel = level
            return true
        } else {
            return false
        }
    }
}

结构体LevelTracker保持记录任何玩家解锁的最高等级。这个值存储在名为hightestUnlockedLevel类型属性中。

LevelTracker也定义了两个类型函数来操作highestUnlockedLevel属性。第一个是名为unlock(_:)的类型函数,当一个新等级解锁的时候更新highestUnlockedLevel的值。第二个是一个便利类型函数名为isUnlocked(_:),如果一个指定的等级数字解锁了就返回true。(注意着写类型方法不用写LevelTracker.highestUnlockedLevel就可以方位highestUnlockedLevel的类型属性)。

除了他的类型属性和类型方法,LevelTracker记录单独玩家通过游戏的进度,LevelTracker定义一个名为advane(to:)的实例方法。在更新currentLevel之前,这个方法检查请求的新等级是否已经解锁了。方法advance(to:)返回一个布尔值来指定它是否可以设置currentLevel。

为了帮助组织currentLevel属性,LevelTracker定义了一个名为advance(to:)的实例fangfa。更新currentLevel之前,这个方法检查请求的新登记是否已经解锁了。advance(to:)方法返回一个布尔值来指定他是否可以设置currentLevel。因为它对于调用advance(to:)方法忽略返回值的代码不是一种错误,这个函数标记了@discardableResult属性。更多关于attribute的信息,查看 Attributes

结构体LevelTracker使用Player类,下面展示的,来记录和更新单独玩家的进度:

class Player {
    var tracker = LevelTracker()
    let playerName: String
    func complete(level: Int) {
        LevelTracker.unlock(level + 1)
        tracker.advance(to: level + 1)
    }
    init(name: String) {
        playerName = name
    }
}

Player类创建了一个新的LEvelTracker实例来记录玩家的进度。也提供了名为complete(level:)方法,当玩家完成一个特殊的阶段是会调用。这个方法为全部玩家解锁了下一个阶段并且更新玩家的进度到下一阶段。(advance(to:)的布尔值返回类型被忽略了,因为阶段已经通过在上一行中调用LevelTracker.unlock(_:)知道已经解锁了)

你可一位一个新的玩家创建一个Player的实例,当完结完成在一个阶段的时候会发生什么:

var player = Player(name: "Argyrios")
player.complete(level: 1)
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
// Prints "highest unlocked level is now 2"

如果你创建了一个第二个我玩家,尝试移动到游戏中任何玩家都没有解锁的阶段,尝试设置玩家当前阶段失败:

player = Player(name: "Beto")
if player.tracker.advance(to: 6) {
    print("player is now on level 6")
} else {
    print("level 6 has not yet been unlocked")
}
// Prints "level 6 has not yet been unlocked"