方法是與特定類型相關(guān)聯(lián)的函數(shù)。類、結(jié)構(gòu)體以及枚舉均可以定義實例方法,該方法為指定類型的實例封裝了特定的任務(wù)與功能。類、結(jié)構(gòu)體以及枚舉也能定義類型方法,該方法與類型自身相關(guān)聯(lián)。類型方法類似于在 Objective-C 中的類方法。
在 Swift 中,結(jié)構(gòu)體和枚舉能夠定義方法;事實上這是 Swift 與 C/Objective-C 的主要區(qū)別之一。在 Objective-C 中,類是唯一能定義方法的類型。在 Swift 中,你可以選擇是否定義一個類、結(jié)構(gòu)體或枚舉,且仍可以靈活地對你所創(chuàng)建的類型進(jìn)行方法的定義。
實例方法是某個特定類、結(jié)構(gòu)體或枚舉類型的實例的方法。他們通過提供訪問的方式和修改實例屬性,或提供與實例目的相關(guān)的功能性來支持這些實例的功能性。準(zhǔn)確的來講,實例方法的語法與函數(shù)完全一致,參考函數(shù)說明。
實例方法要寫在它所屬的類型的前后括號之間。實例方法能夠訪問他所屬類型的所有的其他實例方法和屬性。實例方法只能被它所屬的類的特定實例調(diào)用。實例方法不能被孤立于現(xiàn)存的實例而被調(diào)用。
下面定義一個簡單的類 Counter
的示例(Counter 可以用來對一個動作發(fā)生的次數(shù)進(jìn)行計數(shù)):
class Counter {
var count = 0
func increment() {
count++
}
func incrementBy(amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
類 Counter
可以定義三種實例方法:
increment
讓計數(shù)器按一遞增 incrementBy(amount: Int)
讓計數(shù)器按一個指定的整數(shù)值遞增 reset
將計數(shù)器重置為 0 Counter
這個類還聲明了一個可變屬性 count
,用它來保持對當(dāng)前計數(shù)器值的追蹤。
和調(diào)用屬性一樣,用點語法調(diào)用實例方法:
let counter = Counter()
// the initial counter value is 0
counter.increment()
// the counter's value is now 1
counter.incrementBy(5)
// the counter's value is now 6
counter.reset()
// the counter's value is now 0
函數(shù)參數(shù)有一個局部名稱(在函數(shù)體內(nèi)部使用)和一個外部名稱(在調(diào)用函數(shù)時使用),參考外部參數(shù)名稱。對方法參數(shù)也是一樣的,因為方法僅僅是與某一類型相關(guān)的函數(shù)。但是,局部名稱和外部名稱的默認(rèn)行為不同于函數(shù)和方法。
在 Swift 中的方法和在 Objective-C 中的方法極其相似,像在 Objective-C 一樣,在 Swift 中方法的名稱名稱通常用一個介詞指向方法的第一個參數(shù),比如:with
、for
以及 by
等等,前面的 Counter
類的例子中 incrementBy 方法就是這樣的。當(dāng)其被訪問時,介詞的使用使方法可被解讀為一個句子介詞的使用讓方法在被調(diào)用時能像一個句子一樣被解讀。Swift 這種方法命名約定很容易落實,因為它是用不同的默認(rèn)處理方法參數(shù)的方式,而不是用函數(shù)參數(shù)(來實現(xiàn)的)。
具體來說,Swift 默認(rèn)僅給方法的第一個參數(shù)名稱一個局部參數(shù)名稱;但是默認(rèn)同時給第二個和后續(xù)的參數(shù)名稱局部參數(shù)名稱和外部參數(shù)名稱。這個約定與典型的命名和調(diào)用約定相匹配,這與你在寫 Objective-C 的方法時很相似。這個約定還讓 expressive method 調(diào)用不需要再檢查/限定參數(shù)名。
看看下面這個 Counter
的替換版本(它定義了一個更復(fù)雜的 incrementBy
方法):
class Counter {
var count: Int = 0
func incrementBy(amount: Int, numberOfTimes: Int) {
count += amount * numberOfTimes
}
}
incrementBy
方法有兩個參數(shù):amount
和 numberOfTimes
。默認(rèn)地,Swift 僅把 amount
當(dāng)做一個局部名稱,但是把 numberOfTimes
既看作局部名稱又看做外部名稱。調(diào)用方法如下:
let counter = Counter()
counter.incrementBy(5, numberOfTimes: 3)
// counter value is now 15
你不必對第一個參數(shù)值進(jìn)行外部參數(shù)名稱的定義,因為從函數(shù)名 incrementBy
已經(jīng)能很清楚地看出它的目的/作用。但是,第二個參數(shù),就要被一個外部參數(shù)名稱所限定,以便在方法被調(diào)用時明確它的作用。
這種默認(rèn)的行為能夠有效的檢查方法,比如你在參數(shù) numberOfTimes
前寫了個井號(#
)時:
func incrementBy(amount: Int, #numberOfTimes: Int) {
count += amount * numberOfTimes
}
這種默認(rèn)行為使上面代碼意味著:在 Swift 中定義方法使用了與 Objective-C 同樣的語法風(fēng)格,并且方法將以自然表達(dá)式的方式被調(diào)用。
有時,對一個方法的第一個參數(shù)提供一個外部參數(shù)名是有用的,即使這不是默認(rèn)行為。你可以自己添加一個明確的外部名稱或你也可以用一個 hash 符號作為第一個參數(shù)的前綴,然后用這個局部名字作為外部名字。
相反,若你不想為方法的第二或后續(xù)參數(shù)提供一個外部名稱,你可以通過使用下劃線 (_) 作為該參數(shù)的顯式外部名稱來覆蓋默認(rèn)行為。
類型的每一實例都有一個被稱為 self
的隱含屬性,該屬性完全等同于該實例本身??梢栽谝粋€實例的實例方法中使用這個隱含的 self
屬性來引用當(dāng)前實例。
在上面的例子中,increment
方法也可以被寫成這樣:
func increment() {
self.count++
}
實際上,你不必在你的代碼里面經(jīng)常寫 self
。不論何時,在一個方法中使用一個已知的屬性或者方法名稱,如果你沒有明確的寫 self
,Swift 假定你是指當(dāng)前實例的屬性或者方法。這種假定在上面的 Counter
中已經(jīng)示范了:Counter
中的三個實例方法中都使用的是 count
(而不是 self.count
)。
這條規(guī)則的主要例外發(fā)生在當(dāng)實例方法的某個參數(shù)名稱與實例的某個屬性名稱相同時。在這種情況下,參數(shù)名稱享有優(yōu)先權(quán),并且在引用屬性時必須使用一種更恰當(dāng)(被限定更嚴(yán)格)的方式。你可以使用隱藏的 self
屬性來區(qū)分參數(shù)名稱和屬性名稱。
下面的例子演示 self
消除方法參數(shù)x和實例屬性 x
之間的歧義:
struct Point {
var x = 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")
prints "This point is to the right of the line where x == 1.0"
如果不使用 self
前綴,Swift
就認(rèn)為兩次使用的 x
都指的是名稱為 x
的函數(shù)參數(shù)。
結(jié)構(gòu)體和枚舉均屬于值類型一般情況下,值類型的屬性不能在其實例方法內(nèi)被修改。
但是,如果在某個具體方法中,你需要對結(jié)構(gòu)體或枚舉的屬性進(jìn)行修改,你可以選擇變異(mutating)這個方法。方法可以從內(nèi)部變異它的屬性;并且它做的任何改變在方法結(jié)束時都會回寫到原始結(jié)構(gòu)。方法會給它隱含的 self 屬性賦值一個全新的實例,這個新實例在方法結(jié)束后將替換原來的實例。
對于變異方法,將關(guān)鍵字 mutating
放到方法的 func
關(guān)鍵字之前就可以了:
struct Point {
var x = 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 is now at (3.0, 4.0)"
上文 Point
結(jié)構(gòu)體定義一個變異(mutating)方法 moveByx
,該方法按一定的數(shù)量移動 Point
結(jié)構(gòu)體。moveByX 方法在被調(diào)用時修改了這個 point,而不是返回一個新的 point。方法定義是加上 mutating
關(guān)鍵字,因此,方法可以修改值類型的屬性。
注意:不能在結(jié)構(gòu)體類型常量上調(diào)用變異方法,因為常量的屬性不能被改變,即使想改變的是常量的變量屬性也不行,詳情參見存儲屬性和實例變量:
let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveByX(2.0, y: 3.0)
// this will report an error
變異方法可以賦予隱含屬性 self
一個全新的實例。上述 Point
的示例也可以采用下面的方式來改寫:
struct Point {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}
新版的變異方法 moveByX
創(chuàng)建了一個新的分支結(jié)構(gòu)(他的 x 和 y 的值都被設(shè)定為目標(biāo)值了)。調(diào)用這個版本的方法和調(diào)用上個版本的最終結(jié)果是一樣的。
枚舉的變異方法可以讓 self
從相同的枚舉設(shè)置為不同的成員:
enum TriStateSwitch {
case Off, Low, High
mutating func next() {
switch self {
case Off:
self = Low
case Low:
self = High
case High:
self = Off
}
ovenLight = TriStateSwitch.Low
ovenLight.next()
// ovenLight is now equal to .High
ovenLight.next()
// ovenLight is now equal to .Off
上述示例中定義了一個三態(tài)開關(guān)的枚舉。每次調(diào)用 next
方法時,開關(guān)在不同的電源狀態(tài)(Off
, Low
, High
)之前循環(huán)切換。
如上所述,實例方法是被類型的某個實例調(diào)用的方法。你也可以定義調(diào)用類型本身的方法。這種方法就叫做類型方法。聲明類的類型方法,在方法的 func 關(guān)鍵字之前加上關(guān)鍵字 class
;聲明結(jié)構(gòu)體和枚舉的類型方法,在方法的 func
關(guān)鍵字之前加上關(guān)鍵字 static
。
注:在 Objective-C 中,你可以定義僅適用于 Objective-C 的類的 type-evel 方法。在 Swift,你可以對所有的類,結(jié)構(gòu)體和枚舉進(jìn)行 type-evel 方法定義。每種類型方法僅適用于其所支持的類型。
與實例方法相同,也可利用點語法調(diào)用類型方法。但是,你需要在本類型上調(diào)用類型方法,而不是其類型實例上。下面是如何在 SomeClass
類上調(diào)用類型方法的示例:
class SomeClass {
class func someTypeMethod() {
// type method implementation goes here
}
}
SomeClass.someTypeMethod()
在一個類型方法的方法體內(nèi),self
指向該類型本身,而不是類型的某個實例。對結(jié)構(gòu)體和枚舉來講,這意味著你可以用 self
來消除靜態(tài)屬性和靜態(tài)方法參數(shù)之間的二意性(類似于我們在前面處理實例屬性和實例方法參數(shù)時做的那樣)。
更廣泛地說,你在一個類型方法主體內(nèi)使用的任何未經(jīng)限定的方法和屬性名稱將指的是其他 type-level 方法和屬性。一種類型方法能用其他方法名稱調(diào)用另一種類型方法,而無需在方法名稱前面加上類型名稱的前綴。同樣,結(jié)構(gòu)體和枚舉的類型方法也能夠通過使用不帶有類型名稱前綴的靜態(tài)屬性名稱來訪問靜態(tài)屬性。
下述示例中定義名為 LevelTracker
的結(jié)構(gòu)體,該結(jié)構(gòu)體通過不同級別或階段的游戲?qū)ν婕业倪M(jìn)度進(jìn)行監(jiān)測。這是一個單人游戲,但也能在單個設(shè)備上儲存多個玩家的信息。
所有游戲級別(除了級別 1)在游戲初始時都被鎖定。每當(dāng)玩家完成一個級別,在該設(shè)備上,該級別對所有的玩家解鎖。LevelTracker
結(jié)構(gòu)體用靜態(tài)屬性和方法來監(jiān)測解鎖的游戲級別。也可監(jiān)測每個玩家的當(dāng)前等級。
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
}
var currentLevel = 1
mutating func advanceToLevel(level: Int) -> Bool {
if LevelTracker.levelIsUnlocked(level) {
currentLevel = level
return true
} else {
return false
}
}
LevelTracker
結(jié)構(gòu)體監(jiān)測任何玩家已解鎖的最高級別。該值被存儲在成為 highestUnlockedLevel
的靜態(tài)屬性中。
LeveTracker
還定義了兩個類型函數(shù)與 highestUnlockedLevel
配合工作。第一個為 unockLevel
類型函數(shù),一旦新的級別被解鎖,該函數(shù)會更新 highestUnlockedLevel
的值。第二個為 levelIsUnocked
便利型函數(shù),若某個給定級別數(shù)已經(jīng)被解鎖,該函數(shù)則返回 true
。(注:沒用使用 LevelTracker.highestUnlockedLevel
,這個類型方法還是能夠訪問靜態(tài)屬性 highestUnlockedLevel
。)
除了其靜態(tài)屬性和類型方法之外,LeveTracker
還監(jiān)測每個玩家的游戲進(jìn)程。它使用 currentLevel
實例屬性來監(jiān)測玩家當(dāng)前進(jìn)行的級別。
為便于管理 currentLevel
屬性,LeveTracker
定義了實例方法 advanceToLevel
。在更新 currentLevel
之前,該方法檢查所要求的新級別是否已經(jīng)解鎖。advanceToLevel
方法返回布爾值以指示是否確實能夠設(shè)置 currentLevel
。
下面,Player
類使用 LevelTracker
來監(jiān)測和更新每個玩家的發(fā)展進(jìn)度:
class Player {
var tracker = LevelTracker()
let playerName: String
func completedLevel(level: Int) {
LevelTracker.unlockLevel(level + 1)
tracker.advanceToLevel(level + 1)
}
init(name: String) {
playerName = name
Player
類使用 LevelTracker
來監(jiān)測該玩家的游戲進(jìn)程。它也提供 competedLevel
方法,一旦玩家完成某個指定等級,調(diào)用該方法。該方法為所有玩家解鎖下一個級別并將當(dāng)前玩家進(jìn)程更新為下一個級別。(忽略了 advanceToLevel
布爾返回值,因為之前調(diào)用上行時就知道了這個等級已經(jīng)被解鎖了。)
你可以為一個新玩家創(chuàng)建一個 Player
類的實例,然后看這個玩家完成等級一時發(fā)生了什么:
var player = Player(name: "Argyrios")
player.completedLevel(1)
println("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
// prints "highest unlocked level is now 2"
如果你創(chuàng)建了第二個玩家,并嘗試讓他開始一個沒有被任何玩家解鎖的等級,關(guān)于設(shè)置玩家當(dāng)前等級的嘗試會失?。?
player = Player(name: "Beto")
if player.tracker.advanceToLevel(6) {
println("player is now on level 6")
} else {
println("level 6 has not yet been unlocked")
}
// prints "level 6 has not yet been unlocked"
更多建議: