Swift4.2語言指南(二十七) 訪問控制
訪問控制限制從其他源文件和模塊中的代碼訪問部分代碼。此功能使您可以隱藏代碼的實現細節,並指定一個首選接口,通過該接口可以訪問和使用該代碼。
您可以為各個類型(類,結構和枚舉)以及屬於這些類型的屬性,方法,初始值設定項和下標分配特定的訪問級別。協議可以限制在某個上下文中,全局常量,變量和函數也可以。
除了提供各種級別的訪問控制外,Swift還通過為典型方案提供默認訪問級別來減少指定顯式訪問控制級別的需求。實際上,如果您正在編寫單目標應用程序,則可能根本不需要指定顯式訪問控制級別。
註意
為簡潔起見,代碼中可應用訪問控制的各個方面(屬性,類型,函數等)在下面的部分中稱為“實體”。
模塊和源文件
Swift的訪問控制模型基於模塊和源文件的概念。
甲模塊是代碼分布框架或應用程序,它是建立和運輸為單個單元,並可以通過與Swift的另一個模塊引入的單個單元import
關鍵字。
Xcode中的每個構建目標(例如應用程序包或框架)都被視為Swift中的單獨模塊。如果您將應用程序代碼的各個方面組合在一起作為一個獨立的框架 - 也許是為了跨多個應用程序封裝和重用該代碼 - 那麽您在該框架中定義的所有內容都將成為單獨模塊的一部分,當它在應用程序中導入和使用時,或者當它在另一個框架中使用時。
甲源文件是一個模塊內的單個夫特源代碼文件(實際上,一個應用程序或框架內的一個單獨的文件)。
訪問級別
Swift為代碼中的實體提供了五種不同的訪問級別。這些訪問級別與定義實體的源文件相關,也與源文件所屬的模塊相關。
- 開放訪問和公共訪問使實體可以在其定義模塊的任何源文件中使用,也可以在來自導入定義模塊的另一個模塊的源文件中使用。在指定框架的公共接口時,通常使用開放或公共訪問。開放和公共訪問之間的區別如下所述。
- 內部訪問使實體可以在其定義模塊的任何源文件中使用,但不能在該模塊之外的任何源文件中使用。在定義應用程序或框架的內部結構時,通常使用內部訪問。
- 文件專用訪問將實體的使用限制在其自己的定義源文件中。
- 私有訪問將實體的使用限制為封閉聲明,以及同一文件中該聲明的擴展。當這些詳細信息僅在單個聲明中使用時,使用私有訪問來隱藏特定功能的實現細節。
開放訪問是最高(限制性最小)的訪問級別,私有訪問是最低(限制性最強)的訪問級別。
開放訪問僅適用於類和類成員,它與公共訪問不同,如下所示:
- 具有公共訪問權限或任何更嚴格的訪問級別的類只能在定義它們的模塊中進行子類化。
- 具有公共訪問權限或任何更嚴格的訪問級別的類成員只能在定義它們的模塊中被子類覆蓋。
- 開放類可以在定義它們的模塊中進行子類化,也可以在導入模塊的任何模塊中進行子類化。
- 開放類成員可以被定義它們的模塊中的子類覆蓋,也可以在導入定義它們的模塊的任何模塊中覆蓋。
將類標記為開放明確表示您已考慮使用該類作為超類的其他模塊的代碼的影響,並且您已相應地設計了類的代碼。
訪問級別的指導原則
Swift中的訪問級別遵循一個總體指導原則:沒有實體可以根據具有較低(更嚴格)訪問級別的另一個實體來定義。
例如:
- 公共變量不能定義為具有內部,文件私有或私有類型,因為在使用公共變量的任何地方都可能無法使用該類型。
- 函數不能具有比其參數類型和返回類型更高的訪問級別,因為該函數可用於其組成類型對周圍代碼不可用的情況。
下文詳細介紹了該指導原則對該語言不同方面的具體影響。
默認訪問級別
如果您自己未指定顯式訪問級別,則代碼中的所有實體(具有一些特定的例外情況,如本章後面所述)都具有內部的默認訪問級別。因此,在許多情況下,您無需在代碼中指定顯式訪問級別。
單目標應用的訪問級別
當您編寫一個簡單的單目標應用程序時,您的應用程序中的代碼通常是自包含在應用程序中的,並且不需要在應用程序模塊外部提供。內部的默認訪問級別已滿足此要求。因此,您無需指定自定義訪問級別。但是,您可能希望將代碼的某些部分標記為私有或私有文件,以便從應用程序模塊中的其他代碼隱藏其實現細節。
框架的訪問級別
在開發框架時,將該框架的面向公眾的界面標記為開放或公共,以便其他模塊(例如導入框架的應用程序)可以查看和訪問該框架。這個面向公眾的接口是框架的應用程序編程接口(或API)。
註意
框架的任何內部實現細節仍然可以使用內部的默認訪問級別,或者如果要將它們隱藏在框架內部代碼的其他部分中,則可以將其標記為私有或文件私有。如果您希望實體成為框架API的一部分,則需要將實體標記為開放或公開。
單元測試目標的訪問級別
當您使用單元測試目標編寫應用程序時,您的應用程序中的代碼需要可供該模塊使用才能進行測試。默認情況下,只有標記為open或public的實體才可供其他模塊訪問。但是,如果您使用該@testable
屬性標記產品模塊的import聲明並且在啟用測試的情況下編譯該產品模塊,則單元測試目標可以訪問任何內部實體。
訪問控制語法
通過放置的一個定義實體的訪問級別open
,public
,internal
,fileprivate
,或private
實體的導入前修飾語:
1 public class SomePublicClass {} 2 internal class SomeInternalClass {} 3 fileprivate class SomeFilePrivateClass {} 4 private class SomePrivateClass {} 5 6 public var somePublicVariable = 0 7 internal let someInternalConstant = 0 8 fileprivate func someFilePrivateFunction() {} 9 private func somePrivateFunction() {}
除非另行指定,否則默認訪問級別為內部,如默認訪問級別中所述。這意味著,SomeInternalClass
和someInternalConstant
能夠在沒有明確的訪問級別的修改寫入,仍會有內部的訪問級別:
1 class SomeInternalClass {} // implicitly internal 2 let someInternalConstant = 0 // implicitly internal
自定義類型
如果要為自定義類型指定顯式訪問級別,請在定義類型時執行此操作。然後可以在其訪問級別允許的任何地方使用新類型。例如,如果定義文件專用類,則該類只能在定義文件專用類的源文件中用作屬性的類型,或者用作函數參數或返回類型。
類型的訪問控制級別還會影響該類型成員的默認訪問級別(其屬性,方法,初始值設定項和下標)。如果將類型的訪問級別定義為私有或文件專用,則其成員的默認訪問級別也將為私有或文件專用。如果將類型的訪問級別定義為內部或公共(或使用內部的默認訪問級別而未明確指定訪問級別),則類型成員的默認訪問級別將是內部的。
重要
公共類型默認具有內部成員,而不是公共成員。如果您希望類型成員是公共的,則必須明確標記它。此要求可確保某個類型的面向公眾的API是您選擇發布的內容,並避免錯誤地將類型的內部工作方式顯示為公共API。
1 public class SomePublicClass { // explicitly public class 2 public var somePublicProperty = 0 // explicitly public class member 3 var someInternalProperty = 0 // implicitly internal class member 4 fileprivate func someFilePrivateMethod() {} // explicitly file-private class member 5 private func somePrivateMethod() {} // explicitly private class member 6 } 7 8 class SomeInternalClass { // implicitly internal class 9 var someInternalProperty = 0 // implicitly internal class member 10 fileprivate func someFilePrivateMethod() {} // explicitly file-private class member 11 private func somePrivateMethod() {} // explicitly private class member 12 } 13 14 fileprivate class SomeFilePrivateClass { // explicitly file-private class 15 func someFilePrivateMethod() {} // implicitly file-private class member 16 private func somePrivateMethod() {} // explicitly private class member 17 } 18 19 private class SomePrivateClass { // explicitly private class 20 func somePrivateMethod() {} // implicitly private class member 21 }
元組類型
元組類型的訪問級別是該元組中使用的所有類型的最嚴格的訪問級別。例如,如果您從兩種不同類型組成一個元組,一個具有內部訪問權限,另一個具有私有訪問權限,則該復合元組類型的訪問級別將是私有的。
註意
元組類型沒有類,結構,枚舉和函數的獨立定義。使用元組類型時會自動推導出元組類型的訪問級別,並且無法明確指定。
功能類型
函數類型的訪問級別計算為函數參數類型和返回類型的最嚴格的訪問級別。如果函數的計算訪問級別與上下文默認值不匹配,則必須明確指定訪問級別作為函數定義的一部分。
下面的示例定義了一個名為的全局函數someFunction()
,但沒有為函數本身提供特定的訪問級別修飾符。您可能希望此函數的默認訪問級別為“internal”,但情況並非如此。實際上,someFunction()
不會編譯如下:
1 func someFunction() -> (SomeInternalClass, SomePrivateClass) { 2 // function implementation goes here 3 }
函數的返回類型是一個元組類型,由上面定義的兩個自定義類組成。其中一個類定義為內部,另一個定義為私有。因此,復合元組類型的整體訪問級別是私有的(元組組成類型的最小訪問級別)。
由於函數的返回類型是私有的,因此必須使用private
函數聲明的修飾符來標記函數的整體訪問級別:
1 private func someFunction() -> (SomeInternalClass, SomePrivateClass) { 2 // function implementation goes here 3 }
someFunction()
使用public
或internal
修飾符標記定義或使用內部的默認設置無效,因為該函數的公共或內部用戶可能沒有對函數返回類型中使用的私有類的適當訪問權限。
枚舉類型
枚舉的各個案例自動獲得與其所屬枚舉相同的訪問級別。您無法為單個枚舉案例指定不同的訪問級別。
在下面的示例中,CompassPoint
枚舉具有顯式的公共訪問級別。枚舉的情況下north
,south
,east
,和west
因此也有公共的訪問級別:
1 public enum CompassPoint { 2 case north 3 case south 4 case east 5 case west 6 }
原始值和相關值
用於枚舉定義中的任何原始值或關聯值的類型必須具有至少與枚舉的訪問級別一樣高的訪問級別。例如,您不能將私有類型用作具有內部訪問級別的枚舉的原始值類型。
嵌套類型
私有類型中定義的嵌套類型具有私有的自動訪問級別。在文件專用類型中定義的嵌套類型具有文件專用的自動訪問級別。在公共類型或內部類型中定義的嵌套類型具有內部的自動訪問級別。如果希望公共類型中的嵌套類型公開可用,則必須將嵌套類型顯式聲明為public。
子類
您可以子類化當前訪問上下文中可以訪問的任何類。子類不能具有比其超類更高的訪問級別 - 例如,您不能編寫內部超類的公共子類。
此外,您可以覆蓋在特定訪問上下文中可見的任何類成員(方法,屬性,初始化程序或下標)。
重寫可以使繼承的類成員比其超類版本更易於訪問。在下面的示例中,class A
是一個名為file-private方法的公共類someMethod()
。Class B
是其子類A
,具有降低的“內部”訪問級別。盡管如此,class B
提供了一個覆蓋someMethod()
訪問級別為“internal” 的覆蓋,它高於原始實現someMethod()
:
1 public class A { 2 fileprivate func someMethod() {} 3 } 4 5 internal class B: A { 6 override internal func someMethod() {} 7 }
它甚至對子類成員調用具有比子類成員更低訪問權限的超類成員是有效的,只要對超類成員的調用發生在允許的訪問級別上下文中(即,在與同一源文件相同的源文件中)用於文件私有成員調用的超類,或者與內部成員調用的超類在同一模塊中):
1 public class A { 2 fileprivate func someMethod() {} 3 } 4 5 internal class B: A { 6 override internal func someMethod() { 7 super.someMethod() 8 } 9 }
因為超類A
和子類B
是在同一個源文件中定義的,所以它對於調用的B
實現是有效的。someMethod()
super.someMethod()
常量,變量,屬性和下標
常量,變量或屬性不能比其類型更公開。例如,編寫具有私有類型的公共屬性是無效的。類似地,下標不能比其索引類型或返回類型更公開。
如果常量,變量,屬性或下標使用私有類型,則常量,變量,屬性或下標也必須標記為private
:
private var privateInstance = SomePrivateClass()
getter和setter
常量,變量,屬性和下標的getter和setter自動獲得與它們所屬的常量,變量,屬性或下標相同的訪問級別。
您可以為setter提供比其對應的getter 更低的訪問級別,以限制該變量,屬性或下標的讀寫範圍。通過編寫分配較低訪問級別fileprivate(set)
,private(set)
或internal(set)
前var
或subscript
引導。
註意
此規則適用於存儲的屬性以及計算的屬性。即使您沒有為存儲的屬性編寫顯式的getter和setter,Swift仍然會合成一個隱式的getter和setter,以便您提供對存儲屬性的後備存儲的訪問。使用fileprivate(set)
,, private(set)
和internal(set)
以與計算屬性中的顯式setter完全相同的方式更改此合成setter的訪問級別。
下面的示例定義了一個名為的結構TrackedString
,它跟蹤字符串屬性被修改的次數:
1 struct TrackedString { 2 private(set) var numberOfEdits = 0 3 var value: String = "" { 4 didSet { 5 numberOfEdits += 1 6 } 7 } 8 }
該TrackedString
結構定義了一個名為的存儲字符串屬性value
,其初始值為""
(空字符串)。該結構還定義了一個名為的存儲整數屬性numberOfEdits
,用於跟蹤value
修改的次數。此修改跟蹤是使用didSet
屬性上的屬性觀察器實現的value
,numberOfEdits
每次將value
屬性設置為新值時,屬性觀察器都會增加。
在TrackedString
結構和value
性能不提供明確的訪問級別的修正,所以他們都收到內部默認的訪問級別。但是,numberOfEdits
屬性的訪問級別使用private(set)
修飾符標記,以指示屬性的getter仍具有內部的默認訪問級別,但該屬性只能從作為TrackedString
結構一部分的代碼中設置。這樣可以TrackedString
在numberOfEdits
內部修改屬性,但在屬性在結構定義之外使用時,可以將屬性顯示為只讀屬性。
如果您創建一個TrackedString
實例並多次修改其字符串值,則可以看到numberOfEdits
屬性值update以匹配修改次數:
1 var stringToEdit = TrackedString() 2 stringToEdit.value = "This string will be tracked." 3 stringToEdit.value += " This edit will increment numberOfEdits." 4 stringToEdit.value += " So will this one." 5 print("The number of edits is \(stringToEdit.numberOfEdits)") 6 // Prints "The number of edits is 3"
雖然您可以numberOfEdits
從另一個源文件中查詢屬性的當前值,但您無法從其他源文件修改該屬性。此限制可保護TrackedString
編輯跟蹤功能的實現細節,同時仍可方便地訪問該功能的某個方面。
請註意,如果需要,您可以為getter和setter分配顯式訪問級別。下面的示例顯示了TrackedString
結構的一個版本,其中結構的顯式訪問級別為public。因此,結構的成員(包括numberOfEdits
屬性)默認具有內部訪問級別。您可以numberOfEdits
通過組合public
和private(set)
訪問級別修飾符使結構的屬性getter為public,其屬性setter為private :
1 public struct TrackedString { 2 public private(set) var numberOfEdits = 0 3 public var value: String = "" { 4 didSet { 5 numberOfEdits += 1 6 } 7 } 8 public init() {} 9 }
初始化器
可以為自定義初始值設定項分配小於或等於它們初始化類型的訪問級別。唯一的例外是必需的初始值設定項(如必需的初始值設定項中所定義)。必需的初始值設定項必須具有與其所屬類相同的訪問級別。
與函數和方法參數一樣,初始化程序的參數類型不能比初始化程序自己的訪問級別更私密。
默認初始化器
如默認初始化程序中所述,Swift會自動為任何結構或基類提供一個默認初始值設定項,為任何結構或基類提供其所有屬性的默認值,並且不提供至少一個初始化程序本身。
默認初始化程序具有與其初始化類型相同的訪問級別,除非該類型定義為public
。對於定義為的類型,public
默認初始值設定項被視為內部。如果希望在另一個模塊中使用無參數初始化程序時可以初始化公共類型,則必須自己顯式提供公共無參數初始化程序作為類型定義的一部分。
結構類型的默認成員初始值設定項
如果結構的任何存儲屬性是私有的,則結構類型的默認成員初始值設定項將被視為私有。同樣,如果任何結構的存儲屬性是文件專用的,則初始化程序是文件專用的。否則,初始化程序具有內部訪問級別。
與上面的默認初始化程序一樣,如果您希望在另一個模塊中使用成員初始化程序時可以初始化公共結構類型,則必須自己提供公共成員初始化程序作為類型定義的一部分。
協議
如果要為協議類型分配顯式訪問級別,請在定義協議時執行此操作。這使您可以創建只能在特定訪問上下文中采用的協議。
協議定義中每個要求的訪問級別自動設置為與協議相同的訪問級別。您不能將協議要求設置為與其支持的協議不同的訪問級別。這可確保在采用該協議的任何類型上都可以看到所有協議的要求。
註意
如果定義公共協議,則協議的要求在實施時需要公共訪問級別。此行為與其他類型不同,其中公共類型定義意味著類型成員的內部訪問級別。
協議繼承
如果定義從現有協議繼承的新協議,則新協議最多可以具有與其繼承的協議相同的訪問級別。例如,您無法編寫從內部協議繼承的公共協議。
協議一致性
類型可以符合具有比類型本身更低的訪問級別的協議。例如,您可以定義可以在其他模塊中使用的公共類型,但其與內部協議的一致性只能在內部協議的定義模塊中使用。
類型符合特定協議的上下文是類型訪問級別和協議訪問級別的最小值。如果類型是公共類型,但它符合的協議是內部類型,則該類型與該協議的一致性也是內部的。
在編寫或擴展類型以符合協議時,必須確保每個協議要求的類型實現至少具有與該協議類型一致的訪問級別。例如,如果公共類型符合內部協議,則每個協議要求的類型實現必須至少為“內部”。
註意
在Swift中,與Objective-C一樣,協議一致性是全局的 - 類型不可能在同一程序中以兩種不同的方式符合協議。
擴展
您可以在類,結構或枚舉可用的任何訪問上下文中擴展類,結構或枚舉。在擴展中添加的任何類型成員都具有與要擴展的原始類型中聲明的類型成員相同的默認訪問級別。如果擴展公共或內部類型,則添加的任何新類型成員都具有內部的默認訪問級別。如果擴展文件專用類型,則添加的任何新類型成員都具有文件專用的默認訪問級別。如果擴展私有類型,則添加的任何新類型成員都具有私有的默認訪問級別。
或者,您可以使用顯式訪問級別修飾符標記擴展名(例如,),以便為擴展名中定義的所有成員設置新的默認訪問級別。仍可以在單個類型成員的擴展中覆蓋此新默認值。private extension
如果您使用該擴展來添加協議一致性,則無法為擴展提供顯式訪問級別修飾符。相反,協議自身的訪問級別用於為擴展中的每個協議要求實現提供默認訪問級別。
擴展中的私有成員
與它們擴展的類,結構或枚舉位於同一文件中的擴展的行為就像擴展中的代碼已寫為原始類型聲明的一部分一樣。因此,您可以:
- 在原始聲明中聲明私有成員,並從同一文件中的擴展名訪問該成員。
- 在一個擴展中聲明一個私有成員,並從同一文件中的另一個擴展訪問該成員。
- 在擴展中聲明私有成員,並從同一文件中的原始聲明中訪問該成員。
此行為意味著您可以以相同的方式使用擴展來組織代碼,無論您的類型是否具有私有實體。例如,給出以下簡單協議:
1 protocol SomeProtocol { 2 func doSomething() 3 }
您可以使用擴展來添加協議一致性,如下所示:
1 struct SomeStruct { 2 private var privateVariable = 12 3 } 4 5 extension SomeStruct: SomeProtocol { 6 func doSomething() { 7 print(privateVariable) 8 } 9 }
泛型
泛型類型或泛型函數的訪問級別是泛型類型或函數本身的訪問級別的最小值,以及對其類型參數的任何類型約束的訪問級別。
鍵入別名
為了訪問控制的目的,您定義的任何類型別名都被視為不同類型。類型別名的訪問級別可以小於或等於其別名類型的訪問級別。例如,私有類型別名可以為私有,文件私有,內部,公共或開放類型設置別名,但公共類型別名不能為內部,文件私有或私有類別設置別名。
註意
此規則也適用於用於滿足協議一致性的關聯類型的類型別名。
Swift4.2語言指南(二十七) 訪問控制