Swift 程式語言 — Access Control (1)

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

Jeremy Xue
Jeremy Xue ‘s Blog
10 min readFeb 25, 2020

--

Photo by Markus Spiske on Unsplash

前言:

訪問控制限制從其他文件源文件(source files)和模塊中的程式碼的訪問程式碼部分。此功能使你可以隱藏程式碼的實現細節,並且指定可以訪問和使用該程式碼的首選接口。

你可以分配特定的訪問級別給各個類型(class, struct, enum),以及屬於這些類型的屬性、方法、初始化器和下標。協議可以限制在特定的上下文中,全局常數、變數和函數也可以。

除了提供各種級別的訪問控制之外,Swift 透過為典型場景提供默認訪問級別來減少指定顯示訪問控制級別的需要。確實,如果你正在編寫單個目標(single-target) app,則可能根本不需要指定顯式訪問控制級別。

簡潔起見,對於程式碼中可以設置訪問控制程式碼的各個方面(屬性,類型,函數等)在以下各節中稱為 “實體(entities)”。

|模塊和源文件

Swift 的訪問控制模型基於模塊(module)和源文件(source files)概念。

模塊是程式碼分發的單元、框架或應用。是作為單一個單元構建和發布的,並且可以由另一個模塊使用 Swift 的 import 關鍵字導入。

Xcode 中的每個建構目標(build target),例如 app bundle 或框架(framework) 在 Swift 中都被視為一個獨立的模塊。如果你將 app 的程式碼的各方面組合在一起作為一個獨立的框架(可能在多個應用程序之間封裝和覆用該程式碼),那麼在 app 中導入或使用框架時,或是在另一個框架中使用該框架時,你在該框架中定義的所有內容都將成為獨立模塊的一部分。

源文件(source file)是模塊內的單個 Swift 源程式碼文件(實際上,是應用程序或框架內的單個文件)。儘管通常在單獨的源文件中定義單個類型,但是單個源文件可以包含多種類型、函數等定義。

|訪問等級

Swift 為你程式碼中的實體提供了五種不同的訪問級別(access level)。這些訪問級別與定義實體的源文件有關,也與源文件所屬的模塊相關。

  • openpublic 訪問使實體被使用在任何來自其定義模塊的源文件中,也可以在 import 定義模塊的另一個模塊的源文件中使用。指定框架的公共接口時,通常使用 openpublic 訪問。
  • internal 訪問使實體被使用在任何來自其定義模塊的源文件中,但無法在該模塊以外的任何源文件中使用。在定義應用程序或框架的內部結構時,通常使用 internal 訪問。
  • file-private 訪問將實體的使用限制為自己定義的源文件。當在整個文件中使用特定的實現細節時,使用 file-private 訪問可以隱藏這些細節。
  • private 訪問將實體的使用限制為封閉的宣告以及同一文件中該宣告的擴展名。僅在單個宣告中使用特定細節的實現時,使用 private 訪問可以隱藏這些細節。

open 訪問是最高(最低限制)的訪問級別,而 private 是最低(最高限制)的訪問級別。

open 訪問僅適用於 class 和 class 成員,並且它與 public 訪問不同,它允許模塊外部的程式碼進行子類化(subclass)和覆寫(override),在後面的 Subclass 中討論。明確地標記一個 class 為 open 表示你已經考慮了來自其他模塊的程式碼使用該 class 作為 superclass 所帶來的影響,並且相應的設計了該 class 的程式碼。

|訪問級別的指導原則

Swift 中的訪問級別遵循一個總體指導原則:無法使用具有較低(更嚴格)的訪問級別的另一個實體來定義實體。例如:

  • 一個 public 的變數不會被定義為具有 internalfile-privateprivate 類型,因為在使用 public 的變數的地方可能無法使用該類型。
  • 函數不會比其參數類型和返回類型具有更高的訪問級別,該函數可以在周圍程式碼無法使用其組成類型的情況下使用。

這個指導準則的具體含義對於語言的不同片段在下文會詳細介紹。

|默認訪問級別

如果你沒有顯式的指定訪問級別,那麼所有在程式碼中的實體(有一些特定的例外,在稍後描述)都具有默認的 internal 訪問級別。因此,在多數的情況下,你不需要在程式碼中指定明確的訪問級別。

|單個目標 APP 的訪問級別

當你編寫一個簡單的單目標(single-target)的 app 時,app 中的程式碼通常是獨立包含在 app 中的,並且不需要再 app 模塊之外使用。而默認的訪問級別 internal 已經符合此要求。因此,你無須指定自定義的訪問級別。但是,你可能希望將程式碼的某些部分標記為 file-privateprivate 以便在 app 模塊中的其他代碼中隱藏其實現詳細訊息。

|框架的訪問級別

