26、【Swift】訪問(wèn)控制 - Access Control

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

對(duì)代碼中可設(shè)置訪問(wèn)級(jí)別的特性(屬性、基本類(lèi)型、函數(shù)等),統(tǒng)一稱之為“實(shí)體”(entities)。

模塊和源文件 - Modules and Source Files

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

訪問(wèn)級(jí)別? - Access Levels

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

訪問(wèn)級(jí)別基本原則

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

默認(rèn)訪問(wèn)級(jí)別

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

單 target 應(yīng)用程序的訪問(wèn)級(jí)別

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

框架的訪問(wèn)級(jí)別 - Access Levels for Frameworks

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

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

框架的對(duì)外 API 部分,需要將它們?cè)O(shè)置為 openpublic

單元測(cè)試 target 的訪問(wèn)級(jí)別

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

訪問(wèn)控制語(yǔ)法

  • 通過(guò)修飾符 open、publicinternal、fileprivateprivate 來(lái)聲明實(shí)體的訪問(wèn)級(jí)別:
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)注,否則都會(huì)使用默認(rèn)的 internal 訪問(wèn)級(jí)別
class SomeInternalClass {}   // 隱式 internal
var someInternalConstant = 0 // 隱式 internal

自定義類(lèi)型

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

一個(gè) public 類(lèi)型的所有成員的訪問(wèn)級(jí)別默認(rèn)為 internal 級(jí)別,而不是 public 級(jí)別

如果你想將某個(gè)成員指定為 public 級(jí)別,必須顯式指定

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


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

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

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

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

元組類(lèi)型

  • 由元級(jí)別最嚴(yán)格的類(lèi)型(元素)來(lái)決定
  • 如,構(gòu)建一個(gè)包含兩種不同類(lèi)型的元組,其中一個(gè)為 internal,另一個(gè)類(lèi)型為 private,那么這元組的訪問(wèn)級(jí)別為 private

元組不同于類(lèi)、結(jié)構(gòu)體、枚舉、函數(shù)那樣有單獨(dú)的定義。

一個(gè)元組的訪問(wèn)級(jí)別由元組中元素的訪問(wèn)級(jí)別來(lái)決定的,不能被顯式指定。

函數(shù)類(lèi)型

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

枚舉類(lèi)型

  • 成員的訪問(wèn)級(jí)別和該枚舉類(lèi)型相同
  • 不能為枚舉成員單獨(dú)指定不同的訪問(wèn)級(jí)別
public enum CompassPoint {
    case north
    case south
    case east
    case west
}
// CompassPoint 被明確指定為 public,那么它的成員 north、south、east、west 的訪問(wèn)級(jí)別同樣也是 public:

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

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

嵌套類(lèi)型 - Nested Types

  • 嵌套類(lèi)型的訪問(wèn)級(jí)別 = 包含它的類(lèi)型的訪問(wèn)級(jí)別
    • private 級(jí)別的類(lèi)型中定義的嵌套類(lèi)型自動(dòng)為 private 級(jí)別
    • fileprivate 級(jí)別的類(lèi)型中定義的嵌套類(lèi)型自動(dòng)為 fileprivate 級(jí)別
    • public 或 internal 級(jí)別的類(lèi)型中定義的嵌套類(lèi)型自動(dòng)為 internal 級(jí)別
    • 想讓嵌套類(lèi)型是 public 級(jí)別的,必須顯式指明為 public

子類(lèi)

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

常量、變量、屬性、下標(biāo)

  • 常量、變量、屬性不能擁有比它們類(lèi)型更高的訪問(wèn)級(jí)別。
    • 如,你不能寫(xiě)一個(gè)public 的屬性而它的類(lèi)型是 private 的
  • 下標(biāo)也不能擁有比索引類(lèi)型或返回類(lèi)型更高的訪問(wèn)級(jí)別
private var privateInstance = SomePrivateClass()

