1.0 翻譯:JaceFu 校對:ChildhoodAndy
2.0 翻譯+校對:mmoaay
2.1 翻譯:Prayer 校對:shanks,2015-11-01
2.2 翻譯+校對:SketchK 2016-05-17
3.0.1, shanks,2016-11-13
本頁內(nèi)容包括:
訪問控制可以限定其他源文件或模塊中的代碼對你的代碼的訪問級別。這個(gè)特性可以讓我們隱藏代碼的一些實(shí)現(xiàn)細(xì)節(jié),并且可以為其他人可以訪問和使用的代碼提供接口。
你可以明確地給單個(gè)類型(類、結(jié)構(gòu)體、枚舉)設(shè)置訪問級別,也可以給這些類型的屬性、方法、構(gòu)造器、下標(biāo)等設(shè)置訪問級別。協(xié)議也可以被限定在一定的范圍內(nèi)使用,包括協(xié)議里的全局常量、變量和函數(shù)。
Swift 不僅提供了多種不同的訪問級別,還為某些典型場景提供了默認(rèn)的訪問級別,這樣就不需要我們在每段代碼中都申明顯式訪問級別。其實(shí),如果只是開發(fā)一個(gè)單一目標(biāo)的應(yīng)用程序,我們完全可以不用顯式聲明代碼的訪問級別。
注意
為了簡單起見,對于代碼中可以設(shè)置訪問級別的特性(屬性、基本類型、函數(shù)等),在下面的章節(jié)中我們會稱之為“實(shí)體”。
Swift 中的訪問控制模型基于模塊和源文件這兩個(gè)概念。
模塊指的是獨(dú)立的代碼單元,框架或應(yīng)用程序會作為一個(gè)獨(dú)立的模塊來構(gòu)建和發(fā)布。在 Swift 中,一個(gè)模塊可以使用 import
關(guān)鍵字導(dǎo)入另外一個(gè)模塊。
在 Swift 中,Xcode 的每個(gè)目標(biāo)(例如框架或應(yīng)用程序)都被當(dāng)作獨(dú)立的模塊處理。如果你是為了實(shí)現(xiàn)某個(gè)通用的功能,或者是為了封裝一些常用方法而將代碼打包成獨(dú)立的框架,這個(gè)框架就是 Swift 中的一個(gè)模塊。當(dāng)它被導(dǎo)入到某個(gè)應(yīng)用程序或者其他框架時(shí),框架內(nèi)容都將屬于這個(gè)獨(dú)立的模塊。
源文件就是 Swift 中的源代碼文件,它通常屬于一個(gè)模塊,即一個(gè)應(yīng)用程序或者框架。盡管我們一般會將不同的類型分別定義在不同的源文件中,但是同一個(gè)源文件也可以包含多個(gè)類型、函數(shù)之類的定義。
Swift 為代碼中的實(shí)體提供了五種不同的訪問級別。這些訪問級別不僅與源文件中定義的實(shí)體相關(guān),同時(shí)也與源文件所屬的模塊相關(guān)。
開放訪問為最高(限制最少)訪問級別,私有訪問為最低(限制最多)訪問級別。
開放訪問只作用于類類型和類的成員,它和公開訪問的區(qū)別如下:
Swift 中的訪問級別遵循一個(gè)基本原則:不可以在某個(gè)實(shí)體中定義訪問級別更低(更嚴(yán)格)的實(shí)體。
例如:
關(guān)于此原則的各種情況的具體實(shí)現(xiàn),將在下面的細(xì)節(jié)中體現(xiàn)。
如果你不為代碼中的實(shí)體顯式指定訪問級別,那么它們默認(rèn)為 internal
級別(有一些例外情況,稍后會進(jìn)行說明)。因此,在大多數(shù)情況下,我們不需要顯式指定實(shí)體的訪問級別。
當(dāng)你編寫一個(gè)單目標(biāo)應(yīng)用程序時(shí),應(yīng)用的所有功能都是為該應(yīng)用服務(wù),而不需要提供給其他應(yīng)用或者模塊使用,所以我們不需要明確設(shè)置訪問級別,使用默認(rèn)的訪問級別 internal
即可。但是,你也可以使用文件私有訪問或私有訪問級別,用于隱藏一些功能的實(shí)現(xiàn)細(xì)節(jié)。
當(dāng)你開發(fā)框架時(shí),就需要把一些對外的接口定義為開放訪問或公開訪問級別,以便使用者導(dǎo)入該框架后可以正常使用其功能。這些被你定義為對外的接口,就是這個(gè)框架的 API。
注意
框架依然會使用默認(rèn)的內(nèi)部訪問級別,也可以指定為文件私有訪問或者私有訪問級別。當(dāng)你想把某個(gè)實(shí)體作為框架的 API 的時(shí)候,需顯式為其指定開放訪問或公開訪問級別。
當(dāng)你的應(yīng)用程序包含單元測試目標(biāo)時(shí),為了測試,測試模塊需要訪問應(yīng)用程序模塊中的代碼。默認(rèn)情況下只有開放訪問或公開訪問級別級別的實(shí)體才可以被其他模塊訪問。然而,如果在導(dǎo)入應(yīng)用程序模塊的語句前使用 @testable
特性,然后在允許測試的編譯設(shè)置(Build Options -> Enable Testability
)下編譯這個(gè)應(yīng)用程序模塊,單元測試目標(biāo)就可以訪問應(yīng)用程序模塊中所有內(nèi)部級別的實(shí)體。
通過修飾符 open
,public
,internal
,fileprivate
,private
來聲明實(shí)體的訪問級別:
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() {}
除非專門指定,否則實(shí)體默認(rèn)的訪問級別為內(nèi)部訪問級別,可以查閱默認(rèn)訪問級別這一節(jié)。這意味著在不使用修飾符顯式聲明訪問級別的情況下,SomeInternalClass
和 someInternalConstant
仍然擁有隱式的內(nèi)部訪問級別:
class SomeInternalClass {} // 隱式內(nèi)部訪問級別
var someInternalConstant = 0 // 隱式內(nèi)部訪問級別
如果想為一個(gè)自定義類型指定訪問級別,在定義類型時(shí)進(jìn)行指定即可。新類型只能在它的訪問級別限制范圍內(nèi)使用。例如,你定義了一個(gè)文件私有級別的類,那這個(gè)類就只能在定義它的源文件中使用,可以作為屬性類型、函數(shù)參數(shù)類型或者返回類型,等等。
一個(gè)類型的訪問級別也會影響到類型成員(屬性、方法、構(gòu)造器、下標(biāo))的默認(rèn)訪問級別。如果你將類型指定為私有或者文件私有級別,那么該類型的所有成員的默認(rèn)訪問級別也會變成私有或者文件私有級別。如果你將類型指定為公開或者內(nèi)部訪問級別(或者不明確指定訪問級別,而使用默認(rèn)的內(nèi)部訪問級別),那么該類型的所有成員的默認(rèn)訪問級別將是內(nèi)部訪問。
重要
上面提到,一個(gè)公開類型的所有成員的訪問級別默認(rèn)為內(nèi)部訪問級別,而不是公開級別。如果你想將某個(gè)成員指定為公開訪問級別,那么你必須顯式指定。這樣做的好處是,在你定義公共接口的時(shí)候,可以明確地選擇哪些接口是需要公開的,哪些是內(nèi)部使用的,避免不小心將內(nèi)部使用的接口公開。
public class SomePublicClass { // 顯式公開類
public var somePublicProperty = 0 // 顯式公開類成員
var someInternalProperty = 0 // 隱式內(nèi)部類成員
fileprivate func someFilePrivateMethod() {} // 顯式文件私有類成員
private func somePrivateMethod() {} // 顯式私有類成員
}
class SomeInternalClass { // 隱式內(nèi)部類
var someInternalProperty = 0 // 隱式內(nèi)部類成員
fileprivate func someFilePrivateMethod() {} // 顯式文件私有類成員
private func somePrivateMethod() {} // 顯式私有類成員
}
fileprivate class SomeFilePrivateClass { // 顯式文件私有類
func someFilePrivateMethod() {} // 隱式文件私有類成員
private func somePrivateMethod() {} // 顯式私有類成員
}
private class SomePrivateClass { // 顯式私有類
func somePrivateMethod() {} // 隱式私有類成員
}
元組的訪問級別將由元組中訪問級別最嚴(yán)格的類型來決定。例如,如果你構(gòu)建了一個(gè)包含兩種不同類型的元組,其中一個(gè)類型為內(nèi)部訪問級別,另一個(gè)類型為私有訪問級別,那么這個(gè)元組的訪問級別為私有訪問級別。
注意
元組不同于類、結(jié)構(gòu)體、枚舉、函數(shù)那樣有單獨(dú)的定義。元組的訪問級別是在它被使用時(shí)自動(dòng)推斷出的,而無法明確指定。
函數(shù)的訪問級別根據(jù)訪問級別最嚴(yán)格的參數(shù)類型或返回類型的訪問級別來決定。但是,如果這種訪問級別不符合函數(shù)定義所在環(huán)境的默認(rèn)訪問級別,那么就需要明確地指定該函數(shù)的訪問級別。
下面的例子定義了一個(gè)名為 someFunction()
的全局函數(shù),并且沒有明確地指定其訪問級別。也許你會認(rèn)為該函數(shù)應(yīng)該擁有默認(rèn)的訪問級別 internal
,但事實(shí)并非如此。事實(shí)上,如果按下面這種寫法,代碼將無法通過編譯:
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此處是函數(shù)實(shí)現(xiàn)部分
}
我們可以看到,這個(gè)函數(shù)的返回類型是一個(gè)元組,該元組中包含兩個(gè)自定義的類(可查閱自定義類型)。其中一個(gè)類的訪問級別是 internal
,另一個(gè)的訪問級別是 private
,所以根據(jù)元組訪問級別的原則,該元組的訪問級別是 private
(元組的訪問級別與元組中訪問級別最低的類型一致)。
因?yàn)樵摵瘮?shù)返回類型的訪問級別是 private
,所以你必須使用 private
修飾符,明確指定該函數(shù)的訪問級別:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此處是函數(shù)實(shí)現(xiàn)部分
}
將該函數(shù)指定為 public
或 internal
,或者使用默認(rèn)的訪問級別 internal
都是錯(cuò)誤的,因?yàn)槿绻言摵瘮?shù)當(dāng)做 public
或 internal
級別來使用的話,可能會無法訪問 private
級別的返回值。
枚舉成員的訪問級別和該枚舉類型相同,你不能為枚舉成員單獨(dú)指定不同的訪問級別。
比如下面的例子,枚舉 CompassPoint
被明確指定為 public
級別,那么它的成員 North
、South
、East
、West
的訪問級別同樣也是 public
:
public enum CompassPoint {
case North
case South
case East
case West
}
枚舉定義中的任何原始值或關(guān)聯(lián)值的類型的訪問級別至少不能低于枚舉類型的訪問級別。例如,你不能在一個(gè) internal
訪問級別的枚舉中定義 private
級別的原始值類型。
如果在 private
級別的類型中定義嵌套類型,那么該嵌套類型就自動(dòng)擁有 private
訪問級別。如果在 public
或者 internal
級別的類型中定義嵌套類型,那么該嵌套類型自動(dòng)擁有 internal
訪問級別。如果想讓嵌套類型擁有 public
訪問級別,那么需要明確指定該嵌套類型的訪問級別。
子類的訪問級別不得高于父類的訪問級別。例如,父類的訪問級別是 internal
,子類的訪問級別就不能是 public
。
此外,你可以在符合當(dāng)前訪問級別的條件下重寫任意類成員(方法、屬性、構(gòu)造器、下標(biāo)等)。
可以通過重寫為繼承來的類成員提供更高的訪問級別。下面的例子中,類 A
的訪問級別是 public
,它包含一個(gè)方法 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() {}
}
我們甚至可以在子類中,用子類成員去訪問訪問級別更低的父類成員,只要這一操作在相應(yīng)訪問級別的限制范圍內(nèi)(也就是說,在同一源文件中訪問父類 private
級別的成員,在同一模塊內(nèi)訪問父類 internal
級別的成員):
public class A {
private func someMethod() {}
}
internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}
因?yàn)楦割?A
和子類 B
定義在同一個(gè)源文件中,所以在子類 B
可以在重寫的 someMethod()
方法中調(diào)用 super.someMethod()
。
常量、變量、屬性不能擁有比它們的類型更高的訪問級別。例如,你不能定義一個(gè) public
級別的屬性,但是它的類型卻是 private
級別的。同樣,下標(biāo)也不能擁有比索引類型或返回類型更高的訪問級別。
如果常量、變量、屬性、下標(biāo)的類型是 private
級別的,那么它們必須明確指定訪問級別為 private
:
private var privateInstance = SomePrivateClass()
常量、變量、屬性、下標(biāo)的 Getters
和 Setters
的訪問級別和它們所屬類型的訪問級別相同。
Setter
的訪問級別可以低于對應(yīng)的 Getter
的訪問級別,這樣就可以控制變量、屬性或下標(biāo)的讀寫權(quán)限。在 var
或 subscript
關(guān)鍵字之前,你可以通過 fileprivate(set)
,private(set)
或 internal(set)
為它們的寫入權(quán)限指定更低的訪問級別。
注意
這個(gè)規(guī)則同時(shí)適用于存儲型屬性和計(jì)算型屬性。即使你不明確指定存儲型屬性的Getter
和Setter
,Swift 也會隱式地為其創(chuàng)建Getter
和Setter
,用于訪問該屬性的后備存儲。使用fileprivate(set)
,private(set)
和internal(set)
可以改變Setter
的訪問級別,這對計(jì)算型屬性也同樣適用。
下面的例子中定義了一個(gè)名為 TrackedString
的結(jié)構(gòu)體,它記錄了 value
屬性被修改的次數(shù):
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits += 1
}
}
}
TrackedString
結(jié)構(gòu)體定義了一個(gè)用于存儲 String
值的屬性 value
,并將初始值設(shè)為 ""
(一個(gè)空字符串)。該結(jié)構(gòu)體還定義了另一個(gè)用于存儲 Int
值的屬性 numberOfEdits
,它用于記錄屬性 value
被修改的次數(shù)。這個(gè)功能通過屬性 value
的 didSet
觀察器實(shí)現(xiàn),每當(dāng)給 value
賦新值時(shí)就會調(diào)用 didSet
方法,然后將 numberOfEdits
的值加一。
結(jié)構(gòu)體 TrackedString
和它的屬性 value
均沒有顯式指定訪問級別,所以它們都擁有默認(rèn)的訪問級別 internal
。但是該結(jié)構(gòu)體的 numberOfEdits
屬性使用了 private(set)
修飾符,這意味著 numberOfEdits
屬性只能在定義該結(jié)構(gòu)體的源文件中賦值。numberOfEdits
屬性的 Getter
依然是默認(rèn)的訪問級別 internal
,但是 Setter
的訪問級別是 private
,這表示該屬性只有在當(dāng)前的源文件中是可讀寫的,而在當(dāng)前源文件所屬的模塊中只是一個(gè)可讀的屬性。
如果你實(shí)例化 TrackedString
結(jié)構(gòu)體,并多次對 value
屬性的值進(jìn)行修改,你就會看到 numberOfEdits
的值會隨著修改次數(shù)而變化:
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”
雖然你可以在其他的源文件中實(shí)例化該結(jié)構(gòu)體并且獲取到 numberOfEdits
屬性的值,但是你不能對其進(jìn)行賦值。這一限制保護(hù)了該記錄功能的實(shí)現(xiàn)細(xì)節(jié),同時(shí)還提供了方便的訪問方式。
你可以在必要時(shí)為 Getter
和 Setter
顯式指定訪問級別。下面的例子將 TrackedString
結(jié)構(gòu)體明確指定為了 public
訪問級別。結(jié)構(gòu)體的成員(包括 numberOfEdits
屬性)擁有默認(rèn)的訪問級別 internal
。你可以結(jié)合 public
和 private(set)
修飾符把結(jié)構(gòu)體中的 numberOfEdits
屬性的 Getter
的訪問級別設(shè)置為 public
,而 Setter
的訪問級別設(shè)置為 private
:
public struct TrackedString {
public private(set) var numberOfEdits = 0
public var value: String = "" {
didSet {
numberOfEdits += 1
}
}
public init() {}
}
自定義構(gòu)造器的訪問級別可以低于或等于其所屬類型的訪問級別。唯一的例外是必要構(gòu)造器,它的訪問級別必須和所屬類型的訪問級別相同。
如同函數(shù)或方法的參數(shù),構(gòu)造器參數(shù)的訪問級別也不能低于構(gòu)造器本身的訪問級別。
如默認(rèn)構(gòu)造器所述,Swift 會為結(jié)構(gòu)體和類提供一個(gè)默認(rèn)的無參數(shù)的構(gòu)造器,只要它們?yōu)樗写鎯π蛯傩栽O(shè)置了默認(rèn)初始值,并且未提供自定義的構(gòu)造器。
默認(rèn)構(gòu)造器的訪問級別與所屬類型的訪問級別相同,除非類型的訪問級別是 public
。如果一個(gè)類型被指定為 public
級別,那么默認(rèn)構(gòu)造器的訪問級別將為 internal
。如果你希望一個(gè) public
級別的類型也能在其他模塊中使用這種無參數(shù)的默認(rèn)構(gòu)造器,你只能自己提供一個(gè) public
訪問級別的無參數(shù)構(gòu)造器。
如果結(jié)構(gòu)體中任意存儲型屬性的訪問級別為 private
,那么該結(jié)構(gòu)體默認(rèn)的成員逐一構(gòu)造器的訪問級別就是 private
。否則,這種構(gòu)造器的訪問級別依然是 internal
。
如同前面提到的默認(rèn)構(gòu)造器,如果你希望一個(gè) public
級別的結(jié)構(gòu)體也能在其他模塊中使用其默認(rèn)的成員逐一構(gòu)造器,你依然只能自己提供一個(gè) public
訪問級別的成員逐一構(gòu)造器。
如果想為一個(gè)協(xié)議類型明確地指定訪問級別,在定義協(xié)議時(shí)指定即可。這將限制該協(xié)議只能在適當(dāng)?shù)脑L問級別范圍內(nèi)被采納。
協(xié)議中的每一個(gè)要求都具有和該協(xié)議相同的訪問級別。你不能將協(xié)議中的要求設(shè)置為其他訪問級別。這樣才能確保該協(xié)議的所有要求對于任意采納者都將可用。
注意
如果你定義了一個(gè)public
訪問級別的協(xié)議,那么該協(xié)議的所有實(shí)現(xiàn)也會是public
訪問級別。這一點(diǎn)不同于其他類型,例如,當(dāng)類型是public
訪問級別時(shí),其成員的訪問級別卻只是internal
。
如果定義了一個(gè)繼承自其他協(xié)議的新協(xié)議,那么新協(xié)議擁有的訪問級別最高也只能和被繼承協(xié)議的訪問級別相同。例如,你不能將繼承自 internal
協(xié)議的新協(xié)議定義為 public
協(xié)議。
一個(gè)類型可以采納比自身訪問級別低的協(xié)議。例如,你可以定義一個(gè) public
級別的類型,它可以在其他模塊中使用,同時(shí)它也可以采納一個(gè) internal
級別的協(xié)議,但是只能在該協(xié)議所在的模塊中作為符合該協(xié)議的類型使用。
采納了協(xié)議的類型的訪問級別取它本身和所采納協(xié)議兩者間最低的訪問級別。也就是說如果一個(gè)類型是 public
級別,采納的協(xié)議是 internal
級別,那么采納了這個(gè)協(xié)議后,該類型作為符合協(xié)議的類型時(shí),其訪問級別也是 internal
。
如果你采納了協(xié)議,那么實(shí)現(xiàn)了協(xié)議的所有要求后,你必須確保這些實(shí)現(xiàn)的訪問級別不能低于協(xié)議的訪問級別。例如,一個(gè) public
級別的類型,采納了 internal
級別的協(xié)議,那么協(xié)議的實(shí)現(xiàn)至少也得是 internal
級別。
注意
Swift 和 Objective-C 一樣,協(xié)議的一致性是全局的,也就是說,在同一程序中,一個(gè)類型不可能用兩種不同的方式實(shí)現(xiàn)同一個(gè)協(xié)議。
你可以在訪問級別允許的情況下對類、結(jié)構(gòu)體、枚舉進(jìn)行擴(kuò)展。擴(kuò)展成員具有和原始類型成員一致的訪問級別。例如,你擴(kuò)展了一個(gè) public
或者 internal
類型,擴(kuò)展中的成員具有默認(rèn)的 internal
訪問級別,和原始類型中的成員一致 。如果你擴(kuò)展了一個(gè) private
類型,擴(kuò)展成員則擁有默認(rèn)的 private
訪問級別。
或者,你可以明確指定擴(kuò)展的訪問級別(例如,private extension
),從而給該擴(kuò)展中的所有成員指定一個(gè)新的默認(rèn)訪問級別。這個(gè)新的默認(rèn)訪問級別仍然可以被單獨(dú)指定的訪問級別所覆蓋。
如果你通過擴(kuò)展來采納協(xié)議,那么你就不能顯式指定該擴(kuò)展的訪問級別了。協(xié)議擁有相應(yīng)的訪問級別,并會為該擴(kuò)展中所有協(xié)議要求的實(shí)現(xiàn)提供默認(rèn)的訪問級別。
泛型類型或泛型函數(shù)的訪問級別取決于泛型類型或泛型函數(shù)本身的訪問級別,還需結(jié)合類型參數(shù)的類型約束的訪問級別,根據(jù)這些訪問級別中的最低訪問級別來確定。
你定義的任何類型別名都會被當(dāng)作不同的類型,以便于進(jìn)行訪問控制。類型別名的訪問級別不可高于其表示的類型的訪問級別。例如,private
級別的類型別名可以作為 private
,file-private
,internal
,public
或者open
類型的別名,但是 public
級別的類型別名只能作為 public
類型的別名,不能作為 internal
,file-private
,或 private
類型的別名。
注意
這條規(guī)則也適用于為滿足協(xié)議一致性而將類型別名用于關(guān)聯(lián)類型的情況。