# 訪問控制
*訪問控制*可以限定其它源文件或模塊對你的代碼的訪問。這個特性可以讓你隱藏代碼的實現細節,并且能提供一個接口來讓別人訪問和使用你的代碼。
你可以明確地給單個類型(類、結構體、枚舉)設置訪問級別,也可以給這些類型的屬性、方法、構造器、下標等設置訪問級別。協議也可以被限定在一定訪問級別的范圍內使用,包括協議里的全局常量、變量和函數。
Swift 不僅提供了多種不同的訪問級別,還為某些典型場景提供了默認的訪問級別,這樣就不需要我們在每段代碼中都顯式聲明訪問級別。如果你只是開發一個單 target 的應用程序,完全可以不用顯式聲明代碼的訪問級別。
> 注意
>
> 為了簡單起見,對于代碼中可以設置訪問級別的特性(屬性、基本類型、函數等),在下面的章節中我們會統一稱之為“實體”。
## 模塊和源文件 {#modules-and-source-files}
Swift 中的訪問控制模型基于模塊和源文件這兩個概念。
*模塊*指的是獨立的代碼單元,框架或應用程序會作為一個獨立的模塊來構建和發布。在 Swift 中,一個模塊可以使用 `import` 關鍵字導入另外一個模塊。
在 Swift 中,Xcode 的每個 target(例如框架或應用程序)都被當作獨立的模塊處理。如果你是為了實現某個通用的功能,或者是為了封裝一些常用方法而將代碼打包成獨立的框架,這個框架就是 Swift 中的一個模塊。當它被導入到某個應用程序或者其他框架時,框架的內容都將屬于這個獨立的模塊。
*源文件* 就是 Swift 模塊中的源代碼文件(實際上,源文件屬于一個應用程序或框架)。盡管我們一般會將不同的類型分別定義在不同的源文件中,但是同一個源文件也可以包含多個類型、函數等的定義。
## 訪問級別 {#access-levels}
Swift 為代碼中的實體提供了五種不同的*訪問級別*。這些訪問級別不僅與源文件中定義的實體相關,同時也與源文件所屬的模塊相關。
- *open* 和 *public* 級別可以讓實體被同一模塊源文件中的所有實體訪問,在模塊外也可以通過導入該模塊來訪問源文件里的所有實體。通常情況下,你會使用 open 或 public 級別來指定框架的外部接口。open 和 public 的區別在后面會提到。
- *internal* 級別讓實體被同一模塊源文件中的任何實體訪問,但是不能被模塊外的實體訪問。通常情況下,如果某個接口只在應用程序或框架內部使用,就可以將其設置為 internal 級別。
- *fileprivate* 限制實體只能在其定義的文件內部訪問。如果功能的部分實現細節只需要在文件內使用時,可以使用 fileprivate 來將其隱藏。
- *private* 限制實體只能在其定義的作用域,以及同一文件內的 extension 訪問。如果功能的部分細節只需要在當前作用域內使用時,可以使用 private 來將其隱藏。
open 為最高訪問級別(限制最少),private 為最低訪問級別(限制最多)。
open 只能作用于類和類的成員,它和 public 的區別主要在于 open 限定的類和成員能夠在模塊外能被繼承和重寫,在下面的 [子類](#subclassing) 這一節中有詳解。將類的訪問級別顯式指定為 `open` 表明你已經設計好了類的代碼,并且充分考慮過這個類在其他模塊中用作父類時的影響。
### 訪問級別基本原則 {#guiding-principle-of-access-levels}
Swift 中的訪問級別遵循一個基本原則:*實體不能定義在具有更低訪問級別(更嚴格)的實體中*。
例如:
- 一個 public 的變量,其類型的訪問級別不能是 internal,fileprivate 或是 private。因為無法保證變量的類型在使用變量的地方也具有訪問權限。
- 函數的訪問級別不能高于它的參數類型和返回類型的訪問級別。因為這樣就會出現函數可以在任何地方被訪問,但是它的參數類型和返回類型卻不可以的情況。
關于此原則在各種情況下的具體表現,將在下文有所體現。
### 默認訪問級別 {#default-access-levels}
你代碼中所有的實體,如果你不顯式的指定它們的訪問級別,那么它們將都有一個 `internal` 的默認訪問級別,(有一些例外情況,本文稍后會有說明)。因此,多數情況下你不需要顯示指定實體的訪問級別。
### 單 target 應用程序的訪問級別 {#access-levels-for-single-target-apps}
當你編寫一個單 target 應用程序時,應用的所有功能都是為該應用服務,而不需要提供給其他應用或者模塊使用,所以你不需要明確設置訪問級別,使用默認的訪問級別 internal 即可。但是,你也可以使用 `fileprivate` 或 `private` 訪問級別,用于隱藏一些功能的實現細節。
### 框架的訪問級別 {#access-levels-for-frameworks}
當你開發框架時,就需要把一些對外的接口定義為 open 或 public 訪問級別,以便使用者導入該框架后可以正常使用其功能。這些被你定義為對外的接口,就是這個框架的 API。
> 注意
>
> 框架的內部實現仍然可以使用默認的訪問級別 `internal`,當你需要對框架內部其它部分隱藏細節時可以使用 `private` 或 `fileprivate`。對于框架的對外 API 部分,你就需要將它們設置為 `open` 或 `public` 了。
### 單元測試 target 的訪問級別 {#access-levels-for-unit-test-targets}
當你的應用程序包含單元測試 target 時,為了測試,測試模塊需要訪問應用程序模塊中的代碼。默認情況下只有 `open` 或 `public` 級別的實體才可以被其他模塊訪問。然而,如果在導入應用程序模塊的語句前使用 `@testable` 特性,然后在允許測試的編譯設置(`Build Options -> Enable Testability`)下編譯這個應用程序模塊,單元測試目標就可以訪問應用程序模塊中所有內部級別的實體。
## 訪問控制語法 {#access-control-syntax}
通過修飾符 `open`、`public`、`internal`、`fileprivate`、`private` 來聲明實體的訪問級別:
```swift
public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}
public var somePublicVariable = 0
internal let someInternalConstant = 0
fileprivate func someFilePrivateFunction() {}
private func somePrivateFunction() {}
```
除非專門指定,否則實體默認的訪問級別為 `internal`,可以查閱 [默認訪問級別](#default-access-levels) 這一節。這意味著在不使用修飾符顯式聲明訪問級別的情況下,`SomeInternalClass` 和 `someInternalConstant` 的訪問級別是 `internal`:
```swift
class SomeInternalClass {} // 隱式 internal
var someInternalConstant = 0 // 隱式 internal
```
## 自定義類型 {#custom-types}
如果想為一個自定義類型指定訪問級別,在定義類型時進行指定即可。新類型只能在它的訪問級別限制范圍內使用。例如,你定義了一個 `fileprivate` 級別的類,那這個類就只能在定義它的源文件中使用,可以作為屬性類型、函數參數類型或者返回類型等等。
一個類型的訪問級別也會影響到類型*成員*(屬性、方法、構造器、下標)的默認訪問級別。如果你將類型指定為 `private` 或者 `fileprivate` 級別,那么該類型的所有成員的默認訪問級別也會變成 `private` 或者 `fileprivate` 級別。如果你將類型指定為 `internal` 或 `public`(或者不明確指定訪問級別,而使用默認的 `internal` ),那么該類型的所有成員的默認訪問級別將是 `internal`。
> 重點
>
> 上面提到,一個 `public` 類型的所有成員的訪問級別默認為 `internal` 級別,而不是 `public` 級別。如果你想將某個成員指定為 `public` 級別,那么你必須顯式指定。這樣做的好處是,在你定義公共接口的時候,可以明確地選擇哪些接口是需要公開的,哪些是內部使用的,避免不小心將內部使用的接口公開。
```swift
public class SomePublicClass { // 顯式 public 類
public var somePublicProperty = 0 // 顯式 public 類成員
var someInternalProperty = 0 // 隱式 internal 類成員
fileprivate func someFilePrivateMethod() {} // 顯式 fileprivate 類成員
private func somePrivateMethod() {} // 顯式 private 類成員
}
class SomeInternalClass { // 隱式 internal 類
var someInternalProperty = 0 // 隱式 internal 類成員
fileprivate func someFilePrivateMethod() {} // 顯式 fileprivate 類成員
private func somePrivateMethod() {} // 顯式 private 類成員
}
fileprivate class SomeFilePrivateClass { // 顯式 fileprivate 類
func someFilePrivateMethod() {} // 隱式 fileprivate 類成員
private func somePrivateMethod() {} // 顯式 private 類成員
}
private class SomePrivateClass { // 顯式 private 類
func somePrivateMethod() {} // 隱式 private 類成員
}
```
### 元組類型 {#tuple-types}
元組的訪問級別將由元組中訪問級別最嚴格的類型來決定。例如,如果你構建了一個包含兩種不同類型的元組,其中一個類型為 `internal`,另一個類型為 `private`,那么這個元組的訪問級別為 `private`。
> 注意
>
> 元組不同于類、結構體、枚舉、函數那樣有單獨的定義。一個元組的訪問級別由元組中元素的訪問級別來決定的,不能被顯示指定。
### 函數類型 {#function-types}
函數的訪問級別根據訪問級別最嚴格的參數類型或返回類型的訪問級別來決定。但是,如果這種訪問級別不符合函數定義所在環境的默認訪問級別,那么就需要明確地指定該函數的訪問級別。
下面的例子定義了一個名為 `someFunction()` 的全局函數,并且沒有明確地指定其訪問級別。也許你會認為該函數應該擁有默認的訪問級別 `internal`,但事實并非如此。事實上,如果按下面這種寫法,代碼將無法通過編譯:
```swift
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此處是函數實現部分
}
```
我們可以看到,這個函數的返回類型是一個元組,該元組中包含兩個自定義的類(可查閱 [自定義類型](#custom-types))。其中一個類的訪問級別是 `internal`,另一個的訪問級別是 `private`,所以根據元組訪問級別的原則,該元組的訪問級別是 `private`(元組的訪問級別與元組中訪問級別最低的類型一致)。
因為該函數返回類型的訪問級別是 `private`,所以你必須使用 `private` 修飾符來明確指定該函數的訪問級別:
```swift
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此處是函數實現部分
}
```
將該函數指定為 `public` 或 `internal`,或者使用默認的訪問級別 `internal` 都是錯誤的,因為如果把該函數當做 `public` 或 `internal` 級別來使用的話,可能會無法訪問 `private` 級別的返回值。
### 枚舉類型 {#enumeration-types}
枚舉成員的訪問級別和該枚舉類型相同,你不能為枚舉成員單獨指定不同的訪問級別。
比如下面的例子,枚舉 `CompassPoint` 被明確指定為 `public`,那么它的成員 `north`、`south`、`east`、`west` 的訪問級別同樣也是 `public`:
```swift
public enum CompassPoint {
case north
case south
case east
case west
}
```
#### 原始值和關聯值 {#raw-values-and-associated-values}
枚舉定義中的任何原始值或關聯值的類型的訪問級別至少不能低于枚舉類型的訪問級別。例如,你不能在一個 `internal` 的枚舉中定義 `private` 的原始值類型。
### 嵌套類型 {#nested-types}
嵌套類型的訪問級別和包含它的類型的訪問級別相同,嵌套類型是 public 的情況除外。在一個 public 的類型中定義嵌套類型,那么嵌套類型自動擁有 `internal` 的訪問級別。如果你想讓嵌套類型擁有 `public` 訪問級別,那么必須顯式指定該嵌套類型的訪問級別為 public。
## 子類 {#subclassing}
你可以繼承同一模塊中的所有有訪問權限的類,也可以繼承不同模塊中被 open 修飾的類。一個子類的訪問級別不得高于父類的訪問級別。例如,父類的訪問級別是 `internal`,子類的訪問級別就不能是 `public`。
此外,在同一模塊中,你可以在符合當前訪問級別的條件下重寫任意類成員(方法、屬性、構造器、下標等)。在不同模塊中,你可以重寫類中被 open 修飾的成員。
可以通過重寫給所繼承類的成員提供更高的訪問級別。下面的例子中,類 `A` 的訪問級別是 `public`,它包含一個方法 `someMethod()`,訪問級別為 `fileprivate`。類 `B` 繼承自類 `A`,訪問級別為 `internal`,但是在類 `B` 中重寫了類 `A` 中訪問級別為 `fileprivate` 的方法 `someMethod()`,并重新指定為 `internal` 級別。通過這種方式,我們就可以將某類中 `fileprivate` 級別的類成員重新指定為更高的訪問級別,以便其他人使用:
```swift
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {}
}
```
我們甚至可以在子類中,用子類成員去訪問訪問級別更低的父類成員,只要這一操作在相應訪問級別的限制范圍內(也就是說,在同一源文件中訪問父類 `fileprivate` 級別的成員,在同一模塊內訪問父類 `internal` 級別的成員):
```swift
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}
```
因為父類 `A` 和子類 `B` 定義在同一個源文件中,所以在子類 `B` 可以在重寫的 `someMethod()` 方法中調用 `super.someMethod()`。
## 常量、變量、屬性、下標 {#constants-variables-properties-subscripts}
常量、變量、屬性不能擁有比它們的類型更高的訪問級別。例如,你不能定義一個 `public` 級別的屬性,但是它的類型卻是 `private` 級別的。同樣,下標也不能擁有比索引類型或返回類型更高的訪問級別。
如果常量、變量、屬性、下標的類型是 `private` 級別的,那么它們必須明確指定訪問級別為 `private`:
```swift
private var privateInstance = SomePrivateClass()
```
### Getter 和 Setter {#getters-and-setters}
常量、變量、屬性、下標的 `Getters` 和 `Setters` 的訪問級別和它們所屬類型的訪問級別相同。
`Setter` 的訪問級別可以低于對應的 `Getter` 的訪問級別,這樣就可以控制變量、屬性或下標的讀寫權限。在 `var` 或 `subscript` 關鍵字之前,你可以通過 `fileprivate(set)`,`private(set)` 或 `internal(set)` 為它們的寫入權限指定更低的訪問級別。
> 注意
>
> 這個規則同時適用于存儲型屬性和計算型屬性。即使你不明確指定存儲型屬性的 `Getter` 和 `Setter`,Swift 也會隱式地為其創建 `Getter` 和 `Setter`,用于訪問該屬性的存儲內容。使用 `fileprivate(set)`,`private(set)` 和 `internal(set)` 可以改變 `Setter` 的訪問級別,這對計算型屬性也同樣適用。
下面的例子中定義了一個名為 `TrackedString` 的結構體,它記錄了 `value` 屬性被修改的次數:
```swift
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits += 1
}
}
}
```
`TrackedString` 結構體定義了一個用于存儲 `String` 值的屬性 `value`,并將初始值設為 `""`(一個空字符串)。該結構體還定義了另一個用于存儲 `Int` 值的屬性 `numberOfEdits`,它用于記錄屬性 `value` 被修改的次數。這個功能通過屬性 `value` 的 `didSet` 觀察器實現,每當給 `value` 賦新值時就會調用 `didSet` 方法,然后將 `numberOfEdits` 的值加一。
結構體 `TrackedString` 和它的屬性 `value` 都沒有顯式地指定訪問級別,所以它們都是用默認的訪問級別 `internal`。但是該結構體的 `numberOfEdits` 屬性使用了 `private(set)` 修飾符,這意味著 `numberOfEdits` 屬性只能在結構體的定義中進行賦值。`numberOfEdits` 屬性的 `Getter` 依然是默認的訪問級別 `internal`,但是 `Setter` 的訪問級別是 `private`,這表示該屬性只能在內部修改,而在結構體的外部則表現為一個只讀屬性。
如果你實例化 `TrackedString` 結構體,并多次對 `value` 屬性的值進行修改,你就會看到 `numberOfEdits` 的值會隨著修改次數而變化:
```swift
var stringToEdit = TrackedString()
stringToEdit.value = "This string will be tracked."
stringToEdit.value += " This edit will increment numberOfEdits."
stringToEdit.value += " So will this one."
print("The number of edits is \(stringToEdit.numberOfEdits)")
// 打印“The number of edits is 3”
```
雖然你可以在其他的源文件中實例化該結構體并且獲取到 `numberOfEdits` 屬性的值,但是你不能對其進行賦值。這一限制保護了該記錄功能的實現細節,同時還提供了方便的訪問方式。
你可以在必要時為 `Getter` 和 `Setter` 顯式指定訪問級別。下面的例子將 `TrackedString` 結構體明確指定為了 `public` 訪問級別。結構體的成員(包括 `numberOfEdits` 屬性)擁有默認的訪問級別 `internal`。你可以結合 `public` 和 `private(set)` 修飾符把結構體中的 `numberOfEdits` 屬性的 `Getter` 的訪問級別設置為 `public`,而 `Setter` 的訪問級別設置為 `private`:
```swift
public struct TrackedString {
public private(set) var numberOfEdits = 0
public var value: String = "" {
didSet {
numberOfEdits += 1
}
}
public init() {}
}
```
## 構造器 {#initializers}
自定義構造器的訪問級別可以低于或等于其所屬類型的訪問級別。唯一的例外是 [必要構造器](./14_Initialization.md#required-initializers),它的訪問級別必須和所屬類型的訪問級別相同。
如同函數或方法的參數,構造器參數的訪問級別也不能低于構造器本身的訪問級別。
### 默認構造器 {#default-initializers}
如 [默認構造器](./14_Initialization.md#default-initializers) 所述,Swift 會為結構體和類提供一個默認的無參數的構造器,只要它們為所有存儲型屬性設置了默認初始值,并且未提供自定義的構造器。
默認構造器的訪問級別與所屬類型的訪問級別相同,除非類型的訪問級別是 `public`。如果一個類型被指定為 `public` 級別,那么默認構造器的訪問級別將為 `internal`。如果你希望一個 `public` 級別的類型也能在其他模塊中使用這種無參數的默認構造器,你只能自己提供一個 `public` 訪問級別的無參數構造器。
### 結構體默認的成員逐一構造器 {#default-memberwise-initializers-for-structure-types}
如果結構體中任意存儲型屬性的訪問級別為 `private`,那么該結構體默認的成員逐一構造器的訪問級別就是 `private`。否則,這種構造器的訪問級別依然是 `internal`。
如同前面提到的默認構造器,如果你希望一個 `public` 級別的結構體也能在其他模塊中使用其默認的成員逐一構造器,你依然只能自己提供一個 `public` 訪問級別的成員逐一構造器。
## 協議 {#protocols}
如果想為一個協議類型明確地指定訪問級別,在聲明協議時指定即可。這將限制該協議只能在適當的訪問級別范圍內被遵循。
協議中的每個方法或屬性都必須具有和該協議相同的訪問級別。你不能將協議中的方法或屬性設置為其他訪問級別。這樣才能確保該協議的所有方法或屬性對于任意遵循者都可用。
> 注意
>
> 如果你定義了一個 `public` 訪問級別的協議,那么該協議的所有實現也會是 `public` 訪問級別。這一點不同于其他類型,例如,類型是 `public` 訪問級別時,其成員的訪問級別卻只是 `internal`。
### 協議繼承 {#protocol-inheritance}
如果定義了一個繼承自其他協議的新協議,那么新協議擁有的訪問級別最高也只能和被繼承協議的訪問級別相同。例如,你不能將繼承自 `internal` 協議的新協議訪問級別指定為 `public` 協議。
### 協議遵循 {#protocol-conformance}
一個類型可以遵循比它級別更低的協議。例如,你可以定義一個 `public` 級別類型,它能在別的模塊中使用,但是如果它遵循一個 `internal` 協議,這個遵循的部分就只能在這個 `internal` 協議所在的模塊中使用。
遵循協議時的上下文級別是類型和協議中級別最小的那個。如果一個類型是 `public` 級別,但它要遵循的協議是 `internal` 級別,那么這個類型對該協議的遵循上下文就是 `internal` 級別。
當你編寫或擴展一個類型讓它遵循一個協議時,你必須確保該類型對協議的每一個要求的實現,至少與遵循協議的上下文級別一致。例如,一個 `public` 類型遵循一個 `internal` 協議,這個類型對協議的所有實現至少都應是 `internal` 級別的。
> 注意
>
> Swift 和 Objective-C 一樣,協議遵循是全局的,也就是說,在同一程序中,一個類型不可能用兩種不同的方式實現同一個協議。
## Extension {#extensions}
Extension 可以在訪問級別允許的情況下對類、結構體、枚舉進行擴展。Extension 的新增成員具有和原始類型成員一致的訪問級別。例如,你使用 extension 擴展了一個 `public` 或者 `internal` 類型,則 extension 中的成員就默認使用 `internal` 訪問級別。如果你使用 extension 擴展一個 `fileprivate` 類型,則 extension 中的成員默認使用 `fileprivate` 訪問級別。如果你使用 extension 擴展了一個 `private` 類型,則 extension 的成員默認使用 `private` 訪問級別。
或者,你可以通過修飾語重新指定 extension 的默認訪問級別(例如,`private`),從而給該 extension 中的所有成員指定一個新的默認訪問級別。這個新的默認訪問級別仍然可以被單獨成員指定的訪問級別所覆蓋。
如果你使用 extension 來遵循協議的話,就不能顯式地聲明 extension 的訪問級別。extension 每個 protocol 要求的實現都默認使用 protocol 的訪問級別。
### Extension 的私有成員 {#Private Members in Extensions}
擴展同一文件內的類,結構體或者枚舉,extension 里的代碼會表現得跟聲明在原類型里的一模一樣。也就是說你可以這樣:
- 在類型的聲明里聲明一個私有成員,在同一文件的 extension 里訪問。
- 在 extension 里聲明一個私有成員,在同一文件的另一個 extension 里訪問。
- 在 extension 里聲明一個私有成員,在同一文件的類型聲明里訪問。
這意味著你可以使用 extension 來組織你的代碼,而且不受私有成員的影響。例如,給定下面這樣一個簡單的協議:
```swift
protocol SomeProtocol {
func doSomething()
}
```
你可以使用 extension 來遵循協議,就像這樣:
```swift
struct SomeStruct {
private var privateVariable = 12
}
extension SomeStruct: SomeProtocol {
func doSomething() {
print(privateVariable)
}
}
```
## 泛型 {#generics}
泛型類型或泛型函數的訪問級別取決于泛型類型或泛型函數本身的訪問級別,還需結合類型參數的類型約束的訪問級別,根據這些訪問級別中的最低訪問級別來確定。
## 類型別名 {#type-aliases}
你定義的任何類型別名都會被當作不同的類型,以便于進行訪問控制。類型別名的訪問級別不可高于其表示的類型的訪問級別。例如,`private` 級別的類型別名可以作為 `private`、`fileprivate`、`internal`、`public` 或者 `open` 類型的別名,但是 `public` 級別的類型別名只能作為 `public` 類型的別名,不能作為 `internal`、`fileprivate` 或 `private` 類型的別名。
> 注意
>
> 這條規則也適用于為滿足協議遵循而將類型別名用于關聯類型的情況。
- 1.關于 Swift
- 2.Swift 初見
- 2-1基礎部分
- 2-2基本運算符
- 2-3字符串和字符
- 2-4集合類型
- 2-5控制流
- 2-6函數
- 2-7閉包
- 2-8枚舉
- 2-9類和結構體
- 2-10屬性
- 2-11方法
- 2-12下標
- 2-13繼承
- 2-14構造過程
- 2-15析構過程
- 2-16可選鏈
- 2-17錯誤處理
- 2-18類型轉換
- 2-19嵌套類型
- 2-20擴展
- 2-21協議
- 2-22泛型
- 2-23不透明類型
- 2-24自動引用計數
- 2-25內存安全
- 2-26訪問控制
- 2-27高級運算符
- 3-1關于語言參考
- 3-2詞法結構
- 3-3類型
- 3-4表達式
- 3-5語句
- 3-6聲明
- 3-7特性
- 3-8模式
- 3-9泛型參數
- 4語法總結