Getter 和 Setter

  • getter 和 setter 和它們所屬常量、變量、屬性和下標(biāo)的訪問(wèn)級(jí)別相同
  • Setter 的訪問(wèn)級(jí)別可低于 Getter ,從而控制讀寫(xiě)權(quán)限
  • 語(yǔ)法:varsubscript 關(guān)鍵字之前,你可以通過(guò) fileprivate(set),private(set)internal(set) 為它們的寫(xiě)入權(quán)限指定更低的訪問(wèn)級(jí)別

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

即使你不明確指定存儲(chǔ)型屬性GetterSetter,Swift 也會(huì)隱式創(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)的訪問(wèn)級(jí)別 internal
  • Setter 的訪問(wèn)級(jí)別是 private,這表示該屬性只能在內(nèi)部修改,而在結(jié)構(gòu)體的外部則表現(xiàn)為一個(gè)只讀屬性
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 屬性的值,但不能對(duì)其賦值
  • TrackedString 結(jié)構(gòu)體明確為 public
  • 結(jié)構(gòu)體的成員(包括 numberOfEdits 屬性)擁有默認(rèn)的訪問(wèn)級(jí)別 internal
  • 結(jié)合 publicprivate(set) 修飾符
    • 把結(jié)構(gòu)體中的 numberOfEdits 屬性的 Getter 的訪問(wèn)級(jí)別設(shè)置為 public
    • Setter 的訪問(wèn)級(jí)別設(shè)置為 private
public struct TrackedString {
    public private(set) var numberOfEdits = 0
    public var value: String = "" {
        didSet {
            numberOfEdits += 1
        }
    }
    public init() {}
}

構(gòu)造器

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

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

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

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

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

協(xié)議

  • 限制該協(xié)議只能在適當(dāng)?shù)脑L問(wèn)級(jí)別范圍內(nèi)被遵循。
  • 協(xié)議中的每個(gè)方法或?qū)傩远急仨毢驮搮f(xié)議相同的訪問(wèn)級(jí)別
    • 不能將協(xié)議中的方法或?qū)傩栽O(shè)置為其他訪問(wèn)級(jí)別
    • 才能確保該協(xié)議的所有方法或?qū)傩詫?duì)于任意遵循者都可用。

協(xié)議繼承

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

協(xié)議遵循

  • 一個(gè)類(lèi)型可遵循比它級(jí)別更低的協(xié)議

    • 一個(gè) public 級(jí)別類(lèi)型,如果遵循一個(gè) internal 協(xié)議,遵循的部分只能在這 internal 協(xié)議所在的模塊中使用
  • 遵循了協(xié)議的類(lèi),取協(xié)議和類(lèi)的訪問(wèn)級(jí)別的最小者

    • 如類(lèi)型是 public ,遵循協(xié)議 internal 級(jí)別,這個(gè)類(lèi)型就是 internal 級(jí)別的
  • 寫(xiě)或擴(kuò)展一個(gè)類(lèi)型讓它遵循一個(gè)協(xié)議時(shí),類(lèi)按協(xié)議要求的實(shí)現(xiàn)方法與該協(xié)議的訪問(wèn)級(jí)別一致

    • 一個(gè) public 類(lèi)型遵循一個(gè) internal 協(xié)議,這個(gè)類(lèi)型對(duì)協(xié)議的所有實(shí)現(xiàn)至少都應(yīng)是 internal 級(jí)別的

Swift 和 Objective-C 一樣,協(xié)議遵循是全局的,也就是說(shuō),在同一程序中,一個(gè)類(lèi)型不可能用兩種不同的方式實(shí)現(xiàn)同一個(gè)協(xié)議。

擴(kuò)展 - Extension

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

Extension 的私有成員

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

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

泛型

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

類(lèi)型別名

  • 類(lèi)型別名的訪問(wèn)級(jí)別,不能高于原類(lèi)型
    • private 級(jí)別的類(lèi)型別名可以作為 private、fileprivate、internalpublic 或者 open 類(lèi)型的別名
    • 但是 public 級(jí)別的類(lèi)型別名只能作為 public 類(lèi)型的別名,不能作為 internal、fileprivateprivate 類(lèi)型的別名。

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容