OneDayOneSwift[24] - Access Control

訪問控制可以限定其他源文件或模塊中的代碼對你的代碼的訪問級別。這個特性可以讓我們隱藏代碼的一些實現細節,并且可以為其他人可以訪問和使用的代碼提供接口。

你可以明確地給單個類型(類、結構體、枚舉)設置訪問級別,也可以給這些類型的屬性、方法、構造器、下標等設置訪問級別。協議也可以被限定在一定的范圍內使用,包括協議里的全局常量、變量和函數。

Swift 不僅提供了多種不同的訪問級別,還為某些典型場景提供了默認的訪問級別,這樣就不需要我們在每段代碼中都申明顯式訪問級別。其實,如果只是開發一個單一 target 的應用程序,我們完全可以不用顯式申明代碼的訪問級別。

訪問級別

Swift 為代碼中的實體提供了三種不同的訪問級別。這些訪問級別不僅與源文件中定義的實體相關,同時也與源文件所屬的模塊相關。

  • public:可以訪問同一模塊源文件中的任何實體,在模塊外也可以通過導入該模塊來訪問源文件里的所有實體。通常情況下,框架中的某個接口可以被任何人使用時,你可以將其設置為 public 級別。
  • internal:可以訪問同一模塊源文件中的任何實體,但是不能從模塊外訪問該模塊源文件中的實體。通常情況下,某個接口只在應用程序或框架內部使用時,你可以將其設置為 internal 級別。
  • private:限制實體只能在所在的源文件內部使用。使用 private 級別可以隱藏某些功能的實現細節。

public 為最高(限制最少)訪問級別,private 為最低(限制最多)訪問級別。

ps: Swift 中的 private 訪問級別不同于其他語言,它的范圍限于源文件,而不是聲明范圍內。這就意味著,一個類型可以訪問其所在源文件中的所有 private 實體,但是如果它的擴展定義在其他源文件中,那么它的擴展就不能訪問它在這個源文件中定義的 private 實體。

訪問級別基本原則

Swift 中的訪問級別遵循一個基本原則:不可以在某個實體中定義訪問級別更高的實體。
例如:

  • 一個 public 訪問級別的變量,其類型的訪問級別不能是 internalprivate。因為無法保證變量的類型在使用變量的地方也具有訪問權限。
  • 函數的訪問級別不能高于它的參數類型和返回類型的訪問級別。因為如果函數定義為 public 而參數類型或者返回類型定義為 internalprivate,就會出現函數可以在任何地方被訪問,但是它的參數類型和返回類型卻不可以。

默認訪問級別

如果你不為代碼中的實體顯式指定訪問級別,那么它們默認為 internal 級別(有一些例外情況,稍后會進行說明)。因此,在大多數情況下,我們不需要顯式指定實體的訪問級別。

框架的訪問級別

當你開發框架時,就需要把一些對外的接口定義為 public 級別,以便使用者導入該框架后可以正常使用其功能。這些被你定義為 public 的接口,就是這個框架的 API。

ps: 框架依然會使用默認的 internal 級別,也可以指定為 private 級別。當你想把某個實體作為框架的 API 的時候,需顯式為其指定 public 級別。

單元測試 target 的訪問級別

當你的應用程序包含單元測試 target 時,為了測試,測試模塊需要訪問應用程序模塊中的代碼。默認情況下只有 public 級別的實體才可以被其他模塊訪問。然而,如果在導入應用程序模塊的語句前使用 @testable 特性,然后在允許測試的編譯設置(Build Options -> Enable Testability)下編譯這個應用程序模塊,單元測試 target 就可以訪問應用程序模塊中所有 internal 級別的實體。

語法

public class SomePublicClass {}
internal class SomeInternalClass {}
private class SomePrivateClass {}

public var somePublicVariable = 0
internal let someInternalConstant = 0
private func somePrivateFunction() {}

自定義類型

一個類型的訪問級別也會影響到類型成員(屬性、方法、構造器、下標)的默認訪問級別。如果你將類型指定為 private 級別,那么該類型的所有成員的默認訪問級別也會變成 private。如果你將類型指定為 public 或者 internal 級別(或者不明確指定訪問級別,而使用默認的 internal 訪問級別),那么該類型的所有成員的默認訪問級別將是 internal

