一,概述
訪問控制限制其他源文件和模塊對你的代碼的訪問。這個特性允許你隱藏代碼的實現細節,並指定一個偏好的接口讓其他代碼可以訪問和使用。
你可以給特定的單個類型 (類,結構體和枚舉)設置訪問級別,比如說屬性、方法、初始化器以及屬於那些類型的下標。協議可以限制在一定的范圍內使用,就像全局常量,變量,函數那樣。
除了提供各種級別的訪問控制,Swift 為典型場景提供默認的訪問級別,減少了顯式指定訪問控制級別的需求。 事實上,如果你編寫單目標應用程序,你可能根本不需要顯式指定訪問控制級別。
注意:簡潔起見,代碼中可以設置訪問級別的部分(屬性,類型,函數等)在下面的章節稱為“實體”。
二,兩個概念(模塊和源文件)
Swift 的訪問控制模型基於模塊和源文件的概念。
- 模塊:單一的代碼分配單元。指的是
Framework
或App bundle
。在 Swift 中,可以用import
關鍵字引入自己的工程。在 Swift 中,
Framework
或App bundle
被作為模塊處理。如果你是為了實現某個通用的功能,或者是為了封裝一些常用方法而將代碼打包成Framework
,這個Framework
在 Swift 中就被稱為模塊。不論它被引入到某個 App 工程或者其他的Framework
,它里面的一切(屬性、函數等)都屬於這個模塊。
- 源文件:一個模塊中的單個 Swift 源代碼文件(指的是 Swift 中的
Swift File
,就是編寫 Swift 代碼的文件)。它通常屬於一個模塊。雖然通常在單獨源文件中定義單個類型,但是一個源文件可以包含多個類型,在
類
中又包含函數
、屬性
等類型。
三,五種訪問級別(以下從高到低)
Swift 提供了五種不同的訪問級別,分別是:open
、public
、internal
、fileprivate
和 private
,訪問權限依次由高到低。
-
open
: 可以在定義的模塊中使用,也可在其他的模塊中使用,(模塊相當項目的target)其他模塊也可繼承、重寫。open
只能用在類、類成員上。打包靜態庫給其他項目使用時就需使用open
修飾 -
public
:可以在定義的模塊中使用,也可在其他的模塊中使用,但是其他模塊不能繼承、重寫。 -
internal
: 只允許在定義的模塊中訪問, 不允許在其他模塊中方法。通常用來隱藏文件內部實現細節。 -
fileprivate
: 只允許在定義的源文件中訪問(只能在.swift文件中使用) -
private
: 只允許在定義的封閉聲明中訪問(例如:類中)
注意:不加任何修飾時默認是internal
。通常在設計接口時,如果只在應用程序或者框架內使用可以定義為internal
級別。
四,使用原則
不能用一個更低訪問級別的實體來定義當前實體。
-
比如,以
internal
、fileprivate
或private
修飾的類型,不能定義一個public
變量,因為在public
變量使用的地方,這個類型均不可用。錯誤原因:
Property cannot be declared public because its type uses a private type
-
比如,函數的訪問級別不能高於其參數類型和返回值類型,因為這種函數的使用場景,其組成部分的類型不可用。如:
錯誤原因:
sayHI:
Function must be declared private or fileprivate because its parameter uses a private type
theWinner:
Function must be declared private or fileprivate because its result uses a private type
五,默認級別
-
若無顯式聲明,代碼中所有實體的訪問級別默認為
internal
。 -
如果定義一個
public
或internal
類型,其成員的訪問級別默認為internal
; -
如果定義一個
fileprivate
或private
類型,其成員的訪問級別默認為fileprivate
或private
。
六,訪問控制
-
自定類型
-
如果你想給自定類型指明訪問級別,那就在定義時指明。只要訪問級別允許,新類型就可以被使用。例如,你定義了一個 file-private 的類,它就只能在定義文件中被當作屬性類型、函數參數或返回類型使用。
-
類型的訪問控制級別也會影響它的成員的默認訪問級別(它的屬性,方法,初始化方法,下標)。如果你將類型定義為 private 或 file private 級別,那么它的成員的默認訪問級別也會是 private 或file private。如果你將類型定義為 internal 或 public級別(或直接使用默認級別而不顯式指出),那么它的成員的默認訪問級別會是 internal 。
-
注意: public 的類型默認擁有 internal 級別的成員,而不是 public。如果你想讓其中的一個類型成員是 public 的,你必須按實示例代碼指明。這個要求確保類型的面向公眾的 API 是你選擇的,並且可以避免將類型的內部工作細節公開成 API 的失誤。
///public 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 } ///Internal 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 fileprivate class SomeFilePrivateClass { // explicitly file-private class func someFilePrivateMethod() {} // implicitly file-private class member private func somePrivateMethod() {} // explicitly private class member } ///private private class SomePrivateClass { // explicitly private class func somePrivateMethod() {} // implicitly private class member }
-
-
元組類型
-
元組類型的訪問級別是所有類型里最嚴格的。例如,如果你將兩個不同類型的元素組成一個元組,一個元素的訪問級別是 internal,另一個是 private,那么這個元組類型是 private 級別的。即元組類型的訪問級別,與其所有組成類型中訪問級別最低者相同。
-
注意: 元組類型不像類、結構體、枚舉和函數那樣有一個單獨的定義。元組類型的訪問級別會在使用的時候被自動推斷出來,不需要顯式指明。
-
-
函數類型
-
函數類型的訪問級別由函數成員類型和返回類型中的最嚴格訪問級別決定。如果函數的計算訪問級別與上下文環境默認級別不匹配,你必須在函數定義時顯式指出。
下面的例子定義了一個稱為 someFunction() 的全局函數,而沒有指明它的訪問級別。你或許以為它會是默認的 “internal” 級別,但事實不是這樣。這樣的 someFuniction()是無法通過編譯的:func someFunction() -> (SomeInternalClass, SomePrivateClass) {// function implementation goes here}這個函數的返回類型是一個由兩個在自定義類型里定義的類組成的元組。其中一個類是 “internal” 級別的,另一個是 “private”。因此,這個元組的訪問級別是“private”(元組成員的最嚴級別,或者說級別最低的)。
由於返回類型是 private 級別的,你必須使用 private 修飾符使其合法:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {// function implementation goes here}使用 public 或 internal 標注 someFunction() 的定義是無效的,使用默認的 internal 也是無效的,7的函數可能無法訪問到 private 的函數返回值。
-
-
枚舉類型
-
枚舉中的獨立成員自動使用該枚舉類型的訪問級別。你不能給獨立的成員指明一個不同的訪問級別。
在下面的例子中 CompassPoint 有一個指明的“public”級別。里面的成員 north , south , east , 和 west
因此是“public”:
public enum CompassPoint {case northcase southcase eastcase west}
-
-
-
原始值和關聯值
枚舉定義中的原始值和關聯值使用的類型必須有一個不低於枚舉的訪問級別。例如,你不能使用一個 private 類型作為一個 internal 級別的枚舉類型中的原始值類型。
-
例如:
錯誤原因:Enum case in an internal enum uses a private type
-
-
嵌套類型
private 級別的類型中定義的嵌套類型自動為 private 級別。fileprivate 級別的類型中定義的嵌套類型自動為 fileprivate 級別。public 或 internal 級別的類型中定義的嵌套類型自動為 internal 級別。如果你想讓嵌套類型是 public 級別的,你必須將其顯式指明為 public。
-
子類
你可以繼承任何類只要是在當前可以訪問的上下文環境中。但子類不能高於父類的訪問級別,例如,你不能寫一個 internal 父類的 public 子類。
而且,你可以重寫任何類成員(方法,屬性,初始化器或下標),只要是在確定的訪問域中是可見的。
重寫可以讓一個繼承類成員比它的父類中的更容易訪問。在下例中,public 級別的類 A 有一個 fileprivate 級別的 someMethod() 函數。 B 是 A 的子類,有一個降低的“internal”級別。但是,類 B 對 someMethod() 函數進行了重寫即改為“internal”級別,這比 someMethod() 的原本實現級別更高:
public class A { fileprivate func someMethod() {} }
internal class B: A { override internal func someMethod() {} }
子類成員調用父類中比子類更低訪問級別的成員,只要這個調用發生在一個允許的訪問級別上下文中(即對 fileprivate 成員的調用要求父類在同一個源文件中,對 internal 成員的調用要求父類在同一個模塊中):
public class A { fileprivate func someMethod() {} }
internal class B: A { override internal func someMethod() { super.someMethod() } }
因為父類 A 和子類 B 定義在同一個源文件中,那么 B 類可以在 someMethod() 中調用父類的 someMethod() 。
-
-
常量,變量,屬性和下標
常量、變量、屬性不能擁有比它們類型更高的訪問級別。例如,你不能寫一個public 的屬性而它的類型是 private 的。類似的,下標也不能擁有比它的索引類型和返回類型更高的訪問級別。
如果常量、變量、屬性或下標由private類型組成,那么常量、變量、屬性或下標也要被標注為 private :
private var privateInstance = SomePrivateClass()
-
-
Getters 和 Setters
-
常量、變量、屬性和下標的 getter 和 setter 自動接收它們所屬常量、變量、屬性和下標的訪問級別。
-
你可以給 setter 函數一個比相對應 getter 函數更低的訪問級別以限制變量、屬性、下標的讀寫權限。你可以通過在 var 和 subscript 的置入器之前書寫 fileprivate(set) , private(set) , 或 internal(set) 來聲明更低的訪問級別。
-
注意:這個規則應用於存儲屬性和計算屬性。即使你沒有給一個存儲屬性書寫一個明確的 getter 和 setter,Swift 會為你合成一個 getter 和 setter 以訪問到存儲屬性的隱式存儲。使用 fileprivate(set) , private(set) 和 internal(set) 可以改變這個合成的 setter 的訪問級別,同樣也可以改變計算屬性的訪問級別。
-
下面的例子定義了一個稱為 TrackedString 的結構體,它保持追蹤一個字符串屬性的修改次數:
struct TrackedString { private(set) var numberOfEdits = 0 var value: String = "" { didSet { numberOfEdits += 1 } } }
TrackedString 結構體定義了一個可存儲字符串的屬性 value ,它又一個初始值 "" (空字符串)。這個結構圖同樣定義了一個可存儲整數的屬性 numberOfEdits ,它被用於記錄 value 的修改次數。這個記錄由 value 屬性中的didset屬性實現,它會增加 numberOfEdits 的值一旦 value 被設為一個新值。
TrackedString 結構體和 value 屬性都沒有顯式指出訪問級別修飾符,因此它們都遵循默認的 internal 級別。 numberOfEdits 屬性的訪問級別已經標注為 private(set) 以說明這個屬性的 getter 是默認的 internal 級別,但是這個屬性只能被 TrackedString 內的代碼設置。這允許 TrackedString 在內部修改 numberOfEdits 屬性,而且可以展示這個屬性作為一個只讀屬性當在結構體定義之外使用時——包括 TrackedString 的擴展。
如果你創建了一個 TrackedString 的實例並修改了幾次字符串的值,你可以看到 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 屬性的當前值,但你不能從別的源文件中修改該屬性的值。這個限制保護了 TrackedString 編輯追蹤功能的實現細節,並同時為該功能的一個方面提供方便的訪問。
你若有必要也可以顯式指明 getter 和 setter方法。下面的例子提供了一個定義為 public 級別的 TrackedString 結構體。結構體成員(包括 numberOfEdits 屬性)因此有一個默認的 internal 級別。你可以設置 numberOfEdits 屬性的getter方法為 public,setter 方法為 private 級別,通過結合 public 和 private(set) 訪問級別修飾符:
public struct TrackedString { public private(set) var numberOfEdits = 0 public var value: String = "" { didSet { numberOfEdits += 1 } } public init() {} }
-
-
-
初始化器
我們可以給自定義初始化方法設置一個低於或等於它的所屬的類的訪問級別。唯一的例外是必要初始化器(定義在必要初始化器)。必要初始化器必須和它所屬類的訪問級別一致。
就像函數和方法的參數一樣,初始化器的參數類型不能比初始化方法的訪問級別還低。
默認初始化器
正如默認初始化器中描述的那樣,Swift 自動為任何結構體和類提供一個無參數的默認初始化方法,以給它的屬性提供默認值但不會提供給初始化器自身。
默認初始化方法與所屬類的訪問級別一致,除非該類型定義為 public 。如果一個類定義為 public ,那么默認初始化方法為 internal 級別。如果你想一個 public 類可以被一個無參初始化器初始化當在另一個模塊中使用時,你必須顯式提供一個 public 的無參初始化方法。
-
-
結構體的默認成員初始化器
如果結構體的存儲屬性時 private 的,那么它的默認成員初始化方法就是 private 級別。如果結構體的存儲屬性時 file private 的,那么它的默認成員初始化方法就是 file private 級別。否則就是默認的 internal 級別。正如以上默認初始化的描述,如果你想在另一個模塊中使用結構體的成員初始化方法,你必須提供在定義中提供一個 public 的成員初始化方法。
-
-
協議
如果你想給一個協議類型分配一個顯式的訪問級別,那就在定義時指明。這讓你創建的協議可以在一個明確的訪問上下文中被接受。
協議定義中的每一個要求的訪問級別都自動設為與該協議相同。你不能將一個協議要求的訪問級別設為與協議不同。這保證協議的所有要求都能被接受該協議的類型所見。
注意如果你定義了一個 public 的協議,該協議的規定要求在被實現時擁有一個 public 的訪問級別。這個行為不同於其他類型,一個 public 的類型的成員時 internal 訪問級別。
-
-
協議繼承
如果你定義了一個繼承已有協議的協議,這個新協議最高與它繼承的協議訪問級別一致。例如你不能寫一個 public 的協議繼承一個 internal 的協議。
-
-
-
協議遵循
類型可以遵循更低訪問級別的協議。例如,你可以定義一個可在其他模塊使用的 public 類型,但它就只能在定義模塊中使用如果遵循一個 internal 的協議。
遵循了協議的類的訪問級別取這個協議和該類的訪問級別的最小者。如果這個類型是 public 級別的,它所遵循的協議是 internal 級別,這個類型就是 internal 級別的。
當你寫或是擴張一個類型以遵循協議時,你必須確保該類按協議要求的實現方法與該協議的訪問級別一致。例如,一個 public 的類遵循一個 internal 協議,該類的方法實現至少是 “internal” 的。
-
注意
在 Swift 和 Objective-C 中協議遵循是全局的——一個類不可能在一個程序中用不同方法遵循一個協議。
-
-
擴展
你可以在任何可訪問的上下文環境中對類、結構體、或枚舉進行擴展。在擴展中添加的任何類型成員都有着被擴展類型相同的訪問權限。如果你擴展一個公開或者內部類型,你添加的任何新類型成員都擁有默認的內部訪問權限。如果你擴展一個文件內私有的類型,你添加的任何新類型成員都擁有默認的私有訪問權限。如果你擴展一個私有類型,你添加的任何新類型成員都擁有默認的私有訪問權限。
或者,你可以顯式標注擴展的訪問級別(例如, private extension )已給擴展中的成員設置新的默認訪問級別。這個默認同樣可以在擴展中為單個類型成員重寫。
你不能給用於協議遵循的擴展顯式標注訪問權限修飾符。相反,在擴展中使用協議自身的訪問權限作為協議實現的默認訪問權限。
-
擴展中的私有成員
在同一文件中的擴展比如類、結構體或者枚舉,可以寫成類似多個部分的類型聲明。你可以:
- 在原本的聲明中聲明一個私有成員,然后在同一文件的擴展中訪問它;
- 在擴展中聲明一個私有成員,然后在同一文件的其他擴展中訪問它;
- 在擴展中聲明一個私有成員,然后在同一文件的原本聲明中訪問它。
這樣的行為意味着你可以和組織代碼一樣使用擴展,無論你的類型是否擁有私有成員。比如說,假設下面這樣的簡單協議:
protocol SomeProtocol { func doSomething() }
你可以使用擴展來添加協議遵循,比如這樣:
struct SomeStruct { private var privateVariable = 12 } extension SomeStruct: SomeProtocol { func doSomething() { print(privateVariable) } }
-
-
泛型
泛指類型和泛指函數的訪問級別取泛指類型或函數以及泛型類型參數的訪問級別的最小值。
-
類型別名
為實現訪問控制,你定義的任何類型別名,都被視為與原類型不同的類型。類型別名的訪問級別不高於原類型。規則同樣適用於用於滿足協議遵守的關聯類型的類型別名。
上圖中,
Phone
類型別名的訪問級別public
高於Object
類型的internal
,報錯;Wine
類型別名的訪問級別private
低於Object
類型的internal
,正常;Winery
遵守Factory
協議,關聯類型別名Product
的訪問級別internal
高於Wine
類型別名的private
,報錯。