【Swift 3.1】24 -訪問權(quán)限 (Access Control)
自從蘋果2014年發(fā)布Swift,到現(xiàn)在已經(jīng)兩年多了,而Swift也來到了3.1版本。去年利用工作之余,共花了兩個多月的時(shí)間把官方的Swift編程指南看完。現(xiàn)在整理一下筆記,回顧一下以前的知識。有需要的同學(xué)可以去看官方文檔>>。
訪問權(quán)限可以限制我們的部分代碼被其他文件或模塊訪問。這可以隱藏有些代碼的實(shí)現(xiàn)細(xì)節(jié)。我們可以指定類型(如類、結(jié)構(gòu)和枚舉)的訪問權(quán)限,也可以指定屬性、方法、初始化器和下標(biāo)的訪問權(quán)限。
Swift也提供了默認(rèn)的訪問權(quán)限來減少寫明訪問權(quán)限的需要。
模塊和源文件 (Modules and Source Files)
Swift的訪問控制模型是基于模塊和源文件的概念。
一個模塊是一個代碼分配單元,一個框架或應(yīng)用就是一個代碼單元,可以使用import
關(guān)鍵字被其他模塊導(dǎo)入。
在Swift中,在Xcode的每個構(gòu)建版本(例如一個應(yīng)用程序包或者框架)都被認(rèn)為是一個分離的模塊。
一個源文件是一個模塊里面的代碼文件(實(shí)際上就是應(yīng)用或框架里的一個文件)。雖然通常來說把每個獨(dú)立的類型寫在不同的源文件中,但是一個源文件可以包含多個類型、方法等等。
注意:和訪問權(quán)限相關(guān)的屬性、類型和方法等等都統(tǒng)稱為實(shí)體(entities)。
訪問級別 (Access Levels)
Swift提供了5個訪問級別,這些訪問級別與源文件和模塊有關(guān):
-
open
和public
修飾的實(shí)體可以被模塊內(nèi)的任何文件訪問,也可以被導(dǎo)入了這個模塊的另一個模塊的文件使用。當(dāng)我們定義框架的公開接口時(shí),通常使用open
或者pubilc
。至于這兩者的區(qū)別,下面會講到。 -
internal
修飾的實(shí)體可以被模塊內(nèi)的任何文件訪問,模塊之外的其他文件不能訪問。當(dāng)定義應(yīng)用或者模塊內(nèi)的結(jié)構(gòu)時(shí),通常使用internal
。 -
fileprivate
修飾的實(shí)體可以在這個文件內(nèi)被訪問。當(dāng)實(shí)現(xiàn)細(xì)節(jié)只在文件內(nèi)使用時(shí),通常使用fileprivate
對其他文件隱藏實(shí)現(xiàn)細(xì)節(jié)。 -
private
修飾的實(shí)體只能被實(shí)體所在的聲明內(nèi)部訪問。當(dāng)實(shí)現(xiàn)細(xì)節(jié)只在聲明內(nèi)部使用時(shí),使用private
。
open
訪問權(quán)限是最高的,而private
訪問權(quán)限是最低的。
open
訪問權(quán)限只適用于class和class的成員,與public
的不同在于:
-
public
修飾的class,或被其他更嚴(yán)格的訪問級別修飾,這個class只能在當(dāng)前模塊內(nèi)被繼承。 -
public
修飾的class成員,或被其他更嚴(yán)格的訪問級別修飾,這些成員只能被當(dāng)前模塊內(nèi)的子類重寫。 -
open
修飾的class,能在當(dāng)前模塊內(nèi)被繼承,也可以在導(dǎo)入了這個模塊的另一個模塊內(nèi)被繼承。 -
open
修飾的class成員,能被當(dāng)前模塊內(nèi)的子類重寫,也可以被導(dǎo)入了這個模塊的另一個模塊內(nèi)的子類重寫。
訪問級別指導(dǎo)原則 (Guiding Principle of Access Levels)
Swift的訪問級別遵循一個總的原則:一個實(shí)體不能定義在訪問權(quán)限比它更低的實(shí)體里面。例如:
- 一個
public
變量不能定義在internal
、fileprivate
或者private
修飾的類型中。 - 一個方法的訪問權(quán)限不能高于他們參數(shù)和返回值類型
默認(rèn)訪問權(quán)限 (Default Access Levels)
如果我們沒有明確指定實(shí)體的權(quán)限,代碼中的所有實(shí)體都有一個默認(rèn)的權(quán)限internal
。
單目標(biāo)應(yīng)用的訪問級別 (Access Levels for Single-Target Apps)
當(dāng)我們編寫一個單目標(biāo)應(yīng)用時(shí),我們不必讓外部訪問應(yīng)用的模塊。默認(rèn)的internal
權(quán)限都已經(jīng)滿足要求。所以我們不必自定義訪問級別。但是,有是有我們需要使用fileprivate
或者private
來隱藏一些功能實(shí)現(xiàn)細(xì)節(jié)。
框架的訪問級別 (Access Levels for Frameworks)
當(dāng)我們在開發(fā)一個框架時(shí),使用open
或者public
來標(biāo)記那些想要公開的API,其他模塊導(dǎo)入這個框架就可以訪問公開的API。
單元測試目標(biāo)的訪問級別 (Access Levels for Unit Test Targets)
當(dāng)使用單元測試目標(biāo)編寫應(yīng)用時(shí),為了測試,應(yīng)用的代碼需要向那個模塊公開。默認(rèn)情況下,只有open
和public
修飾的實(shí)體才可以被其他模塊訪問。然而,如果我們在導(dǎo)入產(chǎn)品模塊時(shí),使用@testable
屬性標(biāo)記,單元測試目標(biāo)可以訪問internal
修飾的實(shí)體。
訪問權(quán)限語法 (Access Control Syntax)
各個訪問級別的語法如下:
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() {}
下面兩個實(shí)體的訪問權(quán)限默認(rèn)是internal
:
class SomeInternalClass {} // implicitly internal
let someInternalConstant = 0 // implicitly internal
自定義類型 (Custom Types)
一個類型的訪問權(quán)限會影響類型成員(如類型的屬性、方法、初始化器和下標(biāo))的默認(rèn)權(quán)限。如果類型的訪問權(quán)限為private
或者fileprivate
,那么他的成員的默認(rèn)權(quán)限是private
或者fileprivate
;如果類型的權(quán)限是internal
或者public
,那么他的成員的默認(rèn)權(quán)限是internal
。
注意:一個public
類型,他的成員默認(rèn)是internal
,如果想讓成員的權(quán)限也是public
,我們必須明確寫出。
public class SomePublicClass { // class明確標(biāo)記為public
public var somePublicProperty = 0 // class成員明確標(biāo)記為public
var someInternalProperty = 0 // class成員默認(rèn)是internal
fileprivate func someFilePrivateMethod() {} // class成員明確標(biāo)記為fileprivate
private func somePrivateMethod() {} // class成員明確標(biāo)記為private
}
class SomeInternalClass { // class默認(rèn)是internal
var someInternalProperty = 0 // class成員默認(rèn)是internal
fileprivate func someFilePrivateMethod() {} // class成員明確標(biāo)記為fileprivate
private func somePrivateMethod() {} // class成員明確標(biāo)記為private
}
fileprivate class SomeFilePrivateClass { // class明確標(biāo)記為fileprivate
func someFilePrivateMethod() {} // class成員默認(rèn)是fileprivate
private func somePrivateMethod() {} // class成員明確標(biāo)記為private
}
private class SomePrivateClass { // class明確標(biāo)記為private
func somePrivateMethod() {} // class成員默認(rèn)是private
}
多元組類型 (Tuple Types)
多元組的訪問權(quán)限是以多元組內(nèi)元素訪問權(quán)限最低的為準(zhǔn)。例如,一個元素的權(quán)限是internal
,另一個是private
,那么這個多元組的訪問權(quán)限是private
。
方法類型 (Function Types)
方法的訪問權(quán)限是以參數(shù)類型和返回值類型的最低權(quán)限為準(zhǔn)。如果參數(shù)類型和返回值類型的最低權(quán)限不能滿足要求,需要我們明確寫出方法的權(quán)限。
下面是一個例子:
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// function implementation goes here
}
實(shí)際上這個例子是不能編譯通過的。方法的返回值是一個多元組,其中一個元素的權(quán)限為internal
,另一個是private
,所以多元組的權(quán)限是private
。因?yàn)榉祷刂档臋?quán)限是private
,所以必須明確寫出方法的權(quán)限是private
:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// function implementation goes here
}
把someFunction()
標(biāo)記為public
或者internal
,或者使用默認(rèn)的internal
權(quán)限都是不行的。
枚舉類型 (Enumeration Types)
枚舉的每一個case的權(quán)限自動地與枚舉的權(quán)限相同,我們不能為單個case定義不同的權(quán)限。
下面的CompassPoint
被標(biāo)記為public
,那么每個case的權(quán)限也是public
。
public enum CompassPoint {
case north
case south
case east
case west
}
原始值和關(guān)聯(lián)類型 (Raw Values and Associated Values)
枚舉的原始值和關(guān)聯(lián)類型的訪問權(quán)限不能低于枚舉的訪問權(quán)限。例如,不能把private
修飾的類型作為internal
修飾的枚舉的關(guān)聯(lián)值類型。
嵌套類型 (Nested Types)
在private
類型里定義的嵌套類型,那么嵌套類型的權(quán)限也是private
;在fileprivate
類型里定義的嵌套類型,那么嵌套類型的權(quán)限也是fileprivate
;在public
或者internal
類型里定義的嵌套類型,那么嵌套類型的權(quán)限也是internal
。如果想要嵌套類型是public
的,那么需要明確使用public
修飾。
子類化 (Subclassing)
子類的權(quán)限不能高于父類的權(quán)限。
下面是一個例子,B
重寫了父類的someMethod()
方法,并且權(quán)限是internal
,高于這個方法在父類的權(quán)限fileprivate
:
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {}
}
子類成員可以調(diào)用父類的比子類成員訪問權(quán)限更低的成員,只要調(diào)用父類成員的位置滿足父類成員的權(quán)限要求(也就是說,在同一個源文件內(nèi)調(diào)用父類的fileprivate
成員,或者是在同一個模塊內(nèi)調(diào)用internal
成員)。
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}
因?yàn)楦割?code>A和子類B
定義在同一個文件,所以在B
的someMethod()
方法中調(diào)用super.someMethod()
是有效的。
常量、變量、屬性和下標(biāo) (Constants, Variables, Properties and Subscripts)
常量、變量或者屬性的權(quán)限不能高于他們的類型。
如果常量、變量、屬性或者下標(biāo)的類型是private
,那么常量、變量、屬性或下標(biāo)也必須用private
修飾:
private var privateInstance = SomePrivateClass()
Getters and Setters
常量、變量、屬性和下標(biāo)的getter和setter方法的權(quán)限,默認(rèn)情況下與常量、變量、屬性和下標(biāo)的權(quán)限相同。
我們可以把setter的權(quán)限設(shè)置成低于對應(yīng)getter的權(quán)限??梢杂?code>fileprivate(set)、private(set)
或者internal(set)
來設(shè)置更低的權(quán)限。
注意:這個規(guī)則適用于存儲屬性和計(jì)算屬性。雖然我們沒有明確寫出存儲屬性的getter和setter,但是Swift會默認(rèn)提供的。
例如下面這個例子:
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits += 1
}
}
}
numberOfEdits
屬性被private(set)
修飾,setter的權(quán)限是private
,所以numberOfEdits
只能在TrackedString
內(nèi)部被修改,對于TrackedString
外部來說,numberOfEdits
是一個只讀屬性。而getter的權(quán)限默認(rèn)是TrackedString
一樣,為internal
。
創(chuàng)建TrackedString
實(shí)例,并修改value
屬性:
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
能被外部訪問,但是不能被修改。
注意:我們可以分別設(shè)置getter和setter的權(quán)限。下面的例子是TrackedString
的另外一個版本,被public
修飾,所以這個結(jié)構(gòu)的成員默認(rèn)是internal
的。我們可以結(jié)合public
和private(set)
使得umberOfEdits
屬性的getter是public
,而setter
是private
。
public struct TrackedString {
public private(set) numberOfEdits = 0
public var value: String = "" {
didSet {
numberOfEdits += 1
}
}
public init() {}
}
初始化器 (Initializers)
自定義初始化器的權(quán)限可以低于或等于類型的權(quán)限,除了required
初始化器。required
初始化器的權(quán)限只能等于類型的初始化器。
對于方法和參數(shù),初始化器的參數(shù)權(quán)限不能低于初始化器的權(quán)限。
默認(rèn)初始化器 (Default Initializers)
默認(rèn)初始化器的權(quán)限與它要初始化的類型權(quán)限相同,除非這個類型的權(quán)限是public
。對于public
的類型,他的默認(rèn)初始化器的權(quán)限是internal
。如果想讓其他模塊能使用public
類型的無參數(shù)的初始化器,我們必須使用public
標(biāo)記無參數(shù)的初始化器。
結(jié)構(gòu)類型的默認(rèn)逐一成員初始化器 (Default Memberwise Initializers for Structure Types)
如果結(jié)構(gòu)的任何存儲屬性都是private
的,那么默認(rèn)逐一成員初始化器也是private
的;如果結(jié)構(gòu)的任何存儲屬性都是fileprivate
的,那么默認(rèn)逐一成員初始化器也是fileprivate
的。否則默認(rèn)逐一成員初始化器是internal
的。
如果想讓默認(rèn)逐一成員初始化器是public
的,我們必須自己定義一個逐一成員初始化器,并用public
修飾。
協(xié)議 (Protocols)
如果想用訪問級別修飾協(xié)議,那么需要在定義協(xié)議的時(shí)候?qū)懮显L問級別。
協(xié)議里的每一個要求的權(quán)限默認(rèn)是與當(dāng)前協(xié)議權(quán)限相同,并且不能設(shè)置成與當(dāng)前協(xié)議不一樣的權(quán)限。
注意:如果定義了一個public
協(xié)議,那么協(xié)議里面的所有要求的權(quán)限也是public
。這個規(guī)則不同于其他類型,如果其他類型定義為public
,那么它的成員默認(rèn)是internal
的。
協(xié)議繼承 (Protocol Inheritance)
如果定義了一個新的協(xié)議繼承于一個已經(jīng)存在的協(xié)議,新的協(xié)議權(quán)限不能高于已經(jīng)存在的協(xié)議。不能定義一個public
協(xié)議繼承于internal
協(xié)議。
協(xié)議一致性 (Protocol Conformance)
一個類型可以遵循權(quán)限比它低的協(xié)議。例如,可以定義一個public
類型,然后遵循一個internal
協(xié)議。
如果一個類型是public
的,遵循于internal
協(xié)議,那么這個協(xié)議在public
類型的實(shí)現(xiàn)也是internal
的。
擴(kuò)展 (Extensions)
在擴(kuò)展中新添加的類型成員的權(quán)限默認(rèn)與原類型定義的成員權(quán)限相同。如果擴(kuò)展了一個public
或者internal
的類型,那么新添加的成員權(quán)限是internal
;如果擴(kuò)展了一個fileprivate
的類型,那么新添加的成員權(quán)限是fileprivate
;如果擴(kuò)展了一個private
的類型,那么新添加的成員權(quán)限是private
。
同樣地,我們可以明確的指定擴(kuò)展的訪問權(quán)限(例如使用private extension),那么新指定的權(quán)限將會替代原類型的默認(rèn)權(quán)限。
使用擴(kuò)展遵循協(xié)議 (Adding Protocol Conformance with an Extension)
如果我們使用擴(kuò)展來遵循協(xié)議,那么我們不能明確指定擴(kuò)展的權(quán)限。擴(kuò)展對協(xié)議要求的實(shí)現(xiàn)的權(quán)限與協(xié)議的權(quán)限相同。
泛型 (Generics)
泛型類型和泛型方法的訪問權(quán)限,是泛型類型或者泛型方法與類型約束的最低權(quán)限。
類型別名 (Type Aliases)
類型別名的權(quán)限可以小于或等于它原本的類型。例如,private
的類型別名可以是private
、fileprivate
、internal
、public
或者open
類型的別名。
注意:這個規(guī)則也適用于關(guān)聯(lián)值類型的類型別名。
第二十四部分完。
如果有錯誤的地方,歡迎指正!謝謝!