ps: 上面提到,一個 public 類型的所有成員的訪問級別默認為 internal 級別,而不是 public 級別。如果你想將某個成員指定為 public 級別,那么你必須顯式指定。這樣做的好處是,在你定義公共接口的時候,可以明確地選擇哪些接口是需要公開的,哪些是內部使用的,避免不小心將內部使用的接口公開。

public class SomePublicClass {          // 顯式的 public 類
    public var somePublicProperty = 0   // 顯式的 public 類成員
    var someInternalProperty = 0        // 隱式的 internal 類成員
    private func somePrivateMethod() {} // 顯式的 private 類成員
}

class SomeInternalClass {               // 隱式的 internal 類
    var someInternalProperty = 0        // 隱式的 internal 類成員
    private func somePrivateMethod() {} // 顯式的 private 類成員
}

private class SomePrivateClass {        // 顯式的 private 類
    var somePrivateProperty = 0         // 隱式的 private 類成員
    func somePrivateMethod() {}         // 隱式的 private 類成員
}

元組類型

元組的訪問級別將由元組中訪問級別最嚴格的類型來決定。例如,如果你構建了一個包含兩種不同類型的元組,其中一個類型為 internal 級別,另一個類型為 private 級別,那么這個元組的訪問級別為 private

ps: 元組不同于類、結構體、枚舉、函數那樣有單獨的定義。元組的訪問級別是在它被使用時自動推斷出的,而無法明確指定。

函數類型

函數的訪問級別根據訪問級別最嚴格的參數類型或返回類型的訪問級別來決定。但是,如果這種訪問級別不符合函數定義所在環境的默認訪問級別,那么就需要明確地指定該函數的訪問級別。

private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // 此處是函數實現部分
}

將該函數指定為 publicinternal,或者使用默認的訪問級別 internal 都是錯誤的,因為如果把該函數當做 publicinternal 級別來使用的話,可能會無法訪問 private 級別的返回值。

枚舉類型

枚舉成員的訪問級別和該枚舉類型相同,你不能為枚舉成員單獨指定不同的訪問級別。

枚舉定義中的任何原始值或關聯值的類型的訪問級別至少不能低于枚舉類型的訪問級別。例如,你不能在一個 internal 訪問級別的枚舉中定義 private 級別的原始值類型。

嵌套類型

如果在 private 級別的類型中定義嵌套類型,那么該嵌套類型就自動擁有 private 訪問級別。如果在 public 或者 internal 級別的類型中定義嵌套類型,那么該嵌套類型自動擁有 internal 訪問級別。如果想讓嵌套類型擁有 public 訪問級別,那么需要明確指定該嵌套類型的訪問級別。

子類

子類的訪問級別不得高于父類的訪問級別。例如,父類的訪問級別是 internal,子類的訪問級別就不能是 public
可以通過重寫為繼承來的類成員提供更高的訪問級別。

public class A {
    private func someMethod() {}
}

internal class B: A {
    override internal func someMethod() {
        super.someMethod()
    }
}

常量、變量、屬性、下標

常量、變量、屬性不能擁有比它們的類型更高的訪問級別。例如,你不能定義一個 public 級別的屬性,但是它的類型卻是 private 級別的。同樣,下標也不能擁有比索引類型或返回類型更高的訪問級別。

Getter 和 Setter

常量、變量、屬性、下標的 GettersSetters 的訪問級別和它們所屬類型的訪問級別相同。

Setter 的訪問級別可以低于對應的 Getter 的訪問級別,這樣就可以控制變量、屬性或下標的讀寫權限。在 varsubscript 關鍵字之前,你可以通過 private(set)internal(set) 為它們的寫入權限指定更低的訪問級別。

ps: 這個規則同時適用于存儲型屬性和計算型屬性。即使你不明確指定存儲型屬性的 GetterSetter,Swift 也會隱式地為其創建 GetterSetter,用于訪問該屬性的后備存儲。使用 private(set)internal(set) 可以改變 Setter 的訪問級別,這對計算型屬性也同樣適用。

struct TrackedString {
    private(set) var numberOfEdits = 0
    var value: String = "" {
        didSet {
            numberOfEdits++
        }
    }
}

