方法是與特定類型相關(guān)聯(lián)的函數(shù)。
類、結(jié)構(gòu)和枚舉都可以定義實(shí)例方法,這些方法封裝了處理給定類型實(shí)例的特定任務(wù)和功能。
類、結(jié)構(gòu)和枚舉還可以定義與類型本身關(guān)聯(lián)的類型方法。類型方法類似于Objective-C中的類方法。
結(jié)構(gòu)和枚舉可以在Swift中定義方法,這是與C和Objective-C的主要區(qū)別。在Objective-C中,類是唯一可以定義方法的類型。在Swift中,您可以選擇是定義類、結(jié)構(gòu)還是枚舉,并且仍然可以靈活地定義所創(chuàng)建類型的方法。
Instance Methods 實(shí)例方法
實(shí)例方法是屬于特定類、結(jié)構(gòu)或枚舉的實(shí)例的函數(shù)。它們支持這些實(shí)例的功能,或者提供訪問和修改實(shí)例屬性的方法,或者提供與實(shí)例用途相關(guān)的功能。實(shí)例方法與函數(shù)具有完全相同的語法,如函數(shù)中所述。
在其所屬類型的開括號(hào)和閉括號(hào)中編寫實(shí)例方法。實(shí)例方法可以隱式訪問該類型的所有其他實(shí)例方法和屬性。實(shí)例方法只能在其所屬類型的特定實(shí)例上調(diào)用。如果沒有現(xiàn)有實(shí)例,就不能單獨(dú)調(diào)用它。
下面的例子定義了一個(gè)簡單的Counter類,它可以用來計(jì)算一個(gè)動(dòng)作發(fā)生的次數(shù):
class Counter {
var count = 0
func increment() {
count += 1
}
func increment(by amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
Counter類定義了三個(gè)實(shí)例方法:
- increment() 將計(jì)數(shù)器增加1。
- increment(by: Int) 將計(jì)數(shù)器增量為指定的整數(shù)。
- reset() 將計(jì)數(shù)器重置為零。
Counter類還聲明了一個(gè)變量屬性count,用于跟蹤當(dāng)前計(jì)數(shù)器值。
調(diào)用實(shí)例方法時(shí)使用與屬性相同的點(diǎn)語法:
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
函數(shù)參數(shù)可以同時(shí)具有名稱(用于函數(shù)體中)和參數(shù)標(biāo)簽(用于調(diào)用函數(shù)時(shí))。因?yàn)榉椒ㄖ皇桥cType 相關(guān)聯(lián)的函數(shù)。
The self Property self屬性
類型的每個(gè)實(shí)例都有一個(gè)名為self的隱式屬性,它與實(shí)例本身完全等價(jià)。您可以使用self屬性在它自己的實(shí)例方法中引用當(dāng)前實(shí)例。
上面例子中的increment()方法可以這樣寫:
func increment() {
self.count += 1
}
實(shí)際上,您不需要經(jīng)常在代碼中編寫self。如果您沒有顯式地編寫self, Swift假設(shè)您在方法中使用已知的屬性或方法名時(shí)引用的是當(dāng)前實(shí)例的屬性或方法。這個(gè)假設(shè)通過在Counter的三個(gè)實(shí)例方法中使用count(而不是self.count)來演示。
當(dāng)實(shí)例方法的參數(shù)名稱與該實(shí)例的屬性具有相同的名稱時(shí),此規(guī)則的主要異常發(fā)生。在這種情況下,參數(shù)名稱優(yōu)先,必須以更限定的方式引用屬性。您可以使用self屬性來區(qū)分參數(shù)名稱和屬性名稱。
這里,self消除了方法參數(shù)x和實(shí)例屬性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")
}
如果沒有self前綴,Swift會(huì)假設(shè)x的兩種用法都引用了名為x的方法參數(shù)。編譯器將會(huì)報(bào)錯(cuò)。
Modifying Value Types from Within Instance Methods 從實(shí)例方法中修改值類型
結(jié)構(gòu)和枚舉是值類型。默認(rèn)情況下,值類型的屬性不能從其實(shí)例方法中修改。
但是,如果需要在特定方法中修改值類型屬性(結(jié)構(gòu)體和枚舉),則可以選擇修改該方法的行為。然后,方法可以從方法內(nèi)部修改(即更改)它的屬性,當(dāng)方法結(jié)束時(shí),它所做的任何更改都會(huì)被寫回原始結(jié)構(gòu)。該方法還可以為它的隱式self屬性分配一個(gè)全新的實(shí)例,這個(gè)新實(shí)例將在方法結(jié)束時(shí)替換現(xiàn)有的實(shí)例。
您可以通過在該方法的func關(guān)鍵字之前放置mutating關(guān)鍵字來選擇這種行為:
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結(jié)構(gòu)定義了一個(gè)可變的moveBy(x:y:)方法,它將一個(gè)Point實(shí)例移動(dòng)一定數(shù)量。這個(gè)方法實(shí)際上修改了調(diào)用它的點(diǎn),而不是返回一個(gè)新的點(diǎn)。mutating關(guān)鍵字被添加到其定義中,使其能夠修改屬性。
注意,您不能對(duì)結(jié)構(gòu)類型的常量調(diào)用mutating方法,因?yàn)樗膶傩圆荒芨?,即使它們是變量屬性,如常量結(jié)構(gòu)實(shí)例的存儲(chǔ)屬性中所述:
let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// this will report an error
Assigning to self Within a Mutating Method 在一個(gè)可變方法中給self賦值
修改方法可以為隱式self屬性分配一個(gè)全新的實(shí)例。上面的例子可以用下面的方式來寫:
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)
}
}
這個(gè)版本的moveBy(x:y:)方法創(chuàng)建了一個(gè)新結(jié)構(gòu),其x和y值被設(shè)置為目標(biāo)位置。調(diào)用此方法的替代版本的最終結(jié)果將與調(diào)用早期版本的結(jié)果完全相同。
枚舉的修改方法可以將隱式自參數(shù)設(shè)置為與相同枚舉不同的情況:
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
這個(gè)例子定義了一個(gè)三態(tài)開關(guān)的枚舉。每次調(diào)用next()方法時(shí),開關(guān)在三種不同的電源狀態(tài)(off、low和high)之間循環(huán)。
Type Methods
如上所述,實(shí)例方法是對(duì)特定類型的實(shí)例調(diào)用的方法。
您還可以定義在類型本身上調(diào)用的方法。這些類型的方法稱為類型方法。
通過在方法的func關(guān)鍵字之前編寫static關(guān)鍵字來指示類型方法。
類還可以使用class關(guān)鍵字允許子類覆蓋超類的方法實(shí)現(xiàn)。
在Objective-C中,你只能為Objective-C類定義類型級(jí)別的方法。在Swift中,您可以為所有類、結(jié)構(gòu)和枚舉定義類型級(jí)方法。每個(gè)類型方法都顯式地限定其支持的類型的作用域。
類型方法使用點(diǎn)語法調(diào)用,比如實(shí)例方法。但是,要對(duì)類型調(diào)用類型方法,而不是對(duì)該類型的實(shí)例調(diào)用類型方法。下面是如何在一個(gè)名為SomeClass的類上調(diào)用類型方法:
class SomeClass {
class func someTypeMethod() {
// type method implementation goes here
}
}
SomeClass.someTypeMethod()
在類型方法的主體中,隱式self屬性引用類型本身,而不是該類型的實(shí)例。這意味著您可以使用self來消除類型屬性和類型方法參數(shù)之間的歧義,就像您對(duì)實(shí)例屬性和實(shí)例方法參數(shù)所做的那樣。
更一般地說,在類型方法主體中使用的任何非限定方法和屬性名都將引用其他類型級(jí)別的方法和屬性。類型方法可以用另一個(gè)方法的名稱調(diào)用另一個(gè)類型方法,而不需要用類型名稱作為前綴。類似地,結(jié)構(gòu)和枚舉上的類型方法可以通過使用類型屬性的名稱而不使用類型名稱前綴來訪問類型屬性。
下面的示例定義了一個(gè)名為LevelTracker的結(jié)構(gòu),它可以跟蹤玩家在游戲的不同級(jí)別或階段的進(jìn)程。這是一款單人游戲,但可以在一個(gè)設(shè)備上為多個(gè)玩家存儲(chǔ)信息。
游戲的所有關(guān)卡(除了第一級(jí))在第一次游戲時(shí)都是鎖定的。每當(dāng)玩家完成一個(gè)關(guān)卡時(shí),設(shè)備上的所有玩家都可以解鎖該關(guān)卡。LevelTracker結(jié)構(gòu)使用類型屬性和方法來跟蹤哪些級(jí)別的游戲已經(jīng)解鎖。它還跟蹤單個(gè)玩家的當(dāng)前級(jí)別。
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
}
}
}
關(guān)卡跟蹤器結(jié)構(gòu)跟蹤任何玩家解鎖的最高關(guān)卡。該值存儲(chǔ)在名為highestUnlockedLevel的類型屬性中。
LevelTracker還定義了兩個(gè)類型函數(shù)來使用highestUnlockedLevel屬性。第一個(gè)類型函數(shù)名為unlock(:),每當(dāng)解鎖一個(gè)新級(jí)別時(shí),它都會(huì)更新highestUnlockedLevel的值。第二個(gè)是一個(gè)名為isunlock(:)的便利類型函數(shù),如果某個(gè)特定的級(jí)別號(hào)已經(jīng)被解鎖,那么它將返回true。(注意,這些類型方法可以訪問highestUnlockedLevel類型屬性,而不需要將其編寫為leveltrack .highestUnlockedLevel。)
除了類型屬性和類型方法外,LevelTracker還會(huì)跟蹤玩家在游戲中的進(jìn)程。它使用一個(gè)名為currentLevel的實(shí)例屬性來跟蹤玩家當(dāng)前正在玩的關(guān)卡。
為了幫助管理currentLevel屬性,LevelTracker定義了一個(gè)名為advance(To:)的實(shí)例方法。在更新currentLevel之前,此方法檢查請(qǐng)求的新級(jí)別是否已解鎖。advance(to:)方法返回一個(gè)布爾值,以指示是否能夠?qū)嶋H設(shè)置currentLevel。因?yàn)檎{(diào)用advance(to:)方法來忽略返回值的代碼不一定是錯(cuò)誤的,所以這個(gè)函數(shù)被標(biāo)記為@discardableResult屬性。有關(guān)此屬性的更多信息,請(qǐng)參見關(guān)鍵字屬性Attributes。
LevelTracker結(jié)構(gòu)用于Player類,如下圖所示,用于跟蹤和更新單個(gè)玩家的進(jìn)程:
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類創(chuàng)建一個(gè)新的LevelTracker實(shí)例來跟蹤該播放器的進(jìn)程。它還提供了一個(gè)名為complete(level:)的方法,該方法在玩家完成特定關(guān)卡時(shí)調(diào)用。該方法為所有玩家解鎖下一關(guān),并更新玩家的進(jìn)程以將他們移動(dòng)到下一關(guān)。(將忽略advance(to:)的布爾返回值,因?yàn)橐阎摷?jí)別已通過調(diào)用leveltrack .unlock(_:)在前一行解鎖。
你可以為一個(gè)新玩家創(chuàng)建一個(gè)Player類的實(shí)例,看看當(dāng)玩家完成第一級(jí)時(shí)會(huì)發(fā)生什么:
var player = Player(name: "Argyrios")
player.complete(level: 1)
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
// Prints "highest unlocked level is now 2"
如果你創(chuàng)建了第二個(gè)玩家,你試圖將他移動(dòng)到游戲中任何玩家都未解鎖的關(guān)卡,那么設(shè)置玩家當(dāng)前關(guān)卡的嘗試將失敗:
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"