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