Swift编程语言学习12 ——实例方法(Instance Methods)和类型方法(Type Methods)

时间:2022-06-01 20:59:49

方法是与某些特定类型相关联的函数。类、结构体、枚举都能够定义实例方法;实例方法为给定类型的实例封装了详细的任务与功能。类、结构体、枚举也能够定义类型方法;类型方法与类型本身相关联。类型方法与 Objective-C 中的类方法(class methods)相似。

结构体和枚举可以定义方法是 Swift 与 C/Objective-C 的主要差别之中的一个。在 Objective-C 中,类是唯一能定义方法的类型。但在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活的在你创建的类型(类/结构体/枚举)上定义方法。

实例方法(Instance Methods)

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

实例方法要写在它所属的类型的前后大括号之间。实例方法可以隐式訪问它所属类型的全部的其它实例方法和属性。实例方法仅仅能被它所属的类的某个特定实例调用。实例方法不能脱离于现存的实例而被调用。

以下的样例,定义一个非常easy的类Counter,Counter能被用来对一个动作发生的次数进行计数:

class Counter {
varcount = 0
func increment() {
count++
}
func incrementBy(amount: Int) {
count += amount
}
func reset() {
count = 0
}
}

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

increment让计数器按一递增;

incrementBy(amount: Int)让计数器按一个指定的整数值递增;

reset将计数器重置为0。

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

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

 letcounter = Counter()
// 初始计数值是0
counter.increment()
// 计数值如今是1
counter.incrementBy(5)
// 计数值如今是6
counter.reset()
// 计数值如今是0

方法的局部參数名称和外部參数名称(Local and External Parameter Names for Methods)

函数參数能够同一时候有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用),详情參见函数的外部參数名。方法參数也一样(由于方法就是函数,仅仅是这个函数与某个类型相关联了)。可是,方法和函数的局部名称和外部名称的默认行为是不一样的。

Swift 中的方法和 Objective-C 中的方法极其相似。像在Objective-C 中一样,Swift 中方法的名称通经常使用一个介词指向方法的第一个參数,比方:with,for,by等等。前面的Counter类的样例中incrementBy方法就是这种。介词的使用让方法在被调用时能像一个句子一样被解读。和函数參数不同,对于方法的參数,Swift 使用不同的默认处理方式,这能够让方法命名规范更easy写。

详细来说,Swift 默认仅给方法的第一个參数名称一个局部參数名称;默认同一时候给第二个和兴许的參数名称局部參数名称和外部參数名称。这个约定与典型的命名和调用约定相适应,与你在写 Objective-C 的方法时非常相似。这个约定还让表达式方法在调用时不须要再限定參数名称。

看看以下这个Counter的还有一个版本号(它定义了一个更复杂的incrementBy方法):

class Counter {
varcount: Int = 0
func incrementBy(amount: Int, numberOfTimes: Int) {
count += amount * numberOfTimes
}
}

incrementBy方法有两个參数: amount和numberOfTimes。默认情况下,Swift 仅仅把amount当作一个局部名称,可是把numberOfTimes即看作局部名称又看作外部名称。以下调用这种方法:

let counter = Counter()
counter.incrementBy(5, numberOfTimes: 3)
// counter value is now 15

你不必为第一个參数值再定义一个外部变量名:由于从函数名incrementBy已经能非常清楚地看出它的作用。可是第二个參数,就要被一个外部參数名称所限定,以便在方法被调用时明白它的作用。

这样的默认的行为可以有效的处理方法(method),类似于在參数numberOfTimes前写一个井号(#):

func incrementBy(amount: Int,#numberOfTimes: Int) {
count += amount * numberOfTimes
}

这样的默认行为使上面代码意味着:在 Swift 中定义方法使用了与 Objective-C 相同的语法风格,而且方法将以自然表达式的方式被调用。

改动方法的外部參数名称(Modifying External Parameter Name Behavior for Methods)

有时为方法的第一个參数提供一个外部參数名称是很实用的,虽然这不是默认的行为。你能够自己加入一个显式的外部名称或者用一个井号(#)作为第一个參数的前缀来把这个局部名称当作外部名称使用。

相反,假设你不想为方法的第二个及兴许的參数提供一个外部名称,能够通过使用下划线(_)作为该參数的显式外部名称,这样做将覆盖默认行为。

self属性(The self Property)

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

上面样例中的increment方法还能够这样写:

func incrementBy(amount: Int,#numberOfTimes: Int) {
count += amount * numberOfTimes
}

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

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

以下的样例中,self消除方法參数x和实例属性x之间的歧义:

struct Point {
varx = 0.0, y = 0.0
func isToTheRightOfX(x: Double) -> Bool {
return self.x > x
}
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOfX(1.0) {
println("This point is to the right of the line where x ==1.0")
}
// 输出 "This pointis to the right of the line where x == 1.0"(这个点在x等于1.0这条线的右边)

假设不使用self前缀,Swift 就觉得两次使用的x都指的是名称为x的函数參数。

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

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

可是,假设你确实须要在某个详细的方法中改动结构体或者枚举的属性,你能够选择变异(mutating)这种方法,然后方法就能够从方法内部改变它的属性;而且它做的不论什么改变在方法结束时还会保留在原始结构中。方法还能够给它隐含的self属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例。

要使用变异方法,将keywordmutating 放到方法的funckeyword之前就能够了:

struct Point {
varx = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
x+= deltaX
y+= deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveByX(2.0, y: 3.0)
println("The point is now at(\(somePoint.x), \(somePoint.y))")
// 输出 "The point isnow at (3.0, 4.0)"

上面的Point结构体定义了一个变异方法(mutating method)moveByX,moveByX用来移动点。moveByX方法在被调用时改动了这个点,而不是返回一个新的点。方法定义时加上mutatingkeyword,这才让方法能够改动值类型的属性。

注意:不能在结构体类型常量上调用变异方法,由于常量的属性不能被改变,即使想改变的是常量的变量属性也不行,详情參见存储属性和实例变量

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

在变异方法中给self赋值(Assigning to self Within a Mutating Method)

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

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

新版的变异方法moveByX创建了一个新的结构(它的 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)之前循环切换。

类型方法(Type Methods)

实例方法是被类型的某个实例调用的方法。你也能够定义类型本身调用的方法,这样的方法就叫做类型方法。声明类的类型方法,在方法的funckeyword之前加上keywordclass;声明结构体和枚举的类型方法,在方法的funckeyword之前加上keywordstatic。

注意:

在 Objective-C 里面,你仅仅能为 Objective-C 的类定义类型方法(type-level methods)。在 Swift 中,你能够为全部的类、结构体和枚举定义类型方法:每个类型方法都被它所支持的类型显式包括。

类型方法和实例方法一样用点语法调用。可是,你是在类型层面上调用这种方法,而不是在实例层面上调用。以下是怎样在SomeClass类上调用类型方法的样例:

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

在类型方法的方法体(body)中,self指向这个类型本身,而不是类型的某个实例。对于结构体和枚举来说,这意味着你能够用self来消除静态属性和静态方法參数之间的歧义(类似于我们在前面处理实例属性和实例方法參数时做的那样)。

一般来说,不论什么未限定的方法和属性名称,将会来自于本类中另外的类型级别的方法和属性。一个类型方法可以调用本类中还有一个类型方法的名称,而无需在方法名称前面加上类型名称的前缀。相同,结构体和枚举的类型方法也可以直接通过静态属性的名称訪问静态属性,而不须要类型名称前缀。

以下的样例定义了一个名为LevelTracker结构体。它监測玩家的游戏发展情况(游戏的不同层次或阶段)。这是一个单人游戏,但也能够存储多个玩家在同一设备上的游戏信息。

游戏初始时,全部的游戏等级(除了等级 1)都被锁定。每次有玩家完毕一个等级,这个等级就对这个设备上的全部玩家解锁。LevelTracker结构体用静态属性和方法监測游戏的哪个等级已经被解锁。它还监測每一个玩家的当前等级。

struct LevelTracker {
static var highestUnlockedLevel = 1
static func unlockLevel(level: Int) {
if level > highestUnlockedLevel { highestUnlockedLevel = level }
}
static func levelIsUnlocked(level: Int) -> Bool {
return level <= highestUnlockedLevel
}
varcurrentLevel = 1
mutating func advanceToLevel(level: Int) -> Bool {
if LevelTracker.levelIsUnlocked(level) {
currentLevel = level
return true
}else {
return false
}
}
}

LevelTracker监測玩家的已解锁的最高等级。这个值被存储在静态属性highestUnlockedLevel中。

LevelTracker还定义了两个类型方法与highestUnlockedLevel配合工作。第一个类型方法是unlockLevel:一旦新等级被解锁,它会更新highestUnlockedLevel的值。第二个类型方法是levelIsUnlocked:假设某个给定的等级已经被解锁,它将返回true。(注意:虽然我们没有使用类似LevelTracker.highestUnlockedLevel的写法,这个类型方法还是可以訪问静态属性highestUnlockedLevel)

除了静态属性和类型方法,LevelTracker还监測每一个玩家的进度。它用实例属性currentLevel来监測玩家当前的等级。

为了便于管理currentLevel属性,LevelTracker定义了实例方法advanceToLevel。这种方法会在更新currentLevel之前检查所请求的新等级是否已经解锁。advanceToLevel方法返回布尔值以指示是否可以设置currentLevel。

以下,Player类使用LevelTracker来监測和更新每一个玩家的发展进度:

class Player {
vartracker = LevelTracker()
letplayerName: String
func completedLevel(level: Int) {
LevelTracker.unlockLevel(level + 1)
tracker.advanceToLevel(level + 1)
}
init(name: String) {
playerName = name
}
}

Player类创建一个新的LevelTracker实例来监測这个用户的发展进度。他提供了completedLevel方法:一旦玩家完毕某个指定等级就调用它。这种方法为全部玩家解锁下一等级,而且将当前玩家的进度更新为下一等级。(我们忽略了advanceToLevel返回的布尔值,由于之前调用LevelTracker.unlockLevel时就知道了这个等级已经被解锁了)。

你还能够为一个新的玩家创建一个Player的实例,然后看这个玩家完毕等级一时发生了什么:

var player = Player(name:"Argyrios")
player.completedLevel(1)
println("highest unlocked level is now\(LevelTracker.highestUnlockedLevel)")
// 输出 "highestunlocked level is now 2"(最高等级如今是2

假设你创建了第二个玩家,并尝试让他開始一个没有被不论什么玩家解锁的等级,那么这次设置玩家当前等级的尝试将会失败:

player = Player(name: "Beto")
if player.tracker.advanceToLevel(6) {
println("player is now on level6")
} else {
println("level 6 has not yet beenunlocked")
}
// 输出 "level 6 hasnot yet been unlocked"(等级6还没被解锁)