【Swift 3.1】24 -訪問權(quán)限 (Access Control)

【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):

  • openpublic修飾的實(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變量不能定義在internalfileprivate或者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)情況下,只有openpublic修飾的實(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定義在同一個文件,所以在BsomeMethod()方法中調(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é)合publicprivate(set)使得umberOfEdits屬性的getter是public,而setterprivate。

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、internalpublic或者open類型的別名。

注意:這個規(guī)則也適用于關(guān)聯(lián)值類型的類型別名。


第二十四部分完。


如果有錯誤的地方,歡迎指正!謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內(nèi)容