案例代碼下載
訪問控制
訪問控制限制從其他源文件和模塊中的代碼訪問部分代碼。此功能使可以隱藏代碼的實現(xiàn)細節(jié)绕沈,并指定一個首選接口谓形,通過該接口可以訪問和使用該代碼拟烫。
可以為各個類型(類,結(jié)構(gòu)和枚舉)以及屬于這些類型的屬性向抢,方法认境,initializers和下標分配特定的訪問級別。協(xié)議可以限制在某個上下文中挟鸠,全局常量叉信,變量和函數(shù)也可以。
除了提供各種級別的訪問控制外艘希,Swift還通過為典型方案提供默認訪問級別來減少顯式指定訪問控制級別的需求硼身。實際上,如果正在編寫單目標應用程序覆享,則可能根本不需要顯式指定訪問控制級別佳遂。
注意
為簡便起見,代碼中可以應用訪問控制的各個方面(屬性撒顿,類型丑罪,函數(shù)等)在下面的部分中稱為“實體”。
模塊和源文件
Swift的訪問控制模型基于模塊和源文件的概念。
模塊是代碼分布的單個單元——框架或應用程序被構(gòu)建和包裝為單個單元并可以通過另一個模塊使用Swift的import關(guān)鍵字引入吩屹。
Xcode中的每個構(gòu)建目標(例如應用程序包或框架)都被視為Swift中的單獨模塊跪另。如果將應用程序代碼的各個方面組合在一起作為一個獨立的框架 - 也許是為了跨多個應用程序封裝和重用該代碼 - 當被導入和使用在應用程序中或使用在其他框架中那么在該框架中定義的所有內(nèi)容將成為單獨模塊的一部分。
源文件是一個模塊內(nèi)的單個Swift源代碼文件(實際上煤搜,一個應用程序或框架內(nèi)的一個單獨的文件)免绿。雖然在單獨的源文件中定義單個類型很常見,但單個源文件可以包含多個類型宅楞,函數(shù)等的定義针姿。
訪問級別
Swift為代碼中的實體提供了五種不同的訪問級別。這些訪問級別與定義實體的源文件相關(guān)厌衙,也與源文件所屬的模塊相關(guān)距淫。
- 開放訪問和公共訪問使實體可以在其定義模塊的任何源文件中使用,也可以在另一個導入定義模塊的模塊的源文件中使用婶希。在指定框架的公共接口時榕暇,通常使用開放或公共訪問。開放和公共訪問之間的區(qū)別如下所述喻杈。
- 內(nèi)部訪問使實體可以在其定義模塊的任何源文件中使用彤枢,但不能在該模塊之外的任何源文件中使用。在定義應用程序或框架的內(nèi)部結(jié)構(gòu)時筒饰,通常使用內(nèi)部訪問缴啡。
- 文件私有訪問將實體的使用限制在其自己的定義源文件中。當在整個文件中使用這些詳細信息時瓷们,使用文件專用訪問來隱藏特定功能的實現(xiàn)細節(jié)业栅。
- 私有訪問將實體的使用限制為封閉聲明,以及同一文件中該聲明的擴展谬晕。當這些詳細信息僅在單個聲明中使用時碘裕,使用私有訪問來隱藏特定功能的實現(xiàn)細節(jié)。
開放訪問是最高(限制性最性芮)的訪問級別帮孔,私有訪問是最低(限制性最強)的訪問級別。
開放訪問僅適用于類和類成員不撑,它與公共訪問不同文兢,如下所示:
- 具有公共訪問權(quán)限或任何更嚴格的訪問級別的類只能在定義它們的模塊中進行子類化。
- 具有公共訪問權(quán)限或任何更具限制性的訪問級別的類成員只能在定義它們的模塊中被子類覆蓋燎孟。
- 開放類可以在定義它們的模塊中進行子類化禽作,也可以在導入模塊的任何模塊中進行子類化。
- 開放類成員可以由定義它們的模塊中的子類覆蓋揩页,也可以在導入定義它們的模塊的任何模塊中覆蓋。
將類標記為開放訪問明確表示已考慮使用該類作為其他模塊的超類的代碼的影響,并且已相應地設計了類的代碼爆侣。
訪問級別的指導原則
Swift中的訪問級別遵循一個總體指導原則:沒有實體可以根據(jù)具有較低(更嚴格)訪問級別的另一個實體來定義萍程。
例如:
- 公共變量不能定義為具有內(nèi)部、文件私有或私有類型兔仰,因為在使用公共變量的任何地方都可能無法使用該類型茫负。
- 函數(shù)不能具有比其參數(shù)類型和返回類型更高的訪問級別,因為該函數(shù)可用于其組成類型對周圍代碼不可用的情況乎赴。
下文詳細介紹了該指導原則對該語言不同方面的具體影響忍法。
默認訪問級別
如果沒有自己指定顯式訪問級別,則代碼中的所有實體(具有一些特定的例外情況榕吼,如本章后面所述)都具有內(nèi)部的默認訪問級別饿序。因此,在許多情況下羹蚣,無需在代碼中指定顯式訪問級別原探。
單目標應用的訪問級別
當編寫一個簡單的單目標應用程序時,應用程序中的代碼通常是自包含在應用程序中的顽素,并且不需要在應用程序模塊外部提供咽弦。內(nèi)部的默認訪問級別已匹配此要求。因此胁出,無需指定自定義訪問級別型型。但是,可能希望將代碼的某些部分標記為私有或文件私有全蝶,以便從應用程序模塊中的其他代碼中隱藏其實現(xiàn)細節(jié)闹蒜。
框架的訪問級別
在開發(fā)框架時,將該框架的面向公眾的接口標記為開放或公共裸诽,以便其他模塊(例如導入框架的應用程序)可以查看和訪問該框架嫂用。這個面向公眾的接口是框架的應用程序編程接口(或API)。
注意:
框架的任何內(nèi)部實現(xiàn)細節(jié)仍然可以使用內(nèi)部的默認訪問級別丈冬,或者如果要將它們隱藏在框架內(nèi)部代碼的其他部分中嘱函,則可以將其標記為私有或文件私有。如果希望實體成為框架API的一部分埂蕊,則需要將實體標記為開放或公開往弓。
單元測試目標的訪問級別
當使用單元測試目標編寫應用程序時,應用程序中的代碼需要可供該模塊使用才能進行測試蓄氧。默認情況下函似,只有標記為open或public的實體才可供其他模塊訪問。但是喉童,如果使用@testable屬性標記產(chǎn)品模塊的導入聲明并且在啟用測試的情況下編譯該產(chǎn)品模塊撇寞,則單元測試目標可以訪問任何內(nèi)部實體。
訪問控制語法
定義實體的訪問級別通過在實體的導入前放置一個open,public蔑担,internal牌废,fileprivate,或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() {}
除非另行指定啤握,否則默認訪問級別為內(nèi)部鸟缕,如默認訪問級別中所述。這意味著排抬,SomeInternalClass和someInternalConstant能夠在沒有明確的訪問級別的修改寫入懂从,仍會有內(nèi)部的訪問級別:
class SomeInternalClass {} // 默認內(nèi)部訪問級別
let someInternalConstant = 0 // 默認內(nèi)部訪問級別
自定義類型
如果要為自定義類型指定顯式訪問級別,請在定義類型時執(zhí)行此操作蹲蒲。然后可以在其訪問級別允許的任何地方使用新類型番甩。例如,如果定義文件專用類悠鞍,則該類只能用作定義文件專用類的源文件中的屬性類型对室,或者作為函數(shù)參數(shù)或返回類型。
類型的訪問控制級別還會影響該類型成員的默認訪問級別(其屬性咖祭,方法掩宜,初始值設定項和下標)。如果將類型的訪問級別定義為私有或文件專用么翰,則其成員的默認訪問級別也將為私有或文件專用牺汤。如果將類型的訪問級別定義為內(nèi)部或公共(或使用內(nèi)部的默認訪問級別而未明確指定訪問級別),則類型成員的默認訪問級別將是內(nèi)部的浩嫌。
重要
公共類型默認具有內(nèi)部成員檐迟,而不是公共成員。如果希望類型成員是公共的码耐,則必須明確標記它追迟。此要求可確保某個類型的面向公眾的API是選擇發(fā)布的內(nèi)容,并避免錯誤地將類型的內(nèi)部工作方式顯示為公共API骚腥。
public class SomePublicClass {
public var somePublicProperty = 0
var someInternalProperty = 0
fileprivate func someFilePrivateMethod() {}
private func somePrivateMethod() {}
}
class SomeInternalClass {
var someInternalProperty = 0
fileprivate func someFilePrivateMethod() {}
private func somePrivateMethod() {}
}
fileprivate class SomeFilePrivateClass {
func someFilePrivateMethod() {}
private func somePrivateMethod() {}
}
private class SomePrivateClass {
func somePrivateMethod() {}
}
元組類型
元組類型的訪問級別是元組中使用的所有類型的最嚴格的訪問級別敦间。例如,如果從兩種不同類型組成一個元組束铭,一個具有內(nèi)部訪問權(quán)限廓块,另一個具有私有訪問權(quán)限,則該復合元組類型的訪問級別將是私有的契沫。
注意
元組類型沒有類带猴,結(jié)構(gòu),枚舉和函數(shù)的獨立定義懈万。使用元組類型時會自動推導出元組類型的訪問級別拴清,并且無法明確指定靶病。
函數(shù)類型
函數(shù)類型的訪問級別計算為函數(shù)參數(shù)類型和返回類型的最嚴格的訪問級別。如果函數(shù)的計算訪問級別與上下文默認值不匹配贷掖,則必須明確指定訪問級別作為函數(shù)定義的一部分嫡秕。
下面的示例定義了一個名為someFunction()的全局函數(shù)渴语,但沒有為函數(shù)本身提供特定的訪問級別修飾符苹威。可能希望此函數(shù)具有默認的“內(nèi)部”訪問級別驾凶,但事實并非如此牙甫。實際上如下所寫的someFunction()不會:
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 這是函數(shù)實現(xiàn)
}
函數(shù)的返回類型是一個元組類型,由兩個自定義類型中定義的自定義類組成调违。其中一個類定義為內(nèi)部窟哺,另一個定義為私有。因此技肩,復合元組類型的整體訪問級別是私有的(元組的組成類型的最小訪問級別)且轨。
因為函數(shù)的返回類型是私有的,所以必須使用private函數(shù)聲明的修飾符來標記函數(shù)的整體訪問級別:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 這是函數(shù)實現(xiàn)
}
使用public或internal修飾符或使用的默認設置internal標記someFunction()定義是無效的虚婿,因為該函數(shù)的公共或內(nèi)部使用者可能對函數(shù)返回類型中使用的私有類沒有適當訪問權(quán)限旋奢。
枚舉類型
枚舉的各個案例自動獲得與其所屬枚舉相同的訪問級別。無法為單個枚舉案例指定不同的訪問級別然痊。
在下面的示例中至朗,CompassPoint枚舉具有顯式的公共訪問級別。枚舉的情況north剧浸,south锹引,east,和west因此也有公共的訪問級別:
public enum CompassPoint {
case north
case south
case east
case west
}
原始值和關(guān)聯(lián)值
用于枚舉定義中的任何原始值或關(guān)聯(lián)值的類型必須具有至少與枚舉的訪問級別一樣高的訪問級別唆香。例如嫌变,不能將私有類型用作具有內(nèi)部訪問級別的枚舉的原始值類型。
嵌套類型
私有類型中定義的嵌套類型具有私有的自動訪問級別躬它。在文件專用類型中定義的嵌套類型具有文件專用的自動訪問級別腾啥。在公共類型或內(nèi)部類型中定義的嵌套類型具有內(nèi)部的自動訪問級別。如果希望公共類型中的嵌套類型公開可用虑凛,則必須將嵌套類型顯式聲明為public碑宴。
子類
可以子類化當前訪問上下文中可以訪問的任何類。子類不能具有比其超類更高的訪問級別 - 例如桑谍,不能編寫內(nèi)部超類的公共子類延柠。
此外,可以重寫在特定訪問上下文中可見的任何類成員(方法锣披,屬性贞间,初始化程序或下標)贿条。
重寫可以使繼承的類成員比其超類版本更易于訪問。在下面的示例中增热,類A是有一個名為someMethod()的file-private方法的公共類整以。類B是A子類,具有更低的“內(nèi)部”訪問級別峻仇。盡管如此公黑,class B提供了一個訪問級別為“internal” 的someMethod()覆蓋,它高于原始someMethod()實現(xiàn):
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {}
}
比超類成員具有更低訪問權(quán)限的子類成員調(diào)用超類成員甚至是有效的摄咆,只要對超類成員的調(diào)用發(fā)生在允許的訪問級別上下文中(即凡蚜,在與超類同一源文件源文件中的文件私有成員調(diào)用,或者與超類在同一模塊中的內(nèi)部成員調(diào)用):
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}
因為超類A和子類B是在同一個源文件中定義的吭从,所以對于B的someMethod()實現(xiàn)調(diào)用super.someMethod()是有效的朝蜘。
常量,變量涩金,屬性和下標
常量谱醇,變量或?qū)傩圆荒鼙绕漕愋透_。例如步做,編寫具有私有類型的公共屬性是無效的副渴。類似地,下標不能比其索引類型或返回類型更公開辆床。
如果使用標記私有類型常量佳晶,變量,屬性或下標讼载,則常量轿秧,變量,屬性或下標也必須標記為private:
private var privateInstance = SomePrivateClass()
Getters和Setters
常量咨堤,變量菇篡,屬性和下標的getter和setter自動獲得與它們所屬的常量,變量一喘,屬性或下標相同的訪問級別驱还。
可以為setter提供比其對應的getter 更低的訪問級別,以限制該變量凸克,屬性或下標的讀寫范圍议蟆。通過在var或subscript引導前編寫fileprivate(set),private(set)或internal(set)分配更低訪問級別萎战。
注意
此規(guī)則適用于存儲的屬性以及計算的屬性咐容。即使沒有為存儲的屬性編寫顯式的getter和setter,Swift仍然會合成一個隱式的getter和setter蚂维,以便提供對存儲屬性的后備存儲的訪問戳粒。使用fileprivate(set), private(set)和internal(set)以與計算屬性中的顯式setter完全相同的方式更改此合成setter的訪問級別路狮。
下面的示例定義了一個名為TrackedString的結(jié)構(gòu),它跟蹤字符串屬性被修改的次數(shù):
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits += 1
}
}
}
該TrackedString結(jié)構(gòu)定義了一個名為value的字符串存儲屬性蔚约,其初始值為""(空字符串)奄妨。該結(jié)構(gòu)還定義了一個名為numberOfEdits的整數(shù)存儲屬性,用于跟蹤value修改的次數(shù)苹祟。此修改跟蹤是通過value屬性上的屬性觀察器didSet實現(xiàn)的砸抛,每次將value屬性設置為新值時,屬性觀察器都會遞增numberOfEdits苔咪。
在TrackedString結(jié)構(gòu)和value屬性不提供明確的訪問級別的修改锰悼,所以他們都收到默認的內(nèi)部訪問級別。但是团赏,numberOfEdits屬性的訪問級別標有一個private(set)修飾符,表示該屬性的getter仍然具有內(nèi)部的默認訪問級別耐薯,但該屬性只能從作為TrackedString結(jié)構(gòu)一部分的代碼中設置舔清。這樣TrackedString可以在內(nèi)部修改numberOfEdits屬性,但在屬性在結(jié)構(gòu)定義之外使用時曲初,可以將屬性顯示為只讀屬性体谒。
如果創(chuàng)建一個TrackedString實例并多次修改其字符串值,則可以看到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)")
/*
打印結(jié)果:
The number of edits is 3
*/
雖然可以從另一個源文件中查詢numberOfEdits屬性的當前值臼婆,但無法從其他源文件修改該屬性抒痒。此限制可保護TrackedString編輯跟蹤功能的實現(xiàn)細節(jié),同時仍可方便地訪問該功能的某個方面颁褂。
請注意故响,如果需要,可以為getter和setter分配顯式訪問級別颁独。下面的示例顯示了TrackedString結(jié)構(gòu)的一個版本彩届,其中結(jié)構(gòu)的顯式訪問級別為public。因此誓酒,結(jié)構(gòu)的成員(包括numberOfEdits屬性)默認具有內(nèi)部訪問級別樟蠕。可以通過組合public和private(set)訪問級別修飾符使結(jié)構(gòu)的numberOfEdits屬性的getter為public靠柑,其屬性setter為private :
public struct TrackedString {
public private(set) var numberOfEdits = 0
public var value: String = "" {
didSet {
numberOfEdits += 1
}
}
public init() {}
}
初始化
可以為initializers分配小于或等于它們初始化類型的訪問級別寨辩。唯一的例外是必需的initializers(在必需的initializers中定義)。必需的initializers必須具有與其所屬類相同的訪問級別歼冰。
與函數(shù)和方法參數(shù)一樣靡狞,初始化程序的參數(shù)類型不能比initializers自己的訪問級別更私密。
默認initializers
如在默認initializers中描述停巷,Swift為任何結(jié)構(gòu)或所有屬性提供缺省值并且本身不提供一個initializers的類自動提供了一個默認initializers而無需任何參數(shù)耍攘。
默認initializers具有與其初始化類型相同的訪問級別榕栏,除非該類型定義為public。對于定義為public的類型蕾各,默認initializers被視為內(nèi)部扒磁。如果希望在另一個模塊中使用無參數(shù)初始化程序時可以初始化公共類型,則必須自己明確地提供公共無參數(shù)初始化程序作為類型定義的一部分式曲。
結(jié)構(gòu)類型的默認initializers
如果結(jié)構(gòu)的任何存儲屬性是私有的妨托,則結(jié)構(gòu)類型的默認initializers將被視為私有。同樣吝羞,如果結(jié)構(gòu)的任何存儲屬性是文件專用的兰伤,則initializers是文件專用的。否則钧排,initializers具有內(nèi)部訪問級別敦腔。
與上面的默認任何一樣,如果希望在另一個模塊中使用initializers時可以初始化公共結(jié)構(gòu)類型恨溜,則必須提供公共成員初始化程序作為類型定義的一部分符衔。
協(xié)議
如果要為協(xié)議類型分配顯式訪問級別,請在定義協(xié)議時執(zhí)行此操作糟袁。這使可以創(chuàng)建只能在特定訪問上下文中遵守的協(xié)議判族。
協(xié)議定義中每個需求的訪問級別自動設置為與協(xié)議相同的訪問級別。不能將協(xié)議要求設置為與其支持的協(xié)議不同的訪問級別项戴。這可確保在采用該協(xié)議的任何類型上都可以看到所有協(xié)議的要求形帮。
注意
如果定義公共協(xié)議,則協(xié)議的要求在實施時需要公共訪問級別周叮。此行為與其他類型不同辩撑,其中公共類型定義意味著類型成員的內(nèi)部訪問級別。
協(xié)議繼承
如果定義從現(xiàn)有協(xié)議繼承的新協(xié)議则吟,則新協(xié)議最多可以具有與其繼承的協(xié)議相同的訪問級別槐臀。例如,無法編寫繼承內(nèi)部協(xié)議的公共協(xié)議氓仲。
遵守協(xié)議
類型可以遵守訪問級別低于類型本身的協(xié)議水慨。例如,可以定義在其他模塊中可以使用的公共類型敬扛,但遵守內(nèi)部協(xié)議的只能在內(nèi)部協(xié)議定義的模塊中使用晰洒。
遵守特定協(xié)議的類型上下文是類型訪問級別和協(xié)議訪問級別的最小值。如果類型是公共類型啥箭,但它符合的協(xié)議是內(nèi)部類型谍珊,則遵守該協(xié)議的類型也是內(nèi)部的。
在編寫類型的擴展以遵守協(xié)議時急侥,必須確保每個遵守協(xié)議要求的類型實現(xiàn)至少具有與該協(xié)議類型一致的訪問級別砌滞。例如侮邀,如果公共類型遵守內(nèi)部協(xié)議,則每個遵守協(xié)議要求的類型實現(xiàn)必須至少是“內(nèi)部”的贝润。
注意
在Swift中绊茧,與Objective-C一樣,遵守協(xié)議是全局的 - 類型不可能在同一程序中以兩種不同的方式遵守協(xié)議打掘。
擴展
可以在類华畏,結(jié)構(gòu)或枚舉可用的任何訪問上下文中擴展類,結(jié)構(gòu)或枚舉尊蚁。擴展中添加的任何類型成員具有與要擴展的原始類型中聲明的類型成員相同的默認訪問級別亡笑。如果擴展公共或內(nèi)部類型,則添加的任何新類型成員都具有內(nèi)部的默認訪問級別横朋。如果擴展文件專用類型仑乌,則添加的任何新類型成員都具有文件專用的默認訪問級別。如果擴展私有類型叶撒,則添加的任何新類型成員都具有私有的默認訪問級別绝骚。
或者,可以使用顯式訪問級別修飾符標記擴展名(例如祠够,private extension),以便為擴展名中定義的所有成員設置新的默認訪問級別粪牲。仍可以在單個類型成員的擴展中覆蓋此新默認值古瓤。
如果使用擴展來添加遵守協(xié)議,則無法為擴展提供顯式訪問級別修飾符腺阳。相反落君,協(xié)議自身的訪問級別用于為擴展中的每個協(xié)議要求實現(xiàn)提供默認訪問級別。
擴展中的私有成員
與擴展的類亭引,結(jié)構(gòu)或枚舉位于同一文件中的擴展的行為就像擴展中的代碼已寫為原始類型聲明的一部分一樣绎速。因此,可以:
- 在原始聲明中聲明私有成員焙蚓,并從同一文件中的擴展訪問該成員纹冤。
- 在一個擴展中聲明一個私有成員,并從同一文件中的另一個擴展訪問該成員购公。
- 在擴展中聲明私有成員萌京,并從同一文件中的原始聲明中訪問該成員。
此行為意味著無論的類型是否具有私有實體可以使用擴展以相同的方式組織代碼宏浩。例如知残,給出以下簡單協(xié)議:
protocol SomeProtocol {
func doSomething()
}
可以使用擴展來添加遵守的協(xié)議,如下所示:
struct SomeStruct {
private var privateVariable = 12
}
extension SomeStruct: SomeProtocol {
func doSomething() {
print(privateVariable)
}
}
泛型
泛型類型或泛型函數(shù)的訪問級別是泛型類型或函數(shù)本身的訪問級別以及對其類型參數(shù)的任何類型約束的訪問級別的最小值比庄。
類型別名
為了訪問控制的目的求妹,定義的任何類型別名都被視為不同類型乏盐。類型別名的訪問級別可以小于或等于其別名類型的訪問級別。例如制恍,私有類型別名可以為私有父能,文件私有,內(nèi)部吧趣,公共或開放類型設置別名法竞,但公共類型別名不能為內(nèi)部,文件私有或私有類別設置別名强挫。
注意
此規(guī)則也適用于用于滿足遵守協(xié)議的關(guān)聯(lián)類型的類型別名岔霸。