Swift 程式語言 — Access Control (2)

讓我們在程式碼上加上訪問級別吧!

Jeremy Xue
Jeremy Xue ‘s Blog
11 min readMar 5, 2020

--

Photo by Markus Spiske on Unsplash

前言:

關於前一篇介紹訪問控制的文章在這,如果對於 Swift 中有哪些還不清楚的讀者可以先閱讀我前一篇文章:

|子類

您可以在當前可被訪問的上下文中訪問且與該子類在同一模塊中定義的任何類作為子類。你還可以將在不同模塊中定義的任何 open 類作為子類。子類不能具有比父類更高的訪問級別,例如,你不能編寫 internal 父類的 public 子類。

除此之外,對於定義在同個模塊中的類,你可以覆寫在特定訪問的上下文中可見的任何類的成員(方法、屬性、初始化器或下標)。對於在另一個模塊中定義的類,你可以覆寫任何 open 類的成員。

覆寫可以使繼承類的成員比其父類版本更易於訪問。在下面範例中,class A 是一個 public 類,帶有一個 fileprivatesomeMethod() 方法。Class BA 的子類,具有較低的 internal 的訪問級別。儘管如此,class B 提供了一個訪問級別為 internalsomeMethod() 的覆寫,該訪問級別高於 someMethods() 的原始實現:

子類成員調用比子類成員更低訪問級別的父類成員甚至是有效的,只要對父類成員的調用發生在允許的訪問級別的上下文中(對 fileprivate 的成員的調用要求父類在同一份源文件中,或 internal 成員的調用要求父類在同一個模塊中):

因為父類 A 和 子類 B 都被定義在同一份源文件中,因此 B 的 someMethods() 實現調用 super.someMethod() 有效。

|常數、變數、屬性及下標

常數、變數或屬性的無法比其類型更公開。例如,編寫帶有 private 類型的 public 屬性是無效的。同樣的,下標的索引類型或返回類型也無法更公開。

如果常數、變數、屬性或下標使用 private 類型,則常數、變數、屬性或下標也必須標記為 private

|Getter 與 Setter

常數、變數、屬性和下標的 gettersetter 自動接收與其常數、變數、屬性和下標相同的訪問級別。

你可以為 setter 提供比其相應的 getter 更低的訪問級別,來限制該變數,屬性或下標的讀寫範圍。你可以透過在 var 或下標之前編寫 fileprivate(set)private(set)internal(set) 來分配較低的訪問級別。

此規則適用於存儲屬性以及計算屬性。即使你沒有為存儲屬性編寫顯式的 getter 和 setter,Swift 仍會為你合成一個隱式的 getter 與 setter,以便你可以訪問存儲的屬性的備用儲存。使用 fileprivate(set)、private(set) 和 internal(set) 來改變此合成的 setter 的訪問級別,其方式與計算屬性中顯式的 setter 完全相同。

下面的範例定義了一個名為 TrackerStringstruct,該結構持續追蹤字串屬性被修改的次數:

TrackedString 定義了一個名為 value 的存儲字串屬性,具有一個 “”(空字串)的初始值。該結構還定義了一個稱為 numberOfEdits 的存儲整數屬性,該屬性用於追蹤 value 被修改的次數。此修改追蹤是透過 value 屬性上使用 didSet 屬性觀察器實現的,每次將 value 屬性設為新值時,該觀察器都會使 numberOfEdits 增加。

TrackedStringvalue 屬性沒有提供明確的訪問級別修飾符,因此他們都接受默認的 internal 訪問級別。然而,numberOfEdits 屬性的訪問級別已標記有 private(set) 修飾符,來表示該屬性的 getter 仍具有默認的 internal 訪問級別,但該屬性只能在 TrackedString 結構的一部分程式碼中進行設置。如此一來,TrackedString 可以在內部修改 numberOfEdits 屬性,但是在結構定義之外使用該屬性時,會將其表示為唯讀屬性。

如果你創建 TrackedString 實例並且多次修改其字串值,則可以看到 numberOfEdits 屬性更新到與修改次數相匹配的值:

儘管你可以從另一個源文件中查詢 numberOfEdits 屬性的當前值,但是你無法從另一個源文件中修改該屬性。此限制可以保護 TrackedString 編輯追蹤功能的實現細節,同時仍然可以方便的訪問該功能的某個方面。

請注意,如果需要可以為 gettersetter 分配顯式訪問級別。下面的範例展示了 TrackedString 結構的版本,其中使用顯式的 public 訪問級別定義該結構。因此結構的成員(包括 numberOfEdits 屬性)具有默認的訪問級別。你可以透過結合 publicprivate(set) 訪問級別修飾符,使結構 numberOfEdits 屬性 getterpublic ,並且使屬性的 setterprivate

|初始化器

自定義的初始化器可以被分配小於或等於其初始化器類型的訪問級別。唯一的例外是必須初始化器(required initializer)。必須初始化器必須具有與其所屬的類相同的訪問級別。

