指南:訪問控制(Access Control)

  • 訪問控制可以限定其他源文件或模塊中的代碼對你的代碼的訪問級別。這個特性可以讓我們隱藏代碼的一些實現(xiàn)細節(jié),并且可以為其他人可以訪問和使用的代碼提供接口。

  • 可以明確地給單個類型(類、結(jié)構(gòu)體、枚舉)設置訪問級別,也可以給這些類型的屬性、方法、構(gòu)造器、下標等設置訪問級別。協(xié)議也可以被限定在一定的范圍內(nèi)使用,包括協(xié)議里的全局常量、變量和函數(shù)。

  • 如果只是開發(fā)一個單一 target 的應用程序,完全可以不用顯式申明代碼的訪問級別。

模塊和源文件(Modules and Source Files)

  • Swift 中的訪問控制模型基于模塊和源文件這兩個概念。

  • 模塊指的是獨立的代碼單元,框架framework或應用程序會作為一個獨立的模塊來構(gòu)建和發(fā)布。

  • 在 Swift 中,一個模塊可以使用import關(guān)鍵字導入另外一個模塊。

  • 在 Swift 中,Xcode 的每個 target(例如框架或應用程序)都被當作獨立的模塊處理。

訪問級別(Access Levels)

  • public:可以訪問同一模塊源文件中的任何實體,在模塊外也可以通過導入該模塊來訪問源文件里的所有實體。通常情況下,框架中的某個接口可以被任何人使用時,可以將其設置為 public 級別。

  • internal:可以訪問同一模塊源文件中的任何實體,但是不能從模塊外訪問該模塊源文件中的實體。通常情況下,某個接口只在應用程序或框架內(nèi)部使用時,可以將其設置為 internal級別。

  • private:限制實體只能在所在的源文件內(nèi)部使用。使用 private
    級別可以隱藏某些功能的實現(xiàn)細節(jié)。

  • public為最高(限制最少)訪問級別,private為最低(限制最多)訪問級別。

  • Swift 中的 private訪問級別不同于其他語言,它的范圍限于源文件,而不是聲明范圍內(nèi)。這就意味著,一個類型可以訪問其所在源文件中的所有 private實體,但是如果它的擴展定義在其他源文件中,那么它的擴展就不能訪問它在這個源文件中定義的 private實體。

一個類一個源文件是一個好習慣

訪問級別基本原則(Guiding Principle of Access Levels)

  • Swift 中的訪問級別遵循一個基本原則:不可以在某個實體中定義訪問級別更高的實體。

外面的一定要比里面的高

  • 一個 public訪問級別的變量,其類型的訪問級別不能是 internal或 private。因為無法保證變量的類型在使用變量的地方也具有訪問權(quán)限。

  • 函數(shù)的訪問級別不能高于它的參數(shù)類型和返回類型的訪問級別。因為如果函數(shù)定義為 public而參數(shù)類型或者返回類型定義為 internal或 private,就會出現(xiàn)函數(shù)可以在任何地方被訪問,但是它的參數(shù)類型和返回類型卻不可以。

默認訪問級別(Default Access Levels)

  • 默認為 internal級別,在大多數(shù)情況下,不需要顯式指定實體的訪問級別。

單 target 應用程序的訪問級別(Access Levels for Single-Target Apps)

  • 默認的訪問級別 internal,不需要明確設置訪問級別。

  • 可以使用 private級別,用于隱藏一些功能的實現(xiàn)細節(jié)。

框架的訪問級別(Access Levels for Frameworks)

  • 對外的接口定義為 public級別,以便使用者導入該框架后可以正常使用其功能。

  • 被定義為 public的接口,就是這個框架的 API。

  • 框架依然會使用默認的 internal級別,也可以指定為 private級別。

單元測試 target 的訪問級別(Access Levels for Unit Test Targets)

  • 在導入應用程序模塊的語句前使用 @testable特性,然后在允許測試的編譯設置(Build Options -> Enable Testability)下編譯這個應用程序模塊,單元測試 target 就可以訪問應用程序模塊中所有 internal級別的實體。

訪問控制語法(Access Control Syntax)

  • 通過修飾符 public、internal、private來聲明實體的訪問級別。

  • 除非專門指定,否則實體默認的訪問級別為 internal。

自定義類型(Custom Types)

  • 如果想為一個自定義類型指定訪問級別,在定義類型時進行指定即可。新類型只能在它的訪問級別限制范圍內(nèi)使用。

  • 一個類型的訪問級別也會影響到類型成員(屬性、方法、構(gòu)造器、下標)的默認訪問級別。

  • 一個 public類型的所有成員的訪問級別默認為 internal級別,而不是 public級別。如果想將某個成員指定為 public級別,那么你必須顯式指定。

  • 如果將類型指定為private級別,那么該類型的所有成員的默認訪問級別也會變成 private。

public class SomePublicClass {          // 顯式的 public 類
    public var somePublicProperty = 0   // 顯式的 public 類成員
    var someInternalProperty = 0        // 隱式的 internal 類成員
    private func somePrivateMethod() {} // 顯式的 private 類成員
}

