Swift 程式語言 — Methods

讓我們一起來研究 Swift 中有哪些不同的方法能使用。

Jeremy Xue
Jeremy Xue ‘s Blog
10 min readMay 21, 2019

--

Photo by John Schnobrich on Unsplash

# 前言

方法(methods)是與特定類型相關聯的函數。Classes、structures 以及 enumerations 可以定義實例方法,這些方法封裝了用於處理給定類型的實例的特定任務和功能。Classes、structures 和 enumerations 也可以定義類型方法,它們與類型本身相關聯,類型方法類似於 Objective-C 的 class methods。

事實上在 structures 和 enumerations 可以定義方法是與 C 和 Objective-C 的主要區別。在 Objective-C 中,classes 是唯一可以定義方法的類型。Swift 中你可以選擇定義 classes、structures 或是 enumerations,並且仍然可以靈活地在你創建的類型中定義方法。

# 實例方法

實例方法是屬於特定 classes、structures 和 enumerations 實例的函數。他們透過提供訪問和修改實例屬性的方法,或透過提供與實例目的相關的功能來支援這些實例的功能。

你在其所屬類型的開始和結束括弧內編寫實例方法,實例方法可以隱式訪問該類型的所有其他實例方法和屬性。實例方法只能在所屬類型的特定實例上調用,如果沒有現有實例,則無法單獨調用它。

這是一個定義一個簡單 Counter 的 class,用於計算操作發生的次數:

Counter 中定義了三個實例方法:

  • increment():將計數器增加 1。
  • increment(by:):將計數器增加指定的整數值。
  • reset():將計數器重置為零。

Counter 宣告了一個變量屬性 count,以追蹤目前計數器值。

使用與屬性相同的點語法調用實例方法:

函數參數既可以具有名稱(在函數體內使用),也可以具有參數標籤(調用函數時使用)。方法參數也是如此,因為方法只是與類型相關聯的函數。

# self 屬性

每一個類型的實例都有一個名為 self 的隱式屬性,他與實例本身完全相等。你可以使用 self 屬性在自己的實例方法中引用當前實例。

上面範例中的 increment() 也能夠這樣編寫:

實際上,你不需要時常在程式碼中編寫 self。如果你沒有顯式編寫 self,則只要在方法中使用已知屬性或方法名稱,Swift 就會假設你引用當前實例的屬性或方法。就像是我們在上面的 Counter 中使用 count 而不是 self.count

對於這個規則的一個重要例外是當實例方法的參數名稱與該實例的屬性具有相同名稱時,在這種情況下,參數名稱會優先,並且調用屬性需要有更合理的方式是必要的。你可以使用 self 屬性來區分參數名稱以及屬性名稱。

加上 self 之後,isToTheRightOf(x:) 這個函數中的 self.x 指的是 Point 本身的屬性 x ,而 x 指的是參數名稱。

# 實例方法修改 Value Type

Structures 和 enumerations 皆為 value types默認情況下,無法在其實例方法中修改 value type 的屬性

但是,如果你需要在特定方法中修改 structures 或 enumerations 的屬性,則可以選擇將這個方法變異(mutate),該方法則可以變異(即更改)其屬性,並且當方法結束時,它所做的任何更改都會寫回原始的 structure。方法同樣可以指定一個全新的實例給隱式的 self 屬性,並且該新實例將在方法結束時替換掉現有實例。

你可以在 func 之前加上一個 mutating 關鍵字來使用這個變異行為:

上面 Point 這個 struct 定義了一個 moveBy(x:y:) 的變異方法,它會將 Point 實例移動一定量。此方法實際上修改了調用它的點,而不是返回一個新的點。定義中添加的 mutating 關鍵字允許它修改自身的屬性。

注意,你無法在 structure 類型的常數上調用變異方法,因為其屬性無法更改,即使他們為變數屬性。

Cannot use mutating member on immutable value: ‘fixedPoint’ is a ‘let’ constant

# 在變異方法中指定 self

變異方法可以分配一個全新的實例給隱式的 self 屬性。上面的 Point 範例可以用下面方式編寫:

此版本的變異方法創建一個新的 structure,其 x、y 值設置為目標位置。調用的結果如同與前一個版本相同。

