Swift4.2語言規範(十三) 方法
方法是與特定類型相關聯的函數。類,結構和枚舉都可以定義實例方法,這些方法封裝了用於處理給定類型實例的特定任務和功能。類,結構和枚舉也可以定義類型方法,它們與類型本身相關聯。類型方法類似於Objective-C中的類方法。
結構和枚舉可以在Swift中定義方法的事實是與C和Objective-C的主要區別。在Objective-C中,類是唯一可以定義方法的類型。在Swift中,您可以選擇是定義類,結構還是枚舉,並且仍然可以靈活地在您創建的類型上定義方法。
實例方法
實例方法是屬於特定類,結構或枚舉的實例的函數。它們通過提供訪問和修改實例屬性的方法,或通過提供與實例目的相關的功能來支持這些實例的功能。
您在其所屬類型的開始和結束括號內編寫實例方法。實例方法可以隱式訪問該類型的所有其他實例方法和屬性。實例方法只能在它所屬類型的特定實例上調用。沒有現有實例,不能單獨調用它。
這是一個定義簡單Counter
類的示例,可用於計算操作發生的次數:
1 class Counter { 2 var count = 0 3 func increment() { 4 count += 1 5 } 6 func increment(by amount: Int) { 7 count += amount8 } 9 func reset() { 10 count = 0 11 } 12 }
在Counter
類定義了三個實例方法:
increment()
遞增計數器1
。increment(by: Int)
將計數器遞增指定的整數。reset()
將計數器重置為零。
本Counter
類還聲明一個變量屬性count
,以跟蹤當前計數器值。
使用與屬性相同的點語法調用實例方法:
1 let counter = Counter() 2 // the initial counter value is 0 3 counter.increment()4 // the counter‘s value is now 1 5 counter.increment(by: 5) 6 // the counter‘s value is now 6 7 counter.reset() 8 // the counter‘s value is now 0
函數參數既可以具有名稱(在函數體內使用),也可以具有參數標簽(在調用函數時使用),如函數參數標簽和參數名稱中所述。方法參數也是如此,因為方法只是與類型相關聯的函數。
Self屬性
類型的每個實例都有一個名為的隱式屬性self
,它與實例本身完全等效。您可以使用該self
屬性在其自己的實例方法中引用當前實例。
increment()
上面示例中的方法可能是這樣編寫的:
1 func increment() { 2 self.count += 1 3 }
實際上,您不需要self
經常編寫代碼。如果您沒有顯式寫入self
,則Swift會假定您在方法中使用已知屬性或方法名稱時引用當前實例的屬性或方法。這個假設通過在三個實例方法中使用count
(而不是self.count
)來證明Counter
。
當實例方法的參數名稱與該實例的屬性具有相同的名稱時,會發生此規則的主要例外。在這種情況下,參數名稱優先,並且有必要以更合格的方式引用屬性。您可以使用該self
屬性來區分參數名稱和屬性名稱。
這裏,self
消除了所調用的方法參數x
和也稱為的實例屬性之間的歧義x
:
1 struct Point { 2 var x = 0.0, y = 0.0 3 func isToTheRightOf(x: Double) -> Bool { 4 return self.x > x 5 } 6 } 7 let somePoint = Point(x: 4.0, y: 5.0) 8 if somePoint.isToTheRightOf(x: 1.0) { 9 print("This point is to the right of the line where x == 1.0") 10 } 11 // Prints "This point is to the right of the line where x == 1.0"
如果沒有self
前綴,Swift會假設兩次使用都x
引用了被調用的方法參數x
。
從實例方法中修改值類型
結構和枚舉是值類型。默認情況下,無法在其實例方法中修改值類型的屬性。
但是,如果需要在特定方法中修改結構或枚舉的屬性,則可以選擇改變該方法的行為。然後,該方法可以從方法中改變(即更改)其屬性,並且當方法結束時,它所做的任何更改都將寫回原始結構。該方法還可以為其隱式self
屬性分配一個全新的實例,並且該新實例將在方法結束時替換現有實例。
您可以通過將mutating
關鍵字放在該方法的關鍵字之前來選擇此行為func
:
1 struct Point { 2 var x = 0.0, y = 0.0 3 mutating func moveBy(x deltaX: Double, y deltaY: Double) { 4 x += deltaX 5 y += deltaY 6 } 7 } 8 var somePoint = Point(x: 1.0, y: 1.0) 9 somePoint.moveBy(x: 2.0, y: 3.0) 10 print("The point is now at (\(somePoint.x), \(somePoint.y))") 11 // Prints "The point is now at (3.0, 4.0)"
Point
上面的結構定義了一個變異moveBy(x:y:)
方法,它將Point
實例移動一定量。此方法實際上修改了調用它的點,而不是返回一個新點。該mutating
關鍵字被添加到它的定義,使之能夠修改其屬性。
請註意,您不能在結構類型的常量上調用變異方法,因為它的屬性不能更改,即使它們是變量屬性,如常量結構實例的存儲屬性中所述:
1 let fixedPoint = Point(x: 3.0, y: 3.0) 2 fixedPoint.moveBy(x: 2.0, y: 3.0) 3 // this will report an error
在變異方法中分配給Self
變異方法可以為隱式self
屬性分配一個全新的實例。Point
上面顯示的示例可能是以下列方式編寫的:
1 struct Point { 2 var x = 0.0, y = 0.0 3 mutating func moveBy(x deltaX: Double, y deltaY: Double) { 4 self = Point(x: x + deltaX, y: y + deltaY) 5 } 6 }
此版本的mutating moveBy(x:y:)
方法創建一個新結構,其值x
和y
值設置為目標位置。調用該方法的替代版本的最終結果與調用早期版本完全相同。
枚舉的變換方法可以將隱式self
參數設置為與同一枚舉不同的大小寫:
1 enum TriStateSwitch { 2 case off, low, high 3 mutating func next() { 4 switch self { 5 case .off: 6 self = .low 7 case .low: 8 self = .high 9 case .high: 10 self = .off 11 } 12 } 13 } 14 var ovenLight = TriStateSwitch.low 15 ovenLight.next() 16 // ovenLight is now equal to .high 17 ovenLight.next() 18 // ovenLight is now equal to .off
此示例定義三態切換的枚舉。每次調用其方法時off
,開關在三種不同的電源狀態(,low
和high
)之間循環next()
。
類型方法
如上所述,實例方法是在特定類型的實例上調用的方法。您還可以定義在類型本身上調用的方法。這些方法稱為類型方法。通過static
在方法的func
關鍵字之前寫入關鍵字來指示類型方法。類也可以使用class
關鍵字來允許子類覆蓋超類的該方法的實現。
註意
在Objective-C中,您只能為Objective-C類定義類型級方法。在Swift中,您可以為所有類,結構和枚舉定義類型級方法。每種類型方法都明確限定為它支持的類型。
使用點語法調用類型方法,例如實例方法。但是,您在類型上調用類型方法,而不是在該類型的實例上調用。以下是在類調用上調用類型方法的方法SomeClass
:
1 class SomeClass { 2 class func someTypeMethod() { 3 // type method implementation goes here 4 } 5 } 6 SomeClass.someTypeMethod()
在類型方法的主體內,隱式self
屬性引用類型本身,而不是該類型的實例。這意味著您可以使用self
消除類型屬性和類型方法參數之間的歧義,就像您對實例屬性和實例方法參數一樣。
更一般地,您在類型方法的主體中使用的任何非限定方法和屬性名稱將引用其他類型級別的方法和屬性。類型方法可以使用另一個方法的名稱調用另一個類型方法,而無需使用類型名稱作為前綴。類似地,結構和枚舉上的類型方法可以通過使用不帶類型名稱前綴的type屬性的名稱來訪問類型屬性。
下面的示例定義了一個名為的結構LevelTracker
,它跟蹤玩家在遊戲的不同級別或階段的進度。這是一款單人遊戲,但可以在一臺設備上存儲多個玩家的信息。
首次玩遊戲時,所有遊戲的等級(除了第一級)都被鎖定。每當玩家完成一個等級時,該等級就會被設備上的所有玩家解鎖。該LevelTracker
結構使用類型屬性和方法來跟蹤遊戲的哪些級別已解鎖。它還跟蹤單個玩家的當前級別。
1 struct LevelTracker { 2 static var highestUnlockedLevel = 1 3 var currentLevel = 1 4 5 static func unlock(_ level: Int) { 6 if level > highestUnlockedLevel { highestUnlockedLevel = level } 7 } 8 9 static func isUnlocked(_ level: Int) -> Bool { 10 return level <= highestUnlockedLevel 11 } 12 13 @discardableResult 14 mutating func advance(to level: Int) -> Bool { 15 if LevelTracker.isUnlocked(level) { 16 currentLevel = level 17 return true 18 } else { 19 return false 20 } 21 } 22 }
該LevelTracker
結構跟蹤任何玩家解鎖的最高級別。此值存儲在名為的類型屬性中highestUnlockedLevel
。
LevelTracker
還定義了兩個類型函數來處理highestUnlockedLevel
屬性。第一個是調用的類型函數unlock(_:)
,它會更新highestUnlockedLevel
解鎖新級別時的值。第二個是調用的便捷類型函數isUnlocked(_:)
,true
如果已經解鎖了特定的級別編號,則返回該函數。(請註意,這些類型方法可以訪問highestUnlockedLevel
type屬性而無需將其寫為LevelTracker.highestUnlockedLevel
。)
除了類型屬性和類型方法之外,還可以LevelTracker
跟蹤單個玩家在遊戲中的進度。它使用一個名為的實例屬性currentLevel
來跟蹤玩家當前正在玩的等級。
要幫助管理currentLevel
屬性,請LevelTracker
定義一個名為的實例方法advance(to:)
。在更新之前currentLevel
,此方法檢查所請求的新級別是否已解鎖。該advance(to:)
方法返回一個布爾值,以指示它是否實際上能夠設置currentLevel
。因為調用advance(to:)
方法忽略返回值的代碼不一定是錯誤,所以此函數用@discardableResult
屬性標記。有關此屬性的更多信息,請參閱屬性。
該LevelTracker
結構與Player
類(如下所示)一起使用,以跟蹤和更新單個玩家的進度:
1 class Player { 2 var tracker = LevelTracker() 3 let playerName: String 4 func complete(level: Int) { 5 LevelTracker.unlock(level + 1) 6 tracker.advance(to: level + 1) 7 } 8 init(name: String) { 9 playerName = name 10 } 11 }
在Player
類創建的新實例LevelTracker
來跟蹤玩家的進展。它還提供了一個名為的方法complete(level:)
,只要玩家完成特定級別就會調用該方法。此方法為所有玩家解鎖下一關,並更新玩家的進度以將其移至下一關。(advance(to:)
忽略布爾返回值,因為已知通過LevelTracker.unlock(_:)
上一行的調用解鎖了該級別。)
您可以Player
為新玩家創建該類的實例,並查看玩家完成第一級時會發生什麽:
1 var player = Player(name: "Argyrios") 2 player.complete(level: 1) 3 print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)") 4 // Prints "highest unlocked level is now 2"
如果你創建了第二個玩家,你嘗試將其移動到遊戲中任何玩家尚未解鎖的等級,那麽設置玩家當前等級的嘗試將失敗:
1 player = Player(name: "Beto") 2 if player.tracker.advance(to: 6) { 3 print("player is now on level 6") 4 } else { 5 print("level 6 has not yet been unlocked") 6 } 7 // Prints "level 6 has not yet been unlocked"
Swift4.2語言規範(十三) 方法