26、【Swift】訪問控制 - Access Control

  • 使用場景:
    • 限制其他源文件和模塊對代碼的訪問權(quán)限搔体。
    • 封裝隱藏代碼的實現(xiàn)細(xì)節(jié)恨樟,只公開接口給人調(diào)用
  • 適用范圍:
    • 給單個類型(類、結(jié)構(gòu)體疚俱、枚舉)設(shè)置訪問級別
      • 或單獨給這些類型的屬性劝术、方法、構(gòu)造器、下標(biāo)等設(shè)置訪問級別
    • 限定協(xié)議在一定訪問級別的范圍內(nèi)使用
      • 包括協(xié)議里的全局常量养晋、變量和函數(shù)
  • 默認(rèn)的訪問級別
    • 不需代碼中都顯式聲明訪問級別
  • 開發(fā)一個單 target 的應(yīng)用程序
    • 完全可以不用顯式聲明代碼的訪問級別

對代碼中可設(shè)置訪問級別的特性(屬性衬吆、基本類型、函數(shù)等)匙握,統(tǒng)一稱之為“實體”(entities)咆槽。

模塊和源文件 - Modules and Source Files

  • 訪問控制模型--兩個概念:
    • 模塊:單一的代碼分配單元——一個框架或應(yīng)用程序(a framework or application),一個模塊可使用 import 關(guān)鍵字導(dǎo)入另外一個模塊
      • 源文件:一個模塊中的單個 Swift 源代碼文件(事實上圈纺,是一個應(yīng)用程序或是框架中的單個文件)秦忿,通常在單獨源文件中定義單個類型,但是一個源文件可以包含多個類型蛾娶。函數(shù)等的定義
        • 實體

