【Swift 3.1】24 -訪問權(quán)限 (Access Control)
自從蘋果2014年發(fā)布Swift,到現(xiàn)在已經(jīng)兩年多了,而Swift也來到了3.1版本翼悴。去年利用工作之余功炮,共花了兩個多月的時間把官方的Swift編程指南看完。現(xiàn)在整理一下筆記饱须,回顧一下以前的知識。有需要的同學(xué)可以去看官方文檔>>台谊。
訪問權(quán)限可以限制我們的部分代碼被其他文件或模塊訪問蓉媳。這可以隱藏有些代碼的實現(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)為是一個分離的模塊。
一個源文件是一個模塊里面的代碼文件(實際上就是應(yīng)用或框架里的一個文件)统舀。雖然通常來說把每個獨立的類型寫在不同的源文件中匆骗,但是一個源文件可以包含多個類型、方法等等誉简。
注意:和訪問權(quán)限相關(guān)的屬性碉就、類型和方法等等都統(tǒng)稱為實體(entities)。
訪問級別 (Access Levels)
Swift提供了5個訪問級別闷串,這些訪問級別與源文件和模塊有關(guān):
-
open
和public
修飾的實體可以被模塊內(nèi)的任何文件訪問铝噩,也可以被導(dǎo)入了這個模塊的另一個模塊的文件使用。當(dāng)我們定義框架的公開接口時窿克,通常使用open
或者pubilc
骏庸。至于這兩者的區(qū)別,下面會講到年叮。 -
internal
修飾的實體可以被模塊內(nèi)的任何文件訪問具被,模塊之外的其他文件不能訪問。當(dāng)定義應(yīng)用或者模塊內(nèi)的結(jié)構(gòu)時只损,通常使用internal
一姿。 -
fileprivate
修飾的實體可以在這個文件內(nèi)被訪問七咧。當(dāng)實現(xiàn)細(xì)節(jié)只在文件內(nèi)使用時,通常使用fileprivate
對其他文件隱藏實現(xiàn)細(xì)節(jié)叮叹。 -
private
修飾的實體只能被實體所在的聲明內(nèi)部訪問艾栋。當(dāng)實現(xiàn)細(xì)節(jié)只在聲明內(nèi)部使用時,使用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的訪問級別遵循一個總的原則:一個實體不能定義在訪問權(quán)限比它更低的實體里面粤咪。例如:
- 一個
public
變量不能定義在internal
谚中、fileprivate
或者private
修飾的類型中渴杆。 - 一個方法的訪問權(quán)限不能高于他們參數(shù)和返回值類型
默認(rèn)訪問權(quán)限 (Default Access Levels)
如果我們沒有明確指定實體的權(quán)限,代碼中的所有實體都有一個默認(rèn)的權(quán)限internal
宪塔。
單目標(biāo)應(yīng)用的訪問級別 (Access Levels for Single-Target Apps)
當(dāng)我們編寫一個單目標(biāo)應(yīng)用時磁奖,我們不必讓外部訪問應(yīng)用的模塊。默認(rèn)的internal
權(quán)限都已經(jīng)滿足要求某筐。所以我們不必自定義訪問級別比搭。但是,有是有我們需要使用fileprivate
或者private
來隱藏一些功能實現(xiàn)細(xì)節(jié)南誊。
框架的訪問級別 (Access Levels for Frameworks)
當(dāng)我們在開發(fā)一個框架時身诺,使用open
或者public
來標(biāo)記那些想要公開的API,其他模塊導(dǎo)入這個框架就可以訪問公開的API抄囚。
單元測試目標(biāo)的訪問級別 (Access Levels for Unit Test Targets)
當(dāng)使用單元測試目標(biāo)編寫應(yīng)用時霉赡,為了測試,應(yīng)用的代碼需要向那個模塊公開幔托。默認(rèn)情況下穴亏,只有open
和public
修飾的實體才可以被其他模塊訪問蜂挪。然而,如果我們在導(dǎo)入產(chǎn)品模塊時嗓化,使用@testable
屬性標(biāo)記棠涮,單元測試目標(biāo)可以訪問internal
修飾的實體。
訪問權(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() {}
下面兩個實體的訪問權(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
}
實際上這個例子是不能編譯通過的赤兴。方法的返回值是一個多元組,其中一個元素的權(quán)限為internal
隧哮,另一個是private
桶良,所以多元組的權(quán)限是private
。因為返回值的權(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()
}
}
因為父類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)限坊谁。可以用fileprivate(set)
滑臊、private(set)
或者internal(set)
來設(shè)置更低的權(quán)限口芍。
注意:這個規(guī)則適用于存儲屬性和計算屬性。雖然我們沒有明確寫出存儲屬性的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
實例,并修改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é)議的時候?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
類型的實現(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é)議要求的實現(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)值類型的類型別名霜大。
第二十四部分完。
如果有錯誤的地方革答,歡迎指正战坤!謝謝曙强!