當開發框架時,請將該框架的 public-facing 接口標記為 openpublic,以便於其他模塊(例如導入該框架的 app)可以查看和詢問它。這個 public-facing 的接口就是框架的應用程序編程接口(或 API)。

框架中的任何內部實現細節仍然可以使用 internal 的默認訪問級別。或者,如果你想將他們從框架內部的程式碼的其他部分隱藏起來,則可以將其標記為 privatefile-private。只有在你想要實體成為框架 API 的一部分時,才需要將其標記為 openpublic

當你編寫具有單元測試的 app 時,需要使該 app 中的程式碼可用於該模塊才能被進行測試。默認情況下,其他模塊只能訪問標記為 openpublic 的實體。但是,如果你使用 @testable 屬性標記模塊的導入宣告並在啟用測試的方式下編譯模塊,則單元測試目標可以訪問任何 internal 實體。

|訪問控制語法

透過在實體宣告的開頭放置 openpublicinternalfile-privateprivate 修飾符之一來定義實體的訪問級別。

除非額外指定,否則默認的訪問級別是 internal,如同默認訪問級別時所述。這意味著 SomeInternlClasssomeInternalConstant 可以在沒有顯式訪問級別修飾符的情況下編寫,並且仍然具有 internal 訪問級別:

|自定義類型

如果要為自定義類型指定顯式訪問級別,請在定義類型時執行此操作。然後,只要訪問級別允許,就可以使用新類型。例如,如果定義了 file-private 的 class,則該 class 只能在定義了 file-private 的 class 的源文件中用為屬性的類型,或者用於函數參數或返回類型。

類型的訪問控制也會影響該類型的成員(屬性、方法、初始化器和下標)的默認訪問級別。如果定義了類型的訪問級別為 privatefile-private,則其成員的默認訪問級別也將為 privatefile-private。如果定義了類型的訪問級別為 internalpublic(或使用 internal 的默認訪問級別,而未明確指定訪問級別),則類型成員的默認訪問級別將是 internal

public 類型默認具有 internal 成員,而非 public 成員。如果要讓類型的成員為 public,則必須明確地將其標記為 public。此要求確保你可以選擇發布類型的 public-facing 的 API,並避免錯誤的將類型的內部工作方式表示為 public API。

|元組類型

元組類型的訪問級別是該元組中使用的所有類型中限制性最強的訪問級別。例如,如果由兩種不同的類型組成一個元組,一種具有 internal 訪問,另一種具有 private 訪問,則該複合元組類型的訪問級別將會是 private 的。

元組類型沒有 class、struct、enum 和 function 那樣的獨立定義。元組類型的訪問級別是根據構成該元組類型的類型自動決定的,因此無法明確指定。

|函數類型

函數類型的訪問級別被計算為函數的參數類型和返回類型中最嚴格的訪問級別。如果函數的計算訪問級別與上下文的默認值不匹配,則必須在函數中定義明確指定訪問級別。

下面的範例定義了名為 someFunctions() 的全局函數,而沒有為函數本身提供特定的訪問級別修飾符。你可能希望此功能具有默認訪問級別 internal,但實際上並非如此。實際上,someFunction() 不會按以下方式進行編譯:

函數的返回類型是一個元組類型,由上面在 “自定義類型” 中定義的兩個自定義 class 組成。這些 class 一個被定義為 internal,另一個被定義為 private。因此,複合元組的整體訪問級別為 private(元組組成類型的最低訪問級別)。

由於函數的返回類型是 private 的,因此必須使用 private 修飾符標記該函數的整體訪問級別,來確保該函數的宣告是有效的:

使用 publicinternal 修飾符標記 someFunction() 或使用 internal 的默認設置是無效的,因為該函數的 publicinternal 使用者可能無法適當的訪問該函數返回類型中使用的 private class。

|Enum 類型

枚舉中的個別情況會自動獲得與所數枚舉相同的訪問級別。你無法為各個枚舉案例指定其他的訪問級別。

下列的範例中,CompassPoint 枚舉具有顯式的 public 訪問級別。因此,枚舉案例 northsoutheastwest 也具有 public 的訪問級別:

|RawValue 與 Associated Value

枚舉定義中用於任何 rawValue 或 associated value 的類型的訪問級別必須至少與枚舉的訪問級別一樣高。例如,你不能將 private 類型作為具有 internal 訪問級別的枚舉原始值類型。

|嵌套類型

嵌套類型的訪問級別與其包含類型相同,除非包含類型是 public 的。在 public 類型內定義嵌套類型具有 internal 的自動訪問級別。如果想要 public 類型中的嵌套類型可公開使用,則必須將嵌套類型顯式宣告為 public

--

--

Jeremy Xue
Jeremy Xue ‘s Blog

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