訪問級別? - Access Levels

  • Swift 代碼實體的五個訪問級別
    • Open(允許其他模塊灯谣,繼承和重寫類和類成員) 和 public(禁止其他模塊,繼承和重寫類:
      • 范圍:可被本模塊中所有源文件可訪問蛔琅,另一模塊的源文件訪問需要導(dǎo)入本模塊
      • 應(yīng)用:用 open 或 public 級別來指定框架的外部接口
    • Internal
      • 范圍:本模塊中所有源文件可訪問胎许,其他模塊的源文件不能訪問
      • 應(yīng)用:接口只在應(yīng)用程序或框架內(nèi)部使用,設(shè)置為 internal 級別
    • File-private
      • 范圍:當(dāng)前定義源文件可訪問
      • 應(yīng)用:功能接口實現(xiàn)罗售,全在一個源文件辜窑,用 File-private 隱藏接口實現(xiàn)細(xì)節(jié)
    • private
      • 范圍:在其定義的作用域可訪問 + 同一源文件內(nèi)的 extension 訪問
      • 應(yīng)用:接口只需在當(dāng)前作用域內(nèi)使用時,用 private 來將其隱藏

訪問級別基本原則

  • 總體指導(dǎo)準(zhǔn)則 - overall guiding principle:實體不能定義在比自己訪問級別低的實體中(至少要相同)
    • 訪問級別:實體 ≥ 定義實體的范圍
  • 例子:
    • 定義一個 public 的變量的類型寨躁,不能是 internal, file-private 或是 private穆碎,訪問public 變量的地方,可能無法訪問這個類型的權(quán)限职恳,從而無法訪問該 public 變量
    • 參數(shù)類型所禀、返回類型 ≥ 函數(shù),否側(cè)可以調(diào)用函數(shù)放钦,但無法范圍參數(shù)和返回值

默認(rèn)訪問級別

  • 定義實體時色徘,不顯式指定訪問級別,一般默認(rèn)訪問級別為 internal (有一些情況會例外)
  • 數(shù)情況下操禀,不需要明確指定實體的訪問級別

單 target 應(yīng)用程序的訪問級別

  • 寫單 target 應(yīng)用程序褂策,代碼都在本應(yīng)用使用并且不會在應(yīng)用模塊之外使用,internal 已匹配這種需求
    • 不需明確自定訪問級別
    • 若要對模塊中其他代碼隱藏接口實現(xiàn)細(xì)節(jié)颓屑,標(biāo)注為 file private 或private

框架的訪問級別 - Access Levels for Frameworks

  • 因默認(rèn) internal辙培,但框架接口要給外部調(diào)用,所以定義為 open 或 public
    • 對外的接口邢锯,就是這個框架的 API

內(nèi)部實現(xiàn)仍可用默認(rèn) internal,隱藏細(xì)節(jié)可用 privatefileprivate

框架的對外 API 部分搀别,需要將它們設(shè)置為 openpublic

單元測試 target 的訪問級別

  • 默認(rèn) open 或 public 的才可跨模塊訪問
  • 應(yīng)用程序有單元測試 target 時丹擎,測試模塊要訪問應(yīng)用程序模塊的代碼
    • 在導(dǎo)入應(yīng)用程序模塊的語句前使用 @testable 特性
    • 允許測試的編譯設(shè)置(Build Options -> Enable Testability)下編譯這個應(yīng)用程序模塊
    • 單元測試 target 就可以訪問應(yīng)用程序模塊中所有內(nèi)部級別的實體

訪問控制語法

  • 通過修飾符 openpublicinternal蒂培、fileprivate再愈、private 來聲明實體的訪問級別:
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() {}
  • 除非已經(jīng)標(biāo)注,否則都會使用默認(rèn)的 internal 訪問級別
class SomeInternalClass {}   // 隱式 internal
var someInternalConstant = 0 // 隱式 internal

自定義類型

  • 一個類型的訪問級別會影響類型成員(屬性护戳、方法翎冲、構(gòu)造器、下標(biāo))的默認(rèn)訪問級別
  • 類型定為 privatefileprivate 媳荒,該類型成員默認(rèn)訪問級別也變成 privatefileprivate 級別
  • 類型指定為 internalpublic(或者不明確指定訪問級別抗悍,而使用默認(rèn)的 internal ),該類型所有成員的默認(rèn)訪問級別將是 internal

一個 public 類型的所有成員的訪問級別默認(rèn)為 internal 級別钳枕,而不是 public 級別

如果你想將某個成員指定為 public 級別缴渊,必須顯式指定

這樣做的好處是,在你定義公共接口的時候鱼炒,可以明確地選擇哪些接口是需要公開的衔沼,哪些是內(nèi)部使用的,避免不小心將(類型)內(nèi)部使用的接口公開


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

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

fileprivate class SomeFilePrivateClass {        // 顯式 fileprivate 類
    func someFilePrivateMethod() {}              // 隱式 fileprivate 類成員
    private func somePrivateMethod() {}          // 顯式 private 類成員
}

private class SomePrivateClass {                // 顯式 private 類
    func somePrivateMethod() {}                  // 隱式 private 類成員
}

元組類型

  • 由元級別最嚴(yán)格的類型(元素)來決定
  • 如昔瞧,構(gòu)建一個包含兩種不同類型的元組指蚁,其中一個為 internal,另一個類型為 private自晰,那么這元組的訪問級別為 private凝化。

元組不同于類、結(jié)構(gòu)體缀磕、枚舉缘圈、函數(shù)那樣有單獨的定義。

一個元組的訪問級別由元組中元素的訪問級別來決定的袜蚕,不能被顯式指定糟把。

函數(shù)類型

  • 根據(jù)最嚴(yán)格的參數(shù)類型或返回類型的訪問級別來決定
  • 如不符合函數(shù)定義所在環(huán)境的默認(rèn)訪問級別,需明確指定函數(shù)訪問級別
  • 按下面這種寫法牲剃,代碼將無法通過編譯:
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // 此處是函數(shù)實現(xiàn)部分
}
  • 返回類型-該元組的訪問級別是 private
  • 必須使明確private 修飾符來明確指定該函數(shù)的訪問級別
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // 此處是函數(shù)實現(xiàn)部分
}
  • 函數(shù)當(dāng)做 publicinternal 級別來使用的話遣疯,可能會無法訪問 private 級別的返回值

枚舉類型

  • 成員的訪問級別和該枚舉類型相同
  • 不能為枚舉成員單獨指定不同的訪問級別
public enum CompassPoint {
    case north
    case south
    case east
    case west
}
// CompassPoint 被明確指定為 public,那么它的成員 north凿傅、south缠犀、east、west 的訪問級別同樣也是 public:

原始值和關(guān)聯(lián)值

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

嵌套類型 - Nested Types

  • 嵌套類型的訪問級別 = 包含它的類型的訪問級別
    • private 級別的類型中定義的嵌套類型自動為 private 級別
    • fileprivate 級別的類型中定義的嵌套類型自動為 fileprivate 級別
    • public 或 internal 級別的類型中定義的嵌套類型自動為 internal 級別
    • 想讓嵌套類型是 public 級別的辨液,必須顯式指明為 public

子類

  • 可繼承同一模塊中的所有訪問權(quán)限的類,也可繼承不同模塊被 open 修飾的類
  • 子類不得高于父類(子類 ≤ 父類)
    • 如箱残,父類是 internal滔迈,子類不能是 public
    • 可重寫類成員(方法止吁,屬性,初始化器或下標(biāo))
DcNEDI.png
  • 提高父類權(quán)限:對 someMethod() 函數(shù)進(jìn)行了重寫即改為“internal”級別燎悍,這比 someMethod() 的原本實現(xiàn)級別更高