與函數和方法的參數一樣,初始化器的參數類型不能比初始化器自己的訪問級別更私有。

|默認初始化器

Swift 自動為任何結構或基類提供沒有任何參數的默認初始化器,來為該結構或基類的所有屬性提供默認值,並且不會提供給初始化器本身。

默認初始化器具有與其類型初始化器相同的訪問級別,除非該類型定義為 public。對於定義為 public 的類型,默認初始化器被視為 internal。如果你想要某個 public 類型可以在其他模塊使用無參數初始化,你必須自己明確地提供一個 public 的無參數初始化器,作為類型定義的一部份。

|結構的默認成員初始化器

如果任何結構的存儲屬性都為 private,則該結構的默認成員初始化器被視為 private。同樣的,如果結構的任何存儲屬性為 fileprivate,則該初始化器為 fileprivate。否則,初始化器的訪問其別為 internal

與上面的默認初始化器一樣,如果你想要 public 的結構類型在另一個模塊上使用時,可以使用成員初始化器進行初始化,則你必須自己提供一個 public 的成員初始化器作為類型定義的一部份。

|協議

如果要為協議類型分配顯式訪問級別,請在定義協議時表明。這使你創建協議只能在特定訪問上下文中被採用。

協議定義中的每個要求的訪問級別會自動設置為與協議相同的訪問級別。你不能將協議要求設置與其支持的協議不同的訪問級別。這確保所有協議要求在採用該協議的任何類型上皆可見。

如果定義了 public 協議,則協議的要求在實現時就要求這些要求具有 public 的訪問級別。此行為不同於其他類型,在其他類型中,public 類型定義表示該類型的成員為\ internal 的訪問級別。

|協議繼承

如果定義從現有協議繼承的新協議,則新協議最多可以具有與其繼承的協議相同的訪問級別。例如,你不能編寫從 internal 繼承的 public 協議。

|協議遵循

一種類型可以遵循一個具有比類型本身更低訪問級別的協議。例如,你可以定義可以在其他模塊中使用的 public 類型,但是與其 internal 協議的一致性只能在 internal 協議的定義模塊中使用。

類型遵循特定協議的上下文是該類型的訪問級別和協議的訪問級別中的最小值。例如,如果類型是 public,但其遵循的協議是內部的,則該類型對該協議的一致性也是 internal 的。

在編寫或擴展類型來遵循協議時,必須確保每個協議要求的類型實現至少具有與該協議所遵循的類型相同的訪問級別。例如,如果 public 類型遵循 internal 協議,則每種協議要求的類型實現必須至少為 internal

在 Swift 中,就像在 Objective-C 一樣,協議的一致性是全局的,類型不可能在同一個程序中以兩種不同的方式遵循協議。

|擴展

你可以在 class、struct 或 enum 可用的任何訪問的上下文中擴展 class、struct 或 enum。在擴展中添加的任何類型成員都具有與在要擴展的原始類型中宣告的類型成員相同的默認級別。如果擴展 publicinternal 類型,則添加的任何新類型成員都有 internal 默認訪問級別。如果擴展 fileprivate 類型,則添加的任何新類型都具有 fileprivate 的默認訪問級別。如果擴展 private 類型,則添加的任何新類型成員都具有 private 的默認訪問級別。

或者,你可以使用顯式訪問級別修飾符標記擴展,已為擴展中定義的所有成員設置新的默認訪問級別。對於單個類型成員,仍然可以在擴展中覆蓋此新的默認值。

如果你使用擴展添加協議一致性,則無法為其提供明確的訪問級別修飾符號。而是使用協議自己的訪問級別為擴展中的每個要求實現提供默認的訪問級別。

|擴展中的 private 成員

與擴展所使用的 class、struct 或 enum 位於同一文件中的擴展行為就好像擴展中的程式碼是作為原始類型宣告的一部分編寫的。因此,你可以:

  • 在原始宣告中宣告一個 private 成員,然後從同一文件的擴展訪問該成員。
  • 在擴展中宣告一個 private 成員,然後從同一文件的另一個擴展訪問該成員。
  • 在擴展中宣告一個 private 成員,然後從同一文件中的原始宣告訪問該成員。

此行為意味著你可以使用相同的方式使用擴展來組織程式碼,無論你的類型是否具有 private 實體。例如,以下簡單的協議:

你可以使用擴展來添加協議一致性,如下所示:

|泛型

泛型類型或泛型函數的訪問級別是泛型類型或函數本身的訪問級別以及對其類型參數的任何類型約束的訪問級別的最小值。

|類型別名

出於訪問控制的目的,你定義的任何類型別名都被視為不同的類型。類型別名的訪問級別可以小於或等於其類型別名的訪問級別。例如,private 類型別名可以為 privatefileprivateinternalpublicopen 類型別名,但是 public 類型別名不能為 internalfileprivateprivate 類型別名。

此規則也適用於滿足協議一致性的關聯類型的類型別名。

--

--

Jeremy Xue
Jeremy Xue ‘s Blog

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