> 1.0 翻譯:[JaceFu](http://www.devtalking.com/)?校對:[ChildhoodAndy](http://childhood.logdown.com/)
>
> 2.0 翻譯+校對:[mmoaay](https://github.com/mmoaay)
本頁內容包括:
[TOC=2]
_訪問控制_可以限定其他源文件或模塊中代碼對你代碼的訪問級別。這個特性可以讓我們隱藏功能實現的一些細節,并且可以明確的申明我們提供給其他人的接口中哪些部分是他們可以訪問和使用的。
你可以明確地給單個類型(類、結構體、枚舉)設置訪問級別,也可以給這些類型的屬性、函數、初始化方法、基本類型、下標索引等設置訪問級別。協議也可以被限定在一定的范圍內使用,包括協議里的全局常量、變量和函數。
在提供了不同訪問級別的同時,Swift還為某些典型場景提供了默認的訪問級別,這樣就不需要我們在每段代碼中都申明顯式訪問級別。其實,如果只是開發一個單目標應用程序,我們完全可以不用申明代碼的顯式訪問級別。
> 注意:簡單起見,代碼中可以設置訪問級別的特性(屬性、基本類型、函數等),在下面的章節中我們會以“實體”代替。
## 模塊和源文件
Swift 中的訪問控制模型基于模塊和源文件這兩個概念。
_模塊_指的是以獨立單元構建和發布的`Framework`或`Application`。在Swift 中的一個模塊可以使用`import`關鍵字引入另外一個模塊。
在 Swift 中,Xcode的每個構建目標(比如`Framework`或`app bundle`)都被當作模塊處理。如果你是為了實現某個通用的功能,或者是為了封裝一些常用方法而將代碼打包成獨立的`Framework`,這個`Framework`在 Swift 中就被稱為模塊。當它被引入到某個 app 工程或者另外一個`Framework`時,它里面的一切(屬性、函數等)仍然屬于這個獨立的模塊。
_源文件_指的是 Swift 中的`Swift File`,就是編寫 Swift 源代碼的文件,它通常屬于一個模塊。盡管一般我們將不同的`類`分別定義在不同的源文件中,但是同一個源文件可以包含多個`類`和`函數`?的定義。
## 訪問級別
Swift 為代碼中的實體提供了三種不同的訪問級別。這些訪問級別不僅與源文件中定義的實體相關,同時也與源文件所屬的模塊相關。
* `public`:可以訪問自己模塊中源文件里的任何實體,別人也可以通過引入該模塊來訪問源文件里的所有實體。通常情況下,`Framework`?中的某個接口是可以被任何人使用時,你可以將其設置為`public`級別。
* `internal`:可以訪問自己模塊中源文件里的任何實體,但是別人不能訪問該模塊中源文件里的實體。通常情況下,某個接口或`Framework`作為內部結構使用時,你可以將其設置為`internal`級別。
* `private`:只能在當前源文件中使用的實體,稱為私有實體。使用`private`級別,可以用作隱藏某些功能的實現細節。
`public`為最高級訪問級別,`private`為最低級訪問級別。
> 注意:Swift中的`private`訪問和其他語言中的`private`訪問不太一樣,它的范圍限于整個源文件,而不是聲明。這就意味著,一個`類`?可以訪問定義該`類`?的源文件中定義的所有`private`實體,但是如果一個`類`?的擴展是定義在獨立的源文件中,那么就不能訪問這個`類`?的`private`成員。
### 訪問級別的使用原則
Swift 中的訪問級別遵循一個使用原則:訪問級別統一性。 比如說:
* 一個`public`訪問級別的變量,不能將它的類型定義為`internal`和`private`。因為變量可以被任何人訪問,但是定義它的類型不可以,所以這樣就會出現錯誤。
* 函數的訪問級別不能高于它的參數、返回類型的訪問級別。因為如果函數定義為`public`而參數或者返回類型定義為`internal`或`private`,就會出現函數可以被任何人訪問,但是它的參數和返回類型確不可以,同樣會出現錯誤。
### 默認訪問級別
如果你不為代碼中的所有實體定義顯式訪問級別,那么它們默認為`internal`級別。在大多數情況下,我們不需要設置實體的顯式訪問級別。因為我們一般都是在開發一個`app bundle`。
### 單目標應用程序的訪問級別
當你編寫一個單目標應用程序時,該應用的所有功能都是為該應用服務,不需要提供給其他應用或者模塊使用,所以我們不需要明確設置訪問級別,使用默認的訪問級別`internal`即可。但是如果你愿意,你也可以使用`private`級別,用于隱藏一些功能的實現細節。
### Framework的訪問級別
當你開發`Framework`時,就需要把一些對外的接口定義為`public`級別,以便其他人導入該`Framework`后可以正常使用其功能。這些被你定義為`public`的接口,就是這個`Framework`的API。
> 注意:`Framework`的內部實現細節依然可以使用默認的`internal`級別,或者也可以定義為`private`級別。只有當你想把它作為 API 的一部分的時候,才將其定義為`public`級別。
### 單元測試目標的訪問級別
當你的app有單元測試目標時,為了方便測試,測試模塊需要能訪問到你app中的代碼。默認情況下只有`public`級別的實體才可以被其他模塊訪問。然而,如果在引入一個生產模塊時使用`@testable`注解,然后用帶測試的方式編譯這個生產模塊,單元測試目標就可以訪問所有`internal`級別的實體。
## 訪問控制語法
通過修飾符`public`、`internal`、`private`來聲明實體的訪問級別:
~~~
public class SomePublicClass {}
internal class SomeInternalClass {}
private class SomePrivateClass {}
public var somePublicVariable = 0
internal let someInternalConstant = 0
private func somePrivateFunction() {}
~~~
除非有特殊的說明,否則實體都使用默認的訪問級別`internal`,可以查閱[默認訪問級別](http://wiki.jikexueyuan.com/project/swift/chapter2/24_Access_Control.html#default_access_levels)這一節。這意味著在不使用修飾符聲明顯式訪問級別的情況下,`SomeInternalClass`和`someInternalConstant`仍然擁有隱式的訪問級別`internal`:
~~~
class SomeInternalClass {} // 隱式訪問級別 internal
var someInternalConstant = 0 // 隱式訪問級別 internal
~~~
## 自定義類型
如果想為一個自定義類型申明顯式訪問級別,那么要明確一點。那就是你要確保新類型的訪問級別和它實際的作用域相匹配。比如說,如果你定義了一個`private`類,那這個類就只能在定義它的源文件中當作屬性類型、函數參數或者返回類型使用。
類的訪問級別也可以影響到類成員(屬性、函數、初始化方法等)的默認訪問級別。如果你將類申明為`private`類,那么該類的所有成員的默認訪問級別也會成為`private`。如果你將類申明為`public`或者`internal`類(或者不明確的申明訪問級別,而使用默認的`internal`訪問級別),那么該類的所有成員的訪問級別是`internal`。
> 注意:上面提到,一個`public`類的所有成員的訪問級別默認為`internal`級別,而不是`public`級別。如果你想將某個成員申明為`public`級別,那么你必須使用修飾符明確的聲明該成員。這樣做的好處是,在你定義公共接口API的時候,可以明確的選擇哪些屬性或方法是需要公開的,哪些是內部使用的,可以避免將內部使用的屬性方法公開成公共API的錯誤。
~~~
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`。也就是說元組的訪問級別與元組中訪問級別最低的類型一致。
> 注意:元組不同于類、結構體、枚舉、函數那樣有單獨的定義。元組的訪問級別是在它被使用時自動推導出的,而不是明確的申明。
### 函數類型
函數的訪問級別需要根據該函數的參數類型和返回類型的訪問級別得出。如果根據參數類型和返回類型得出的函數訪問級別不符合默認上下文,那么就需要明確地申明該函數的訪問級別。
下面的例子定義了一個名為`someFunction`全局函數,并且沒有明確地申明其訪問級別。也許你會認為該函數應該擁有默認的訪問級別`internal`,但事實并非如此。事實上,如果按下面這種寫法,帶埋是無法編譯通過的:
~~~
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// function implementation goes here
}
~~~
我們可以看到,這個函數的返回類型是一個元組,該元組中包含兩個自定義的類(可查閱[自定義類型](http://wiki.jikexueyuan.com/project/swift/chapter2/24_Access_Control.html#custom_types))。其中一個類的訪問級別是`internal`,另一個的訪問級別是`private`,所以根據元組訪問級別的原則,該元組的訪問級別是`private`(元組的訪問級別與元組中訪問級別最低的類型一致)。
因為該函數返回類型的訪問級別是`private`,所以你必須使用`private`修飾符,明確的聲明該函數:
~~~
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// function implementation goes here
}
~~~
將該函數申明為`public`或`internal`,或者使用默認的訪問級別`internal`都是錯誤的,因為如果把該函數當做`public`或`internal`級別來使用的話,是無法得到`private`級別的返回值的。
### 枚舉類型
枚舉中成員的訪問級別繼承自該枚舉,你不能為枚舉中的成員單獨申明不同的訪問級別。
比如下面的例子,枚舉`CompassPoint`被明確的申明為`public`級別,那么它的成員`North`,`South`,`East`,`West`的訪問級別同樣也是`public`:
~~~
public enum CompassPoint {
case North
case South
case East
case West
}
~~~
### 原始值和關聯值
枚舉定義中的任何原始值或關聯值的類型都必須有一個訪問級別,這個級別至少要不低于枚舉的訪問級別。比如說,你不能在一個`internal`訪問級別的枚舉中定義`private`級別的原始值類型。
### 嵌套類型
如果在`private`級別的類型中定義嵌套類型,那么該嵌套類型就自動擁有`private`訪問級別。如果在`public`或者`internal`級別的類型中定義嵌套類型,那么該嵌套類型自動擁有`internal`訪問級別。如果想讓嵌套類型擁有`public`訪問級別,那么需要明確地申明該嵌套類型的訪問級別。
## 子類
子類的訪問級別不得高于父類的訪問級別。比如說,父類的訪問級別是`internal`,子類的訪問級別就不能申明為`public`。
此外,在滿足子類不高于父類訪問級別以及遵循各訪問級別作用域(即模塊或源文件)的前提下,你可以重寫任意類成員(方法、屬性、初始化方法、下標索引等)。
如果我們無法直接訪問某個類中的屬性或函數等,那么可以繼承該類,從而可以更容易的訪問到該類的類成員。下面的例子中,類`A`的訪問級別是`public`,它包含一個函數`someMethod`,訪問級別為`private`。類`B`繼承類`A`,并且訪問級別申明為`internal`,但是在類`B`中重寫了類`A`中訪問級別為`private`的方法`someMethod`,并重新申明為`internal`級別。通過這種方式,我們就可以訪問到某類中`private`級別的類成員,并且可以重新申明其訪問級別,以便其他人使用:
~~~
public class A {
private func someMethod() {}
}
internal class B: A {
override internal func someMethod() {}
}
~~~
只要滿足子類不高于父類訪問級別以及遵循各訪問級別作用域的前提下(即`private`的作用域在同一個源文件中,`internal`的作用域在同一個模塊下),我們甚至可以在子類中,用子類成員訪問父類成員,哪怕父類成員的訪問級別比子類成員的要低:
~~~
public class A {
private func someMethod() {}
}
internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}
~~~
因為父類`A`和子類`B`定義在同一個源文件中,所以在類`B`中可以在重寫的`someMethod`方法中調用`super.someMethod()`。
## 常量、變量、屬性、下標
常量、變量、屬性不能擁有比它們的類型更高的訪問級別。比如說,你定義一個`public`級別的屬性,但是它的類型是`private`級別的,這是編譯器所不允許的。同樣,下標也不能擁有比索引類型或返回類型更高的訪問級別。
如果常量、變量、屬性、下標索引的定義類型是`private`級別的,那么它們必須要明確的申明訪問級別為`private`:
~~~
private var privateInstance = SomePrivateClass()
~~~
### Getter和Setter
常量、變量、屬性、下標索引的`Getters`和`Setters`的訪問級別繼承自它們所屬成員的訪問級別。
`Setter`的訪問級別可以低于對應的`Getter`的訪問級別,這樣就可以控制變量、屬性或下標索引的讀寫權限。在`var`或`subscript`定義作用域之前,你可以通過`private(set)`或`internal(set)`先為它們的寫權限申明一個較低的訪問級別。
> 注意:這個規定適用于用作存儲的屬性或用作計算的屬性。即使你不明確地申明存儲屬性的`Getter`、`Setter`,Swift也會隱式的為其創建`Getter`和`Setter`,用于對該屬性進行讀取操作。使用`private(set)`和`internal(set)`可以改變Swift隱式創建的`Setter`的訪問級別。這對計算屬性也同樣適用。
下面的例子中定義了一個名為`TrackedString`的結構體,它記錄了`value`屬性被修改的次數:
~~~
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits++
}
}
}
~~~
`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`的值會隨著修改次數進行變化:
~~~
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)")
// prints "The number of edits is 3"
~~~
雖然你可以在其他的源文件中實例化該結構體并且獲取到`numberOfEdits`屬性的值,但是你不能對其進行賦值。這樣就能很好的告訴使用者,你只管使用,而不需要知道其實現細節。
如果有必要你可以為`Getter`和`Setter`方法設定顯式訪問級別。下面的例子就是明確申明了`public`訪問級別的`TrackedString`結構體。結構體的成員(包括`numberOfEdits`屬性)擁有默認的訪問級別`internal`。你可以結合`public`和`private(set)`修飾符把結構體中的`numberOfEdits`屬性`Getter`方法的訪問級別設置為`public`,而`Setter`方法的訪問級別設置為`private`:
~~~
public struct TrackedString {
public private(set) var numberOfEdits = 0
public var value: String = "" {
didSet {
numberOfEdits++
}
}
public init() {}
}
~~~
## 初始化
我們可以給自定義的初始化方法申明訪問級別,但是要不高于它所屬類的訪問級別。但[必要構造器](http://wiki.jikexueyuan.com/project/swift/chapter2/14_Initialization.html#required_initializers)例外,它的訪問級別必須和所屬類的訪問級別相同。
如同函數或方法參數,初始化方法參數的訪問級別也不能低于初始化方法的訪問級別。
### 默認初始化方法
Swift為結構體、類都提供了一個默認的無參初始化方法,用于給它們的所有屬性提供賦值操作,但不會給出具體值。默認初始化方法可以參閱[默認構造器](http://wiki.jikexueyuan.com/project/swift/chapter2/14_Initialization.html#default_initializers)。默認初始化方法的訪問級別與所屬類型的訪問級別相同。
> 注意:如果一個類型被申明為`public`級別,那么默認的初始化方法的訪問級別為`internal`。如果你想讓無參的初始化方法在其他模塊中可以被使用,那么你必須提供一個具有`public`訪問級別的無參初始化方法。
### 結構體的默認成員初始化方法
如果結構體中的任一存儲屬性的訪問級別為`private`,那么它的默認成員初始化方法訪問級別就是`private`。盡管如此,結構體的初始化方法的訪問級別依然是`internal`。
如果你想在其他模塊中使用該結構體的默認成員初始化方法,那么你需要提供一個訪問級別為`public`的默認成員初始化方法。
## 協議
如果想為一個協議明確的申明訪問級別,那么需要注意一點,就是你要確保該協議只在你申明的訪問級別作用域中使用。
協議中的每一個必須要實現的函數都具有和該協議相同的訪問級別。這樣才能確保該協議的使用者可以實現它所提供的函數。
> 注意:如果你定義了一個`public`訪問級別的協議,那么實現該協議提供的必要函數也會是`public`的訪問級別。這一點不同于其他類型,比如,`public`訪問級別的其他類型,他們成員的訪問級別為`internal`。
### 協議繼承
如果定義了一個新的協議,并且該協議繼承了一個已知的協議,那么新協議擁有的訪問級別最高也只和被繼承協議的訪問級別相同。比如說,你不能定義一個`public`的協議而去繼承一個`internal`的協議。
### 協議一致性
類可以采用比自身訪問級別低的協議。比如說,你可以定義一個`public`級別的類,可以讓它在其他模塊中使用,同時它也可以采用一個`internal`級別的協議,并且只能在定義了該協議的模塊中使用。
采用了協議的類的訪問級別取它本身和所采用協議中最低的訪問級別。也就是說如果一個類是`public`級別,采用的協議是`internal`級別,那么采用了這個協議后,該類的訪問級別也是`internal`。
如果你采用了協議,那么實現了協議所必須的方法后,該方法的訪問級別遵循協議的訪問級別。比如說,一個`public`級別的類,采用了`internal`級別的協議,那么該類實現協議的方法至少也得是`internal`。
> 注意:Swift和Objective-C一樣,協議的一致性保證了一個類不可能在同一個程序中用不同的方法采用同一個協議。
## 擴展
你可以在條件允許的情況下對類、結構體、枚舉進行擴展。擴展成員應該具有和原始類成員一致的訪問級別。比如你擴展了一個公共類型,那么你新加的成員應該具有和原始成員一樣的默認的`internal`訪問級別。
或者,你可以明確申明擴展的訪問級別(比如使用`private extension`)給該擴展內所有成員申明一個新的默認訪問級別。這個新的默認訪問級別仍然可以被單獨成員所申明的訪問級別所覆蓋。
### 協議的擴展
如果一個擴展采用了某個協議,那么你就不能對該擴展使用訪問級別修飾符來申明了。該擴展中實現協議的方法都會遵循該協議的訪問級別。
## 泛型
泛型類型或泛型函數的訪問級別取泛型類型、函數本身、泛型類型參數三者中的最低訪問級別。
## 類型別名
任何你定義的類型別名都會被當作不同的類型,以便于進行訪問控制。一個類型別名的訪問級別不可高于原類型的訪問級別。比如說,一個`private`級別的類型別名可以設定給一個`public`、`internal`、`private`的類型,但是一個`public`級別的類型別名只能設定給一個`public`級別的類型,不能設定給`internal`或`private`?級別的類型。
> 注意:這條規則也適用于為滿足協議一致性而給相關類型命名別名的情況。
- 介紹
- 歡迎使用 Swift
- 關于 Swift
- Swift 初見
- Swift 版本歷史記錄
- Swift1.0 發布內容
- Swift 教程
- 基礎部分
- 基本運算符
- 字符串和字符
- 集合類型
- 控制流
- 函數
- 閉包
- 枚舉
- 類和結構體
- 屬性
- 方法
- 下標腳本
- 繼承
- 構造過程
- 析構過程
- 自動引用計數
- 可選鏈
- 錯誤處理
- 類型轉換
- 嵌套類型
- 擴展
- 協議
- 泛型
- 權限控制
- 高級操作符
- 語言參考
- 關于語言參考
- 詞法結構
- 類型
- 表達式
- 語句
- 聲明
- 特性
- 模式
- 泛型參數
- 語法總結
- 蘋果官方Blog官方翻譯
- Access Control 權限控制的黑與白
- 造個類型不是夢-白話Swift類型創建
- WWDC里面的那個“大炮打氣球”
- Swift與C語言指針友好合作
- 引用類型和值類型的恩怨
- 訪問控制和Protected
- 可選類型完美解決占位問題