Swift 程式語言 — Protocols (1)

讓我們深入研究 Swift 中的協議是什麼吧!

Jeremy Xue
Nov 2 · 9 min read
Photo by Scott Webb on Unsplash

前言:

協議(Protocol)為方法、屬性和其他特定任務需求或功能定義藍圖。然後協議可以被特定 class、structure 或 enum 採用,以提供這些要求的實際的實現。滿足協議要求的任何類型都被稱為遵循該協議。

除了指定必須遵循標準的類型的要求之外,你還可以擴展協議來實現這些要求中的某些要求,或者實現遵循標準的類型才可以採用的附加功能。

|協議語法

用與 class、struct 和 enum 非常相似的方式來定義協議:

自定義類型透過在將協議名稱放在類型名稱後方,並用:分隔來表示他們採用特定的協議,作為其定義的一部分。多個協議可以使用逗號區隔分別列出:

如果 class 具有 superclass,請在採用任何協議之前列出其 superclass 名稱,並用逗號區隔:

|屬性要求

協議可以要求任何遵循的類型提供具有特定名稱和類型的實例屬性或是類型屬性。協議沒有指定屬性應該是存儲屬性或是計算屬性,它只有指定需要屬性名稱和類型。該協議還指定了每個屬性必須為可讀(gettable)的或是可讀和可讀寫(gettable & settable)的。

如果協議要求的某個屬性必須是可讀和可讀寫的,則該屬性的要求不能由常數的儲存屬性或唯讀的計算屬性來滿足。如果協議只要求可讀屬性,則可以透過任何類型的屬性來滿足該要求,並且如果你的程式碼需要的話,該屬性也是可寫的。

屬性要求總是宣告為變數屬性,並且以 var 關鍵字為前綴。透過在類型宣告後寫 { get set } 來表示為可讀寫的屬性,透過寫 { get } 來表示可讀的屬性:

在協議中定義類型類型屬性要求時,請總是在其前面加上 static 關鍵字。即使在透過類實現類型屬性要求時可以使用 classstatic 關鍵字做為前綴,該規則也適用:

這是一個具有單個實例屬性要求的協議範例:

FullyNamed 協議要求一個遵循的類型來提供完全限定的名稱。該協議沒有指定遵循類型的其他任何性質,只有指定類型必須能提供為其提供全名。該協議表示任何 FullyNamed 類型必須有一個稱為 fullName 的可獲取的實例屬性,其類型為 String

這是採用並遵循 FullyNamed 協議的簡單的範例:

這個範例定義了一個 Personstruct,該結構表示一個特定人物的名稱。它表示它採用 FullyNamed 協議作為其定義第一行的一部分。

每個 Person 實例都有一個名為 fullName 的存儲屬性,類型為 String。這符合 FullyNamed 協議的單一要求,並且意味著 Person 以遵循該協議。(如果沒有滿足協議要求,Swift 將在編譯時報錯。)

這邊是一個更複雜的 class,它也採用並遵循 FullyNamed 協議:

這個 class 實現 fullName 屬性要求為星船計算唯讀屬性。每個 StarShipclass 實例都存儲一個強制性的 name 和一個 optional 的 prefixfullName 屬性使用 prefix(如果存在)將其添加到 name 的開頭來為星船創建全名。

|方法要求

協議可以要求符合類型來實現特定的實例方法和類型方法。這些方法用與普通實例和類型方法完全相同的方式寫為協議定義的一部分,但是沒有花括弧或方法主體。可變參數是允許的,但要遵循與常規方法相同的規則。但是無法在協議的定義中為方法參數指定默認值。

與類型屬性要求一樣,在協議中定義類型方法時,請總是在其前面加上 static 關鍵字。即使該類型方法要求由 class 實現時以 classstatic 關鍵字也是如此:

下面的範例定義了具有實例方法要求的協議:

RandomNumberGenerator 要求任何遵循的類型具有一個稱為 random 的實例方法,當被調用時,它會回傳一個 Double 值。雖然他沒有沒有指定作為協議的一部分,它假設這個值將是從 0.0 到 1.0 之間的數值(但不包括 0.0) 。

RandomNumberGenerator 協議對如何生成每個隨機數沒有任何假設,它只是要求生成器提供生成新隨機數的標準方法。

這是採用並符合 RandomNumberGenerator 協議的 class 的實現。這個 class 實現線性同餘方法的偽隨機數生成器算法:

|變異方法要求

有時候,一個法需要修改(或變異)它所屬的實例。對於基於值類型的實例方法,請將 mutating 關鍵字放在方法的 func 關鍵字之前,來表示允許該方法修改其所屬實例以及該實例的任何屬性。在 Modifying Value Types from Within Instance Methods 介紹了這個過程。

如果你定義了一個協議實例方法要求,來對採用協議的任何實例進行變異,請為該方法標記為 mutating 關鍵字作為定義的一部分。這使 struct 和 enum 可以採用協議並滿足該方法要求。

如果你將協議方法要求標記為 mutating,則在位 class 編寫該方法實現時無須編寫 mutating 關鍵字。mutating 關鍵字只有在 struct 和 enum 中使用。

下面的範例定義了名為 Togglable 的協議,其定義了一個稱為 toggle 的實例方法需求。顧名思義,toggle 方法用於切換或是反轉任何遵循類型的狀態,通常是透過修改該類型的屬性來實現的。

toggle 方法在 Togglable 定義中有標記 mutating 關鍵字,來表示該方法在被調用時會改變符合類型的實例狀態:

如果你為某個 struct 或 enum 實現 Togglable 協議,則該 struct 可以通過使用 mutating 標記這個 toggle 方法的實現來符合協議。

下面的範例定義了一個名為 OnOffSwitchenum。這個 enum 在兩個狀態之間切換,透過 enum 中的 onoff 的情況表示。該 enumtoggle 實現標記為 mutating 來符合 Togglable 協議的要求:

|初始化器要求

協議可以要求特定的初始化器由遵循的類型實現。你可以使用與普通初始化器完全相同的方式,將這些初始化器寫為協議定義的一部分,但不需要花括號或初始化器主體:

|協議初始化器要求的 class 實現

你可以在遵循的 class 上實現初始化器要求,可以是指定初始化器或便捷初始化器。在這兩種情況下,都必須使用 required 修飾符標記初始化器實現:

使用 required 修飾符可確保你在遵循 class 的所有 subclass 提供初始化器要求顯式的繼承或實現,使他們也遵循協議:

有關更多的必要初始化器資訊,可以查看 Required Initializers

你不需要在有標記 final 修飾符的 class 上使用 required 修飾符號來標記協議初始化器的實現,因為 final class 不能被 subclassed。有更多 final 修飾符的訊息,請查看 Preventing Overrides

如果 subclass 覆寫 superclass 中的指定初始化器,並且通過協議實現了符合的初始化器要求,請同時使用 requiredoverride 修飾符標記初始化器實現:

|可失敗初始化器要求

協議可以為遵循類型定義可失敗初始化器要求,如 Failable Initializers 中所定義。

可失敗初始化器要求可以透過遵循類型中的可失敗初始化器和不可失敗初始化去來滿足。不可失敗初始化器的要求可以透過不可失敗初始化器或隱式展開的可失敗初始化器來滿足。

Jeremy Xue ‘s Blog

大部分文章是在分享在 iOS 上開發的文章 ( Swift ),有時候也會分享一些生活中的事物。

Jeremy Xue

Written by

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

Jeremy Xue ‘s Blog

大部分文章是在分享在 iOS 上開發的文章 ( Swift ),有時候也會分享一些生活中的事物。

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade