談Swift中的訪問控制
訪問控制(Access Control)
訪問控制可以限定其他源文件或模塊中的代碼對你的代碼的訪問級別。這個特性可以讓我們隱藏代碼的一些實現細節,並且可以指定一些代碼和訪問和使用的優先接口。
你可以明確地給單個類型(類、結構體、枚舉)設置訪問級別,也可以給這些類型的屬性、方法、構造器、下標等設置訪問級別。協議也可以被限定在一定的範圍內使用,包括協議裏的全局常量、變量和函數。
Swift 不僅提供了多種不同的訪問級別,還為某些典型場景提供了默認的訪問級別,這樣就不需要我們在每段代碼中都申明顯式訪問級別。其實,如果只是開發一個單一目標的應用程序,我們完全可以不用顯式聲明代碼的訪問級別。
註意:為了簡單起見,對於代碼中可以設置訪問級別的特性(屬性、基本類型、函數等),在下面的章節中我們會稱之為“實體”。
模塊和源文件(Modules and Source Files)
Swift中的訪問控制模型基於模塊和源文件這兩個概念。
模塊指的是獨立的代碼單元,框架或應用程序會作為一個獨立的模塊來構建和發布。在 Swift中,一個模塊可以使用import關鍵字導入另外一個模塊。
在Swift中,Xcode的每個目標(例如框架或應用程序)都被當作獨立的模塊處理。如果你是為了實現某個通用的功能,或者是為了封裝一些常用方法而將代碼打包成獨立的框架,這個框架就是 Swift中的一個模塊。當它被導入到某個應用程序或者其他框架時,框架內容都將屬於這個獨立的模塊。
源文件就是 Swift中的源代碼文件,它通常屬於一個模塊,即一個應用程序或者框架。盡管我們一般會將不同的類型分別定義在不同的源文件中,但是同一個源文件也可以包含多個類型、函數之類的定義。
訪問級別(Access Levels)
Swift 為代碼中的實體提供了五種不同的訪問級別。這些訪問級別不僅與源文件中定義的實體相關,同時也與源文件所屬的模塊相關。
1:開放訪問(open access)和公開訪問(public access)可以訪問同一模塊源文件中的任何實體,在模塊外也可以通過導入該模塊來訪問源文件裏的所有實體。通常情況下,框架中的某個接口可以被任何人使用時,你可以將其設置為開放(Open)或者公開(public)訪問。對於開放(Open)或者公開(public)訪問的區別,後面會講到。
2:內部訪問(internal)可以訪問同一模塊源文件中的任何實體,但是不能從模塊外訪問該模塊源文件中的實體。通常情況下,某個接口只在應用程序或框架內部使用時,你可以將其設置為內部訪問。
3:文件私有訪問(fileprivate)限制實體只能被所定義的文件內部訪問。當需要把這些細節被整個文件使用的時候,使用文件私有訪問隱藏了一些特定功能的實現細節。
4:私有訪問(private)限制實體只能在所定義的作用域內使用。需要把這些細節被整個作用域使用的時候,使用文件私有訪問隱藏了一些特定功能的實現細節。
開放訪問(open access)為最高(限制最少)訪問級別,私有訪問(private access)為最低(限制最多)訪問級別。開放訪問(open access)只作用於類類型和類的成員,它和公開訪問(public access)的區別如下:
1:公開訪問(public access)或者其他更嚴訪問級別的類,只能在它們定義的模塊內部被繼承。
2:公開訪問(public access)或者其他更嚴訪問級別的類成員,只能在它們定義的模塊內部的子類中重寫。
3:開放訪問(open access)的類,可以在它們定義的模塊中被繼承,也可以在引用它們的模塊中被繼承。
4:開放訪問(open access)的類成員,可以在它們定義的模塊中子類中重寫,也可以在引用它們的模塊中的子類重寫。
把一個類標記為開放,顯式地表明,你認為其他模塊中的代碼使用此類作為父類,然後你已經設計好了你的類的代碼了。
訪問級別基本原則(Guiding Pribciple of Access Levels)
Swift 中的訪問級別遵循一個基本原則:不可以在某個實體中定義訪問級別更低(更嚴格)的實體。例如:
1:一個公開訪問級別的變量(public variable),其類型的訪問級別不能是內部(internal),文件私有(fileprivate)或是私有類型(private)的。因為無法保證變量的類型在使用變量的地方可獲取。
2:函數的訪問級別不能高於它的參數類型和返回類型的訪問級別。因為這樣就會出現函數可以在任何地方被訪問,但是它的參數類型和返回類型卻不可以的情況。
關於此原則的各種情況的具體實現,將在下面的細節中體現。
默認訪問級別(Default Access Levels)
如果你不為代碼中的實體顯式指定訪問級別,那麽它們默認為internal級別(有一些例外情況,稍後會進行說明)。因此,在大多數情況下,我們不需要顯式指定實體的訪問級別。
單目標應用程序的訪問級別(Access Levelsfor Single-Target Apps)
當你編寫一個簡單的單目標應用程序時,應用的所有功能都是為該應用服務,而不需要提供給其他應用或者模塊使用,所以我們不需要明確設置訪問級別,使用默認的訪問級別internal即可。但是,你也可以使用文件私有訪問(fileprivate)或私有訪問級別(private),用於隱藏一些功能的實現細節。
框架的訪問級別(Access Levelsfor Frameworks)
當你開發框架時,就需要把一些對外的接口定義為開放(open)訪問或公開(public)訪問級別,以便使用者導入該框架後可以正常使用其功能。這些被你定義為對外的接口,就是這個框架的API。
註意:框架依然會使用默認的內部訪問級別,也可以指定為文件私有訪問或者私有訪問級別。當你想把某個實體作為框架的 API的時候,需顯式為其指定開放訪問或公開訪問級別。
單元測試目標的訪問級別(Access Levelsfor Unit Test Targets)
當你的應用程序包含單元測試目標時,為了測試,測試模塊需要訪問應用程序模塊中的代碼。默認情況下只有開放訪問或公開訪問級別級別的實體才可以被其他模塊訪問。然而,如果在導入應用程序模塊的語句前使用@testable特性,然後在允許測試的編譯設置(Build Options -> Enable Testability)下編譯這個應用程序模塊,單元測試目標就可以訪問應用程序模塊中所有內部級別的實體。
訪問控制語法(Access Control Syntax)
通過修飾符 open,public,internal,fileprivate,private來聲明實體的訪問級別:
public class SomePublicClass{} internal class SomeInternalClass{} fileprivate class SomeFilePrivateClass{} private class SomePrivateClass{} public var somePublicVariable = 0 internal var someInternalConstant = 0 fileprivate func someFilePrivateFuncation(){} private func somePrivateFuncation(){}
除非專門指定,否則實體默認的訪問級別為內部訪問級別,可以查閱默認訪問級別這一節。
這意味著在不使用修飾符顯式聲明訪問級別的情況下,
SomeInternalClass和 someInternalConstant仍然擁有隱式的內部訪問級別:
class SomeInternalClass {} // 隱式內部訪問級別
var someInternalConstant = 0 // 隱式內部訪問級別
自定義類型(Custom Types)
如果想為一個自定義類型指定訪問級別,在定義類型時進行指定即可。新類型只能在它的訪問級別限制範圍內使用。例如,你定義了一個文件私有級別的類,那這個類就只能在定義它的源文件中使用,可以作為屬性類型、函數參數類型或者返回類型,等等。
一個類型的訪問級別也會影響到類型成員(屬性、方法、構造器、下標)的默認訪問級別。如果你將類型指定為私有或者文件私有級別,那麽該類型的所有成員的默認訪問級別也會變成私有或者文件私有級別。如果你將類型指定為公開或者內部訪問級別(或者不明確指定訪問級別,而使用默認的內部訪問級別),那麽該類型的所有成員的默認訪問級別將是內部訪問。
重要:上面提到,一個公開類型(public type)的所有成員的訪問級別默認為內部訪問級別,而不是公開級別。如果你想將某個成員指定為公開訪問級別,那麽你必須顯式指定。這樣做的好處是,在你定義公共接口的時候,可以明確地選擇哪些接口是需要公開的,哪些是內部使用的,避免不小心將內部使用的接口公開。
//顯式公開類 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() {} }
元組類型(Tuple Types)
元組的訪問級別將由元組中訪問級別最嚴格的類型來決定。例如,如果你構建了一個包含兩種不同類型的元組,其中一個類型為內部訪問級別,另一個類型為私有訪問級別,那麽這個元組的訪問級別為私有訪問級別。
註意:元組不同於類、結構體、枚舉、函數那樣有單獨的定義。元組的訪問級別是在它被使用時自動推斷出的,而無法明確指定。
函數類型(Function Types)
函數的訪問級別根據訪問級別最嚴格的參數類型或返回類型的訪問級別來決定。但是,如果這種訪問級別不符合函數定義所在環境的默認訪問級別,那麽就需要明確地指定該函數的訪問級別。
下面的例子定義了一個名為 someFunction()的全局函數,並且沒有明確地指定其訪問級別。也許你會認為該函數應該擁有默認的訪問級別 internal,但事實並非如此。事實上,如果按下面這種寫法,代碼將無法通過編譯:
func someFunction() -> (SomeInternalClass, SomePrivateClass) { // 此處是函數實現部分 }
我們可以看到,這個函數的返回類型是一個元組,該元組中包含兩個自定義的類(可查閱自定義類型)。其中一個類的訪問級別是 internal,另一個的訪問級別是 private,所以根據元組訪問級別的原則,該元組的訪問級別是 private(元組的訪問級別與元組中訪問級別最低的類型一致)。
因為該函數返回類型的訪問級別是 private,所以你必須使用private修飾符,明確指定該函數的訪問級別:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) { // 此處是函數實現部分 }
將該函數指定為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訪問級別。如果在public或者internal級別的類型中定義嵌套類型,那麽該嵌套類型自動擁有internal訪問級別。如果想讓嵌套類型擁有public訪問級別,那麽需要明確指定該嵌套類型的訪問級別。
子類(Subclassing)
子類的訪問級別不得高於父類的訪問級別。例如,父類的訪問級別是 internal,子類的訪問級別就不能是public。
此外,你可以在符合當前訪問級別的條件下重寫任意類成員(方法、屬性、構造器、下標等)。
可以通過重寫為繼承來的類成員提供更高的訪問級別。下面的例子中,類 A的訪問級別是 public,它包含一個方法 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() {} }
我們甚至可以在子類中,用子類成員去訪問訪問級別更低的父類成員,只要這一操作在相應訪問級別的限制範圍內(也就是說,在同一源文件中訪問父類private級別的成員,在同一模塊內訪問父類internal級別的成員):
public class A { private func someMethod() {} } internal class B: A { override internal func someMethod() { super.someMethod() } }
因為父類A和子類B定義在同一個源文件中,所以在子類B可以在重寫的 someMethod()方法中調用super.someMethod()。
常量、變量、屬性、下標(Constants、Variables、Properties、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)可以改變 Setter的訪問級別,這對計算型屬性也同樣適用。
下面的例子中定義了一個名為 TrackedString的結構體,它記錄了 value屬性被修改的次數:
struct TrackedString { private(set) var numberOfEdits = 0 var value: String = "" { didSet { numberOfEdits += 1 } } }
TrackedString結構體定義了一個用於存儲String值的屬性 value,並將初始值設為""(一個空字符串)。該結構體還定義了另一個用於存儲Int值的屬性 numberOfEdits,它用於記錄屬性 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." print("The number of edits is \(stringToEdit.numberOfEdits)") // 打印 “The number of edits is 3”
雖然你可以在其他的源文件中實例化該結構體並且獲取到 numberOfEdits屬性的值,但是你不能對其進行賦值。這一限制保護了該記錄功能的實現細節,同時還提供了方便的訪問方式。
你可以在必要時為 Getter和 Setter顯式指定訪問級別。下面的例子將TrackedString結構體明確指定為了public訪問級別。結構體的成員(包括 numberOfEdits屬性)擁有默認的訪問級別internal。你可以結合public和private(set)修飾符把結構體中的 numberOfEdits屬性的 Getter的訪問級別設置為 public,而 Setter的訪問級別設置為 private:
public struct TrackedString { public private(set) var numberOfEdits = 0 public var value: String = "" { didSet { numberOfEdits += 1 } } public init() {} }
構造器(Initializers)
自定義構造器的訪問級別可以低於或等於其所屬類型的訪問級別。唯一的例外是必要構造器,它的訪問級別必須和所屬類型的訪問級別相同。
如同函數或方法的參數,構造器參數的訪問級別也不能低於構造器本身的訪問級別。
默認構造器(Defalut Initializers)
如默認構造器所述,Swift會為結構體和類提供一個默認的無參數的構造器,只要它們為所有存儲型屬性設置了默認初始值,並且未提供自定義的構造器。
默認構造器的訪問級別與所屬類型的訪問級別相同,除非類型的訪問級別是public。如果一個類型被指定為public級別,那麽默認構造器的訪問級別將為internal。如果你希望一個public級別的類型也能在其他模塊中使用這種無參數的默認構造器,你只能自己提供一個public訪問級別的無參數構造器。
結構體默認的成員逐一構造器(Default Memberwise Initializerfor Structure Types)
如果結構體中任意存儲型屬性的訪問級別為 private,那麽該結構體默認的成員逐一構造器的訪問級別就是private。否則,這種構造器的訪問級別依然是internal。
如同前面提到的默認構造器,如果你希望一個public級別的結構體也能在其他模塊中使用其默認的成員逐一構造器,你依然只能自己提供一個public訪問級別的成員逐一構造器。
協議(Protocols)
如果想為一個協議類型明確地指定訪問級別,在定義協議時指定即可。這將限制該協議只能在適當的訪問級別範圍內被采納。
協議中的每一個要求都具有和該協議相同的訪問級別。你不能將協議中的要求設置為其他訪問級別。這樣才能確保該協議的所有要求對於任意采納者都將可用。
註意:如果你定義了一個public訪問級別的協議,那麽該協議的所有實現也會是public訪問級別。這一點不同於其他類型,例如,當類型是public訪問級別時,其成員的訪問級別卻只是internal。
協議繼承(Protocol Inheritance)
如果定義了一個繼承自其他協議的新協議,那麽新協議擁有的訪問級別最高也只能和被繼承協議的訪問級別相同。例如,你不能將繼承自internal協議的新協議定義為public協議。
協議一致性(Protocol Conformance)
一個類型可以遵守(conform)比自身訪問級別低的協議。例如,你可以定義一個public級別的類型,它可以在其他模塊中使用,同時它也可以遵守一個internal級別的協議,但是只能在該協議所在的模塊中作為符合該協議的類型使用。
遵守了協議的類型的訪問級別取它本身和所采納協議兩者間最低的訪問級別。也就是說如果一個類型是public級別,遵守的協議是internal級別,那麽遵守了這個協議後,該類型作為符合協議的類型時,其訪問級別也是internal。
如果你遵守了協議,那麽實現了協議的所有要求後,你必須確保這些實現的訪問級別不能低於協議的訪問級別。例如,一個public級別的類型,采納了internal級別的協議,那麽協議的實現至少也得是internal級別。
註意:Swift和 Objective-C一樣,協議的一致性是全局的,也就是說,在同一程序中,一個類型不可能用兩種不同的方式實現同一個協議。
擴展(Extensions)
你可以在訪問級別允許的情況下對類、結構體、枚舉進行擴展。擴展成員具有和原始類型成員一致的訪問級別。例如,你擴展了一個public或者internal類型,擴展中的成員具有默認的internal訪問級別,和原始類型中的成員一致。如果你擴展了一個private類型,擴展成員則擁有默認的private訪問級別。
或者,你可以明確指定擴展的訪問級別(例如,private extension),從而給該擴展中的所有成員指定一個新的默認訪問級別。這個新的默認訪問級別仍然可以被單獨指定的訪問級別所覆蓋。
通過擴展添加協議一致性(Adding Protocol Conformance with an Extension)
如果你通過擴展來遵守協議,那麽你就不能顯式指定該擴展的訪問級別了。協議擁有相應的訪問級別,並會為該擴展中所有協議要求的實現提供默認的訪問級別。
泛型(Generics)
泛型類型或泛型函數的訪問級別取決於泛型類型或泛型函數本身的訪問級別,還需結合類型參數的類型約束的訪問級別,根據這些訪問級別中的最低訪問級別來確定。
類型別名(Type Aliases)
你定義的任何類型別名都會被當作不同的類型,以便於進行訪問控制。類型別名的訪問級別不可高於其表示的類型的訪問級別。例如,private級別的類型別名可以作為 private,file-private,internal,public或者open類型的別名,但是public級別的類型別名只能作為public類型的別名,不能作為 internal,file-private,或private類型的別名。
註意:這條規則也適用於為滿足協議一致性而將類型別名用於關聯類型的情況。
---------------------
作者:Longshihua
來源:CSDN
原文:https://blog.csdn.net/longshihua/article/details/64442360
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
談Swift中的訪問控制