訪問控制能夠限制你的代碼從其他文件和模塊中訪問,這個特性能夠讓你隱藏你具體的實現,并且也可以讓代碼能夠訪問和使用。
你可以給class
,structures
,enumerations
這幾種類型增加訪問等級,也可以給properties
,methods
,initializers
,和屬于這些類型的subscripts
.可以限制明確的上下文,也可以是全局常量,變量和函數。
Access Levels(訪問級別)#####
Swift對代碼提供了五種不同的訪問等級.這些訪問等級既相對于被定義的源文件,也相對于文件所屬的模塊。
-
Open
和public
能夠讓同一模塊中的任何文件中訪問,也可以通過導入定義的模塊讓其他模塊或者源文件訪問.當一個framework中說明是公用的訪問接口時,用open
或者public
都可以.兩者的不同下邊做了具體說明. -
Internal
能夠讓同一模塊中的任何文件中訪問,但是不能讓這個模塊以外的文件訪問. -
File-private
限制只能讓自己定義的文件訪問。在整個文件被訪問時,使用file-private
可以隱藏函數的具體實現。 -
Private
能夠限制只在聲明的實體中訪問。
Open
是最高的訪問級別,private
是最低的訪問級別。
Open
僅可以在類和類成員中使用,它區別于public
從以下幾點:
- 使用
public
的類,可以在它們定義模塊中派生子類。 - 使用
public
的類成員,可以在它們定義的模塊中,被子類繼承。 -
Open
類可以在定義的模塊中派生子類,可以在任何模塊中導入定義的模塊。 -
Open
類可以在定義的模塊中被子類繼承,并且可以在任何模塊中導入定義的模塊。
Guiding Principle of Access Levels(訪問級別的使用原則)####
在Swift中,訪問等級遵循規定原則:訪問級別統一性。
例如:
- 一個
public
變量不能被internal
,file-private
,或private
定義。因為public
被訪問的地方,其他被定義的不能被訪問。 - 函數的訪問級別不能高于它的參數、返回類型的訪問級別。因為如果函數定義為public而參數或者返回類型定義為
internal
或private
,就會出現函數可以被任何人訪問,但是它的參數和返回類型不可以,同樣會出現錯誤.
Default Access Levels(默認的訪問級別)####
代碼中的所有實體,如果你不明確的定義其訪問級別,那么它們默認為internal級別。在大多數情況下,我們不需要明確的設置實體的訪問級別,因為我們大多數時候都是在開發一個 App bundle。
Access Levels for Single-Target Apps(單目標應用程序的訪問級別)####
當你編寫一個單目標應用程序時,該應用的所有功能都是為該應用服務,不需要提供給其他應用或者模塊使用,所以我們不需要明確設置訪問級別,使用默認的訪問級別internal
即可。但是如果你愿意,你也可以使用private
級別,用于隱藏一些功能的實現細節。
Access Levels for Frameworks(Frameworks的訪問級別)####
當你開發Framework時,你需要使用open
或public
標記為開放接口,以便其他人導入該Framework后可以正常使用其功能。這些被你定義為開放的實體,就是這個Framework的API。
注意:Framework的內部實現細節依然可以使用默認的internal級別,如果你想隱藏實現細節,或者也可以定義為`private`或`file private`級別。如果你想將它作為 `framework`的API,你可以使用`open`或`public`。
Access Control Syntax(訪問控制語法)####
通過修飾符open
, public
,internal
, file private
, or private
來聲明實體的訪問級別:
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
,可以查閱默認訪問級別這一節.這意味著SomeInternalClass
和someInternalConstant
可以去掉internal
,但是他們仍然擁有隱式的訪問級別internal
:
class SomeInternalClass {} // implicitly internal
let someInternalConstant = 0 // implicitly internal
Custom Types(自定義類型)####
如果你想為一個自定義類型指定一個明確的訪問級別,那么你要明確一點。那就是你要確保新類型的訪問級別和它實際的作用域相匹配。比如說,如果某個類里的屬性、函數、返回值它們的作用域僅在當前的源文件中,那么你就可以將這個類聲明為file-private
類。
類的訪問級別也可以影響到類成員(屬性、函數、初始化方法等)的默認訪問級別。如果你將類聲明為private
或 file private
類,那么該類的所有成員的默認訪問級別也會成為private
或 file private
。如果你將類申明為public
或者internal
類(或者不明確的指定訪問級別,而使用默認的internal
訪問級別),那么該類的所有成員的訪問級別是internal
。
注意:上面提到,一個`public`類的所有成員的訪問級別默認為`internal`級別,而不是`public`級別。如果你想將某個成員申明為public級別,那么你必須使用修飾符明確的申明該成員。這樣做的好處是,在你定義公共接口API的時候,可以明確的選擇哪些屬性或方法是需要公開的,哪些是內部使用的,可以避免將內部使用的屬性方法公開成公共API的錯誤。
public class SomePublicClass { // explicitly public class
public var somePublicProperty = 0 // explicitly public class member
var someInternalProperty = 0 // implicitly internal class member
fileprivate func someFilePrivateMethod() {} // explicitly file-private class member
private func somePrivateMethod() {} // explicitly private class member
}
class SomeInternalClass { // implicitly internal class
var someInternalProperty = 0 // implicitly internal class member
fileprivate func someFilePrivateMethod() {} // explicitly file-private class member
private func somePrivateMethod() {} // explicitly private class member
}
fileprivate class SomeFilePrivateClass { // explicitly file-private class
func someFilePrivateMethod() {} // implicitly file-private class member
private func somePrivateMethod() {} // explicitly private class member
}
private class SomePrivateClass { // explicitly private class
func somePrivateMethod() {} // implicitly private class member
}
Tuple Types(元組類型)####
元組的訪問級別使用是所有類型的訪問級別使用中最為嚴謹的。比如說,如果你構建一個包含兩種不同類型元素的元組,其中一個元素類型的訪問級別為internal
,另一個為private
級別,那么這個元組的訪問級別為private
。也就是說元組的訪問級別遵循它里面元組中最低級的訪問級別。
注意:元組不同于類、結構體、枚舉、函數那樣有單獨的定義。元組的訪問級別是在它被使用時自動推導出的,而不是明確的申明。
Function Types(函數類型)####
函數的訪問級別需要根據該函數的參數類型訪問級別、返回類型訪問級別得出。如果根據參數類型和返回類型得出的函數訪問級別不符合上下文,那么就需要明確的申明該函數的訪問級別。
下面的例子中定義了一個全局函數名為someFunction
,并且沒有明確的申明其訪問級別。你也許會認為該函數應該擁有默認的訪問級別internal
,但事實并非如此。事實上,如果按下面這種寫法,編譯器是無法編譯通過的:
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// function implementation goes here
}
我們可以看到,這個函數的返回類型是一個元組,該元組中包含兩個自定義的類(可查閱自定義類型)。其中一個類的訪問級別是internal,另一個的訪問級別是private,所以根據元組訪問級別的原則,該元組的訪問級別是private(元組的訪問級別遵循它里面元組中最低級的訪問級別)。
因為該函數返回類型的訪問級別是private
,所以你必須使用private
修飾符,明確的申請該函數:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// function implementation goes here
}
將該函數申明為public
或internal
,或者使用默認的訪問級別internal都是錯誤的,因為如果把該函數當做public
或internal
級別來使用的話,是無法得到private
級別的返回值的。
Enumeration Types(枚舉類型)####
枚舉中成員的訪問級別繼承自該枚舉,你不能為枚舉中的成員指定訪問級別。
比如下面的例子,枚舉CompassPoint
被明確的申明為public
級別,那么它的成員North
,South
,East
,West
的訪問級別同樣也是public
:
public enum CompassPoint {
case North
case South
case East
case West
}
Raw Values and Associated Values(原始值和關聯值)####
用于枚舉定義中的任何原始值,或關聯的值類型必須有一個訪問級別,至少要高于枚舉的訪問級別。比如說,你不能在一個internal
訪問級別的枚舉中定義private
級別的原始值類型。
Nested Types(嵌套類型)####
如果在private
級別的類型中定義嵌套類型,那么該嵌套類型就自動擁有private
訪問級別。如果在file-private
級別的類型中定義嵌套類型,那么該嵌套類型就自動擁有file-private
訪問級別.如果在public
或者internal
級別的類型中定義嵌套類型,那么該嵌套類型自動擁有internal
訪問級別。如果想讓嵌套類型擁有public
訪問級別,那么需要對該嵌套類型進行明確的訪問級別申明。
Subclassing(子類)####
子類的訪問級別不得高于父類的訪問級別。比如說,父類的訪問級別是internal
,子類的訪問級別就不能申明為public
。
此外,在滿足子類不高于父類訪問級別以及遵循各訪問級別作用域(即模塊或源文件)的前提下,你可以重寫任意類成員(方法、屬性、初始化方法、下標索引等)。
如果我們無法直接訪問某個類中的屬性或函數等,那么可以繼承該類,從而可以更容易的訪問到該類的類成員。下面的例子中,類A的訪問級別是public
,它包含一個函數someMethod
,訪問級別為file-private
。類B繼承類A,并且訪問級別申明為internal
,但是在類B中重寫了類A中訪問級別為file-private
的方法someMethod
,并重新申明為internal
級別。通過這種方式,我們就可以訪問到某類中file-private
級別的類成員,并且可以重新申明其訪問級別,以便其他人使用:
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {}
}
只要滿足子類不高于父類訪問級別以及遵循各訪問級別作用域的前提下(即file-private
的作用域在同一個源文件中,internal
的作用域在同一個模塊下),我們甚至可以在子類中,用子類成員訪問父類成員,哪怕父類成員的訪問級別比子類成員的要低:
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, and Subscripts(常量、變量、屬性、下標)####
常量、變量、屬性不能擁有比它們的類型更高的訪問級別。比如說,你定義一個public
級別的屬性,但是它的類型是private
級別的,這是編譯器不允許的。同樣,下標也不能擁有比索引類型或返回類型更高的訪問級別。
如果常量、變量、屬性、下標索引的定義類型是private
級別的,那么它們必須要明確的申明訪問級別為private
:
private var privateInstance = SomePrivateClass()
Getter和Setter####
常量、變量、屬性、下標索引的Getters
和Setters
的訪問級別繼承自它們所屬成員的訪問級別。
Setter
的訪問級別可以低于對應的Getter
的訪問級別,這樣就可以控制變量、屬性或下標索引的讀寫權限。在var
或subscript
定義作用域之前,你可以通過fileprivate(set)
,private(set)
或internal(set)
先為它門的寫權限申明一個較低的訪問級別。
注意:這個規定適用于用作存儲的屬性或用作計算的屬性。即使你不明確的申明存儲屬性的`Getter`、`Setter`,`Swift`也會隱式的為其創建`Getter`和`Setter`,用于對該屬性進行讀取操作。使用`fileprivate(set)`,`private(set)`和`internal(set)`可以改變Swift隱式創建的`Setter`的訪問級別。在計算屬性中也是同樣的。
下面的例子中定義了一個結構體名為`TrackedString`,它記錄了`value`屬性被修改的次數:
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits++
}
}
}
TrackedString
結構體定義了一個用于存儲的屬性名為value
,類型為String
,并將初始化值設為""(即一個空字符串)。該結構體同時也定義了另一個用于存儲的屬性名為numberOfEdits
,類型為Int
,它用于記錄屬性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."
println("The number of edits is \(stringToEdit.numberOfEdits)")
// prints "The number of edits is 3"
雖然你可以在其他的源文件中實例化該結構體并且獲取到numberOfEdits
屬性的值,但是你不能對其進行賦值。這樣就能很好的告訴使用者,你只管使用,而不需要知道其實現細節。
Initializers(初始化)####
我們可以給自定義的初始化方法指定訪問級別,但是必須要低于或等于它所屬類的訪問級別。但如果該初始化方法是必須要使用的話,那它的訪問級別就必須和所屬類的訪問級別相同。
如同函數或方法參數,初始化方法參數的訪問級別也不能低于初始化方法的訪問級別。
Default Initializers(默認初始化方法)####
Swift為結構體、類都提供了一個默認的無參初始化方法,用于給它們的所有屬性提供賦值操作,但不會給出具體值。默認初始化方法可以參閱Default Initializers
。默認初始化方法的訪問級別與所屬類型的訪問級別相同。
注意:如果一個類型被申明為public
級別,那么默認的初始化方法的訪問級別為internal
。如果你想讓無參的初始化方法在其他模塊中可以被使用,那么你必須提供一個具有public
訪問級別的無參初始化方法。
Default Memberwise Initializers for Structure Types(結構體的默認成員初始化方法)####
如果結構體中的任一存儲屬性的訪問級別為private
,那么它的默認成員初始化方法訪問級別就是private
。盡管如此,結構體的初始化方法的訪問級別依然是internal
。
如果你想在其他模塊中使用該結構體的默認成員初始化方法,那么你需要提供一個訪問級別為public
的默認成員初始化方法。
Protocols(協議)####
如果你想為一個協議明確的申明訪問級別,那么有一點需要注意,就是你要確保該協議只在你申明的訪問級別作用域中使用。
協議中的每一個必須要實現的函數都具有和該協議相同的訪問級別。這樣才能確保該協議的使用者可以實現它所提供的函數。
注意:如果你定義了一個public
訪問級別的協議,那么實現該協議提供的必要函數也會是public的訪問級別。這一點不同于其他類型,比如,public
訪問級別的其他類型,他們成員的訪問級別為internal
。
Protocol Inheritance(協議繼承)####
如果定義了一個新的協議,并且該協議繼承了一個已知的協議,那么新協議擁有的訪問級別最高也只和被繼承協議的訪問級別相同。比如說,你不能定義一個public
的協議而去繼承一個internal
的協議。
Protocol Conformance(協議一致性)####
類可以采用比自身訪問級別低的協議。比如說,你可以定義一個public級別的類,可以讓它在其他模塊中使用,同時它也可以采用一個internal
級別的協議,并且只能在定義了該協議的模塊中使用。
采用了協議的類的訪問級別遵循它本身和采用協議中最低的訪問級別。也就是說如果一個類是public
級別,采用的協議是internal
級別,那個采用了這個協議后,該類的訪問級別也是internal
。
如果你采用了協議,那么實現了協議必須的方法后,該方法的訪問級別遵循協議的訪問級別。比如說,一個public
級別的類,采用了internal
級別的協議,那么該類實現協議的方法至少也得是internal
。
注意:在Swift中和Objective-C中一樣,協議的一致性保證了一個類不可能在同一個程序中用不同的方法采用同一個協議。
Extensions(擴展)####
你可以在條件允許的情況下對類、結構體、枚舉進行擴展。擴展成員應該具有和原始類成員一致的訪問級別。比如你擴展了一個公共類型,那么你新加的成員應該具有和原始成員一樣的默認的internal
訪問級別。
或者,你可以明確申明擴展的訪問級別(比如使用private extension
)給該擴展內所有成員指定一個新的默認訪問級別。這個新的默認訪問級別仍然可以被單獨成員所指定的訪問級別所覆蓋。
Adding Protocol Conformance with an Extension(協議的擴展)####
如果一個擴展采用了某個協議,那么你就不能對該擴展使用訪問級別修飾符來申明了。該擴展中實現協議的方法都會遵循該協議的訪問級別。
Generics(泛型)####
泛型類型或泛型函數的訪問級別遵循泛型類型、函數本身、泛型類型參數三者中訪問級別最低的級別。
Type Aliases(類型別名)####
任何被你定義的類型別名都會被視作為不同的類型,這些類型用于訪問控制。一個類型別名的訪問級別可以低于或等于這個類型的訪問級別。比如說,一個private
級別的類型別名可以設定給一個open
,public
,internal
,file-private
,private
的類型,但是一個public
級別的類型別名只能設定給一個public
級別的類型,不能設定給internal
,file-private
或private
的類類型。
注意:這條規則也適用于為滿足協議一致性而給相關類型命名別名。