Enumerations 的變異方法可以將隱式的 self 參數設置為相同 enumeration 的不同 case

這個範例定義了三種型態的 enumeration。每次調用 next() 方法時,開關會在三種狀態間循環。

# 類型方法

如上所述,實例方法是在特定類型的實例上調用的方法,你還可以定義在類型本身上調用的方法,這些方法稱為類型方法。透過在 func 關鍵字前加上 static 關鍵字來表示為類型方法, Classes 也可以透過 class 關鍵字來允許 subClass override superClass 該方法的實現。

在 Objective-C 中,你只能為 Objective-C 的 classes 定義類型級方法。但是在 Swift 裡,你可以在所有的 classes 定義類型級方法,每種類型方法都明確限定他支持的類型。

使用點語法來調用類型方法,如同實例方法。但是你在類型上調用的類型方法,而不是在該類型的實例上調用

在類型方法的主體中,隱式的 self 屬性指的是類型本身,而不是該類型的實例。這意味著你可以使用 self 來消除類型屬性和類型方法之間的歧義,用法與實例屬性和實例方式完全相同。

一般來說,任何你在類型方法主體中的非限定方法和屬性名稱將引用其他類型級別的方法和屬性。類型方法可以使用另一個方法的名稱來調用另一個類型方法,而不需要使用類型名稱作為前綴。相同的,Structures 與 enumerations 上的類型方法透過使用不帶名稱的前綴的類型屬性的名稱來訪問其類型屬性。

下面的範例我們定義了一個 LevelTrackerstruct,它會追蹤玩家在遊戲中的不同級別或階段的進度。這是一款單人遊戲,但可以在一台設備上存儲多個玩家的資訊。

首次遊玩時,所有的遊戲等級(除了等級 1 )都會被鎖定。每當玩家完成一個等級時,該等級就會被裝置上的所有玩家解鎖。LevelTracker 使用類型屬性和方法來追蹤遊戲中的哪些等級以解鎖,還會追蹤單個玩家的當前等級。

LevelTracker 追蹤任何玩家解鎖的最高等級,這個值被存儲在名為 highestUnlockedLevel 的類型屬性中。

LevelTracker 還定義了兩個類型函數來使用 highestUnlockedLevel 屬性。第一個是名為 unlock(_:) 類型函數,只要解鎖一個新的等級,就會更新 highestUnlockedLevel 的值。第二個是名為 isUnlocked(_:) 的便捷類型函數,如果已經解鎖特定等級,則返回 true

請注意,這些類型方法可以直接訪問 highestUnlockedLevel 類型屬性,而不需要寫為 LevelTracker.highestUnlockedLevel

除了類型屬性和類型方法之外,LevelTracker 追蹤了單個玩家在遊戲中的進度。他使用名為 currentLevel 的實例屬性來追蹤玩家目前遊玩的等級。

為了幫助管理 currentLevel 屬性,LevelTracker 定義了名為 advance(to:) 的實例方法。在更新 currentLevel 之前,此方法會檢查所請求的新等級是否已經解鎖。advance(to:) 方法會返回一個 Bool 值,來表示它實際上是否能設置 currentLevel

因為調用 advance(to:) 方法忽略返回值不一定是錯誤,所以這個函數被標記為 @discardableResult 特性。

LevelTrackerPlayer 這個 class 一起使用,如下所示,用於追蹤和更新單個玩家的進度:

Player 創建 LevelTracker 的新實例來追蹤玩家的進度。他還提供了一個名為 complete(level:) 的方法,只要玩家完成特定等級就會調用該方法。此方法為所有玩家解鎖下一關,並更新玩家的進度到下一個等級。(忽略 advance(to:) 的返回值被忽略掉了,因為等級在先前調用 LevelTracker.unlock(_:) 時已經解鎖了。)

你可以創建 Player 實例,並查看玩家完成等級 1 時會發生什麼:

如果創建了第二個玩家,你試圖將其移動到遊戲中任何玩家尚未解鎖的等級時,那麼設置玩家的當前等級將會失敗:

--

--

Jeremy Xue
Jeremy Xue ‘s Blog

Hi, I’m Jeremy. [好想工作室 — iOS Developer]