class SomeInternalClass {               // 隱式的 internal 類
    var someInternalProperty = 0        // 隱式的 internal 類成員
    private func somePrivateMethod() {} // 顯式的 private 類成員
}

private class SomePrivateClass {        // 顯式的 private 類
    var somePrivateProperty = 0         // 隱式的 private 類成員
    func somePrivateMethod() {}         // 隱式的 private 類成員
}

元組類型(Tuple Types)

  • 元組的訪問級別將由元組中訪問級別最嚴格的類型來決定。

  • 元組的訪問級別是在它被使用時自動推斷出的,而無法明確指定。

函數(shù)類型(Function Types)

  • 函數(shù)的訪問級別根據(jù)訪問級別最嚴格的參數(shù)類型或返回類型的訪問級別來決定。
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // 此處是函數(shù)實現(xiàn)部分
}

將該函數(shù)指定為 public或 internal,或者使用默認的訪問級別 internal都是錯誤的,因為如果把該函數(shù)當做public或 internal級別來使用的話,可能會無法訪問 private級別的返回值。

枚舉類型(Enumeration Types)

  • 枚舉成員的訪問級別和該枚舉類型相同,不能為枚舉成員單獨指定不同的訪問級別。
public enum CompassPoint {
    case North
    case South
    case East
    case West
}
// 成員 North、South、East、West的訪問級別同樣也是 public

原始值和關(guān)聯(lián)值(Raw Values and Associated Values)

  • 枚舉定義中的任何原始值或關(guān)聯(lián)值的類型的訪問級別至少不能低于枚舉類型的訪問級別。例如,不能在一個 internal訪問級別的枚舉中定義 private級別的原始值類型。

嵌套類型(Nested Types)

  • 如果在 private級別的類型中定義嵌套類型,那么該嵌套類型就自動擁有 private訪問級別。

  • 如果在 public或者internal 級別的類型中定義嵌套類型,那么該嵌套類型自動擁有 internal訪問級別。

  • 如果想讓嵌套類型擁有public訪問級別,那么需要明確指定該嵌套類型的訪問級別。

子類(Subclassing)

  • 子類的訪問級別不得高于父類的訪問級別。

  • 可以在符合當前訪問級別的條件下重寫任意類成員(方法、屬性、構(gòu)造器、下標等)。

  • 可以通過重寫為繼承來的類成員提供更高的訪問級別。通過這種方式,可以將某類中private級別的類成員重新指定為更高的訪問級別,以便其他人使用

public class A {
    private func someMethod() {}
}

internal class B: A {
    override internal func someMethod() {}
}
  • 可以在子類中,用子類成員去訪問訪問級別更低的父類成員,只要這一操作在相應訪問級別的限制范圍內(nèi)(也就是說,在同一源文件中訪問父類 private級別的成員,在同一模塊內(nèi)訪問父類 internal級別的成員)
public class A {
    private func someMethod() {}
}

internal class B: A {
    override internal func someMethod() {
        super.someMethod()
    }
}

常量、變量、屬性、下標(Constants, Variables, Properties, and Subscripts)

  • 常量、變量、屬性不能擁有比它們的類型更高的訪問級別。

  • 下標也不能擁有比索引類型或返回類型更高的訪問級別。

  • 如果常量、變量、屬性、下標的類型是 private 級別的,那么它們必須明確指定訪問級別為 private。

Getter 和 Setter(Getters and Setters)

  • 常量、變量、屬性、下標的 Getters和 Setters的訪問級別和它們所屬類型的訪問級別相同。

  • Setter的訪問級別可以低于對應的 Getter的訪問級別,這樣就可以控制變量、屬性或下標的讀寫權(quán)限。在 var或subscript關(guān)鍵字之前,可以通過 private(set)或 internal(set)為它們的寫入權(quán)限指定更低的訪問級別。

  • 可以在必要時為 Getter和 Setter顯式指定訪問級別。

public struct TrackedString {
    public private(set) var numberOfEdits = 0
    public var value: String = "" {
        didSet {
            numberOfEdits += 1
        }
    }
    public init() {}
}

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”

構(gòu)造器(Initializers)

  • 自定義構(gòu)造器的訪問級別可以低于或等于其所屬類型的訪問級別。

  • 必要構(gòu)造器(required)的訪問級別必須和所屬類型的訪問級別相同。

  • 構(gòu)造器參數(shù)的訪問級別也不能低于構(gòu)造器本身的訪問級別。

默認構(gòu)造器(Default Initializers)

  • 默認構(gòu)造器的訪問級別與所屬類型的訪問級別相同,除非類型的訪問級別是 public。如果一個類型被指定為 public級別,那么默認構(gòu)造器的訪問級別將為 internal。

  • 如果希望一個 public級別的類型也能在其他模塊中使用這種無參數(shù)的默認構(gòu)造器,只能自己提供一個 public訪問級別的無參數(shù)構(gòu)造器。