public class A {
         fileprivate func someMethod() {}
}
internal class B: A {
         override internal func someMethod() {}
}
  • 類 A 和子類 B 定義在同一個源文件中敬惦,那么 B 類可以在 someMethod() 中調(diào)用父類的 someMethod()

常量、變量谈山、屬性俄删、下標(biāo)

  • 常量、變量奏路、屬性不能擁有比它們類型更高的訪問級別畴椰。
    • 如,你不能寫一個public 的屬性而它的類型是 private 的
  • 下標(biāo)也不能擁有比索引類型或返回類型更高的訪問級別
private var privateInstance = SomePrivateClass()

Getter 和 Setter

  • getter 和 setter 和它們所屬常量思劳、變量迅矛、屬性和下標(biāo)的訪問級別相同
  • Setter 的訪問級別可低于 Getter ,從而控制讀寫權(quán)限
  • 語法:varsubscript 關(guān)鍵字之前潜叛,你可以通過 fileprivate(set)秽褒,private(set)internal(set) 為它們的寫入權(quán)限指定更低的訪問級別

這規(guī)則適用于存儲型和計算型屬性。

即使你不明確指定存儲型屬性GetterSetter威兜,Swift 也會隱式創(chuàng)建 GetterSetter

  • TrackedString 的結(jié)構(gòu)體销斟,記錄了 value 屬性被修改的次數(shù):
struct TrackedString {
    private(set) var numberOfEdits = 0
    var value: String = "" {
        didSet {
            numberOfEdits += 1
        }
    }
}
  • numberOfEdits 屬性的 Getter 依然是默認(rèn)的訪問級別 internal
  • Setter 的訪問級別是 private,這表示該屬性只能在內(nèi)部修改椒舵,而在結(jié)構(gòu)體的外部則表現(xiàn)為一個只讀屬性
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 屬性的值蚂踊,但不能對其賦值
  • TrackedString 結(jié)構(gòu)體明確為 public
  • 結(jié)構(gòu)體的成員(包括 numberOfEdits 屬性)擁有默認(rèn)的訪問級別 internal
  • 結(jié)合 publicprivate(set) 修飾符
    • 把結(jié)構(gòu)體中的 numberOfEdits 屬性的 Getter 的訪問級別設(shè)置為 public
    • Setter 的訪問級別設(shè)置為 private
public struct TrackedString {
    public private(set) var numberOfEdits = 0
    public var value: String = "" {
        didSet {
            numberOfEdits += 1
        }
    }
    public init() {}
}

構(gòu)造器

  • 自定義構(gòu)造器
    • 可低于或等于所屬類型
  • 必要構(gòu)造器
  • 必須和所屬類型的訪問級別相同
  • 類似函數(shù)或方法,構(gòu)造器參數(shù)不能低于構(gòu)造器本身的訪問級別

默認(rèn)構(gòu)造器

  • Swift 會為結(jié)構(gòu)體和類提供一個默認(rèn)的無參數(shù)的構(gòu)造器(前提條件:給存儲屬性賦初值 + 未定義構(gòu)造器)
  • 默認(rèn)構(gòu)造器的訪問級別與所屬類型的訪問級別相同
    • 類型被指定為 public 級別笔宿,那么默認(rèn)構(gòu)造器的訪問級別將為 internal
  • 希望在其他模塊中使用這種無參數(shù)的默認(rèn)構(gòu)造器犁钟,自己提供一個 public 訪問級別的無參數(shù)構(gòu)造器

結(jié)構(gòu)體默認(rèn)的成員逐一構(gòu)造器

  • 任意存儲型屬性的訪問級別為 private,成員逐一構(gòu)造器的訪問級別就是 private泼橘。否則涝动,這種構(gòu)造器的訪問級別依然是 internal
  • 希望一個 public 級別的結(jié)構(gòu)體也能在其他模塊中使用其默認(rèn)的成員逐一構(gòu)造器炬灭,只能自己提供一個 public 訪問級別的成員逐一構(gòu)造器

協(xié)議

  • 限制該協(xié)議只能在適當(dāng)?shù)脑L問級別范圍內(nèi)被遵循醋粟。
  • 協(xié)議中的每個方法或?qū)傩远急仨毢驮搮f(xié)議相同的訪問級別
    • 不能將協(xié)議中的方法或?qū)傩栽O(shè)置為其他訪問級別
    • 才能確保該協(xié)議的所有方法或?qū)傩詫τ谌我庾裱叨伎捎谩?/li>

協(xié)議繼承

  • 新協(xié)議和被繼承協(xié)議的訪問級別相同
    • 如,不能將繼承自 internal 協(xié)議的新協(xié)議定為 public 協(xié)議重归。