構造器

自定義構造器的訪問級別可以低于或等于其所屬類型的訪問級別。唯一的例外是必要構造器,它的訪問級別必須和所屬類型的訪問級別相同。

如同函數或方法的參數,構造器參數的訪問級別也不能低于構造器本身的訪問級別。

默認構造器

默認構造器的訪問級別與所屬類型的訪問級別相同,除非類型的訪問級別是 public。如果一個類型被指定為 public 級別,那么默認構造器的訪問級別將為 internal。如果你希望一個 public 級別的類型也能在其他模塊中使用這種無參數的默認構造器,你只能自己提供一個 public 訪問級別的無參數構造器。

結構體默認的成員逐一構造器

如果結構體中任意存儲型屬性的訪問級別為 private,那么該結構體默認的成員逐一構造器的訪問級別就是 private。否則,這種構造器的訪問級別依然是 internal

協議

如果想為一個協議類型明確地指定訪問級別,在定義協議時指定即可。這將限制該協議只能在適當的訪問級別范圍內被采納。

協議中的每一個要求都具有和該協議相同的訪問級別。你不能將協議中的要求設置為其他訪問級別。這樣才能確保該協議的所有要求對于任意采納者都將可用。

ps: 如果你定義了一個 public 訪問級別的協議,那么該協議的所有實現也會是 public 訪問級別。這一點不同于其他類型,例如,當類型是 public 訪問級別時,其成員的訪問級別卻只是 internal

協議繼承

如果定義了一個繼承自其他協議的新協議,那么新協議擁有的訪問級別最高也只能和被繼承協議的訪問級別相同。例如,你不能將繼承自 internal 協議的新協議定義為 public 協議。

協議一致性

一個類型可以采納比自身訪問級別低的協議。例如,你可以定義一個 public 級別的類型,它可以在其他模塊中使用,同時它也可以采納一個 internal 級別的協議,但是只能在該協議所在的模塊中作為符合該協議的類型使用。

采納了協議的類型的訪問級別取它本身和所采納協議兩者間最低的訪問級別。也就是說如果一個類型是 public 級別,采納的協議是 internal 級別,那么采納了這個協議后,該類型作為符合協議的類型時,其訪問級別也是 internal

如果你采納了協議,那么實現了協議的所有要求后,你必須確保這些實現的訪問級別不能低于協議的訪問級別。例如,一個 public 級別的類型,采納了 internal 級別的協議,那么協議的實現至少也得是 internal 級別。

ps: Swift 和 Objective-C 一樣,協議的一致性是全局的,也就是說,在同一程序中,一個類型不可能用兩種不同的方式實現同一個協議。

擴展

你可以在訪問級別允許的情況下對類、結構體、枚舉進行擴展。擴展成員具有和原始類型成員一致的訪問級別。例如,你擴展了一個 public 或者 internal 類型,擴展中的成員具有默認的 internal 訪問級別,和原始類型中的成員一致 。如果你擴展了一個 private 類型,擴展成員則擁有默認的 private 訪問級別。

或者,你可以明確指定擴展的訪問級別(例如,private extension),從而給該擴展中的所有成員指定一個新的默認訪問級別。這個新的默認訪問級別仍然可以被單獨指定的訪問級別所覆蓋。

通過擴展添加協議一致性

如果你通過擴展來采納協議,那么你就不能顯式指定該擴展的訪問級別了。協議擁有相應的訪問級別,并會為該擴展中所有協議要求的實現提供默認的訪問級別。

泛型

泛型類型或泛型函數的訪問級別取決于泛型類型或泛型函數本身的訪問級別,還需結合類型參數的類型約束的訪問級別,根據這些訪問級別中的最低訪問級別來確定。

類型別名

你定義的任何類型別名都會被當作不同的類型,以便于進行訪問控制。類型別名的訪問級別不可高于其表示的類型的訪問級別。例如,private 級別的類型別名可以作為 publicinternalprivate 類型的別名,但是 public 級別的類型別名只能作為 public 類型的別名,不能作為 internalprivate 類型的別名。

ps: 這條規則也適用于為滿足協議一致性而將類型別名用于關聯類型的情況。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容