結(jié)構(gòu)體默認的成員逐一構(gòu)造器(Default Memberwise Initializers for Structure Types)

  • 如果結(jié)構(gòu)體中任意存儲型屬性的訪問級別為 private,那么該結(jié)構(gòu)體默認的成員逐一構(gòu)造器的訪問級別就是 private。否則,這種構(gòu)造器的訪問級別依然是 internal。

  • 如果希望一個 public級別的結(jié)構(gòu)體也能在其他模塊中使用其默認的成員逐一構(gòu)造器,只能自己提供一個 public訪問級別的成員逐一構(gòu)造器。

協(xié)議(Protocols)

  • 如果想為一個協(xié)議類型明確地指定訪問級別,在定義協(xié)議時指定即可。

  • 協(xié)議中的每一個要求都具有和該協(xié)議相同的訪問級別。不能將協(xié)議中的要求設置為其他訪問級別。這樣才能確保該協(xié)議的所有要求對于任意實現(xiàn)者都將可用。

  • 如果你定義了一個 public訪問級別的協(xié)議,那么該協(xié)議的所有實現(xiàn)也會是 public訪問級別。

協(xié)議繼承(Protocol Inheritance)

  • 如果定義了一個繼承自其他協(xié)議的新協(xié)議,那么新協(xié)議擁有的訪問級別最高也只能和被繼承協(xié)議的訪問級別相同。例如,不能將繼承自 internal協(xié)議的新協(xié)議定義為 public協(xié)議。

協(xié)議一致性(Protocol Conformance)

  • 一個類型可以遵循比自身訪問級別低的協(xié)議。例如,可以定義一個 public級別的類型,它可以在其他模塊中使用,同時它也可以遵循一個 internal級別的協(xié)議,但是只能在該協(xié)議所在的模塊中作為符合該協(xié)議的類型使用。

  • 遵循了協(xié)議的類型的訪問級別取它本身和所遵循協(xié)議兩者間最低的訪問級別。也就是說如果一個類型是 public級別,遵循的協(xié)議是 internal級別,那么遵循了這個協(xié)議后,該類型作為符合協(xié)議的類型時,其訪問級別也是 internal。

  • 如果遵循了協(xié)議,那么實現(xiàn)了協(xié)議的所有要求后,必須確保這些實現(xiàn)的訪問級別不能低于協(xié)議的訪問級別。例如,一個 public級別的類型,遵循了 internal級別的協(xié)議,那么協(xié)議的實現(xiàn)至少也得是 internal級別。

  • 協(xié)議的一致性是全局的,也就是說,在同一程序中,一個類型不可能用兩種不同的方式實現(xiàn)同一個協(xié)議。

擴展(Extensions)

  • 擴展成員具有和原始類型成員一致的訪問級別。例如,擴展了一個 public或者 internal類型,擴展中的成員具有默認的 internal訪問級別,和原始類型中的成員一致 。如果擴展了一個 private類型,擴展成員則擁有默認的 private訪問級別。

  • 可以明確指定擴展的訪問級別(例如,private extension),從而給該擴展中的所有成員指定一個新的默認訪問級別。這個新的默認訪問級別仍然可以被單獨指定的訪問級別所覆蓋。

通過擴展添加協(xié)議一致性(Adding Protocol Conformance with an Extension)

  • 如果通過擴展來采納協(xié)議,那么就不能顯式指定該擴展的訪問級別了。協(xié)議擁有相應的訪問級別,并會為該擴展中所有協(xié)議要求的實現(xiàn)提供默認的訪問級別。

泛型(Generics)

  • 泛型類型或泛型函數(shù)的訪問級別取決于泛型類型或泛型函數(shù)本身的訪問級別,還需結(jié)合類型參數(shù)的類型約束的訪問級別,根據(jù)這些訪問級別中的最低訪問級別來確定。

類型別名(Type Aliases)

  • 定義的任何類型別名都會被當作不同的類型,以便于進行訪問控制。

  • 類型別名的訪問級別不可高于其表示的類型的訪問級別。例如,private級別的類型別名可以作為 public、internal、private類型的別名,但是 public級別的類型別名只能作為 public類型的別名,不能作為 internal或 private類型的別名。


  • Swift只有源文件,沒有頭文件,這個和Object-C完全不同。再也不用糾結(jié)變量和函數(shù)定義應該放哪個文件了。這個特性推薦
  • 默認的全局訪問internal,帶來了很大的方便。再也不需要一個歸總的header文件,也不需要pch文件,作用只是方便訪問
  • private的作用范圍是源文件,而不是所在的類型,這個像C語言中的靜態(tài)全局變量。這個也是推薦的
  • 需要隱藏的內(nèi)容需要手動指定private,這個思路和C相反,但是推薦。在一個target中,內(nèi)部公開,方便訪問是第一優(yōu)先級的。隱藏需要特別指定,這個符合思維習慣,推薦。
  • 寫framework的時候,默認是不公開的,對外接口需要顯示用public指定,這個思路也支持。
  • 協(xié)議強調(diào)訪問的一致性,這個特性支持。簡單好用
  • 元組,泛型,類型別名,只能低,不能高的思路也支持。簡單好用
  • 繼承通過重寫可以提升級別的特性,持保留態(tài)度。遷就于現(xiàn)有代碼,可以理解。但是自己設計的新類,如果用到這種思路,需要重新思考整體設計思路。這個顯然不推薦。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

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