協(xié)議遵循

  • 一個類型可遵循比它級別更低的協(xié)議

    • 一個 public 級別類型米愿,如果遵循一個 internal 協(xié)議,遵循的部分只能在這 internal 協(xié)議所在的模塊中使用
  • 遵循了協(xié)議的類鼻吮,取協(xié)議和類的訪問級別的最小者

    • 如類型是 public 育苟,遵循協(xié)議 internal 級別,這個類型就是 internal 級別的
  • 寫或擴(kuò)展一個類型讓它遵循一個協(xié)議時椎木,類按協(xié)議要求的實現(xiàn)方法與該協(xié)議的訪問級別一致

    • 一個 public 類型遵循一個 internal 協(xié)議宙搬,這個類型對協(xié)議的所有實現(xiàn)至少都應(yīng)是 internal 級別的

Swift 和 Objective-C 一樣笨腥,協(xié)議遵循是全局的,也就是說勇垛,在同一程序中,一個類型不可能用兩種不同的方式實現(xiàn)同一個協(xié)議士鸥。

擴(kuò)展 - Extension

  • Extension 的新增成員有和原始類型成員一致的訪問級別
    • extension 一個 public 或者 internal 類型闲孤, extension 中的成員默認(rèn)為 internal 訪問級別
    • 用 extension 擴(kuò)展一個 fileprivate 類型,則 extension 中的成員默認(rèn)使用 fileprivate 訪問級別
    • 用 extension 擴(kuò)展了一個 private 類型烤礁,則 extension 的成員默認(rèn)使用 private 訪問級別
  • 可以重新指定 extension 的默認(rèn)訪問級別(例如讼积,private),從而給 extension 中所有成員一個新默認(rèn)訪問級別
  • 用 extension 來遵循協(xié)議的話脚仔,就不能顯式地聲明 extension 的訪問級別
    • extension 每個 protocol 要求的實現(xiàn)都默認(rèn)使用 protocol 的訪問級別

Extension 的私有成員

  • 擴(kuò)展同一文件內(nèi)的類勤众,結(jié)構(gòu)體或者枚舉,extension 里的代碼會表現(xiàn)得跟聲明在原類型里的一模一樣鲤脏。也就是說你可以這樣:
    • 在類型的聲明里们颜,聲明一個私有成員,在同一文件的 extension 里訪問猎醇。
    • 在 extension 里聲明一個私有成員窥突,在同一文件的另一個 extension 里訪問。
    • 在 extension 里聲明一個私有成員硫嘶,在同一文件的類型聲明里訪問阻问。
  • 可以使用 extension 來組織你的代碼,而且不受私有成員的影響
protocol SomeProtocol {
    func doSomething()
}
  • 使用 extension 來遵循協(xié)議沦疾,就像這樣:
struct SomeStruct {
    private var privateVariable = 12
}

extension SomeStruct: SomeProtocol {
    func doSomething() {
        print(privateVariable)
    }
}

泛型

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

類型別名

  • 類型別名的訪問級別,不能高于原類型
    • private 級別的類型別名可以作為 private哮塞、fileprivate刨秆、internalpublic 或者 open 類型的別名
    • 但是 public 級別的類型別名只能作為 public 類型的別名彻桃,不能作為 internal坛善、fileprivateprivate 類型的別名。

這條規(guī)則也適用于為滿足協(xié)議遵循而將類型別名用于關(guān)聯(lián)類型的情況邻眷。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末眠屎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子肆饶,更是在濱河造成了極大的恐慌改衩,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驯镊,死亡現(xiàn)場離奇詭異葫督,居然都是意外死亡竭鞍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門橄镜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來偎快,“玉大人,你說我怎么就攤上這事洽胶∩辜校” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵姊氓,是天一觀的道長丐怯。 經(jīng)常有香客問我,道長翔横,這世上最難降的妖魔是什么读跷? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮禾唁,結(jié)果婚禮上效览,老公的妹妹穿的比我還像新娘。我一直安慰自己蟀俊,他們只是感情好钦铺,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肢预,像睡著了一般矛洞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烫映,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天沼本,我揣著相機(jī)與錄音,去河邊找鬼锭沟。 笑死抽兆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的族淮。 我是一名探鬼主播辫红,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼祝辣!你這毒婦竟也來了贴妻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤蝙斜,失蹤者是張志新(化名)和其女友劉穎名惩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孕荠,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡娩鹉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年攻谁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弯予。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡戚宦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出熙涤,到底是詐尸還是另有隱情阁苞,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布祠挫,位于F島的核電站,受9級特大地震影響悼沿,放射性物質(zhì)發(fā)生泄漏等舔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一糟趾、第九天 我趴在偏房一處隱蔽的房頂上張望慌植。 院中可真熱鬧,春花似錦义郑、人聲如沸蝶柿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽交汤。三九已至,卻和暖如春劫笙,著一層夾襖步出監(jiān)牢的瞬間芙扎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工填大, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留戒洼,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓允华,卻偏偏與公主長得像圈浇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子靴寂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

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