Swift語法2.24(訪問控制)

訪問控制(Access Control)

本文內容包括:

  • 模塊和源文件
  • 訪問級別
  • 訪問級別的基本原則
  • 默認訪問級別
  • Single-Target應用程序的訪問級別
  • Framework的訪問級別
  • Unit Test Target的訪問級別
  • 訪問控制語法
  • 自定義類型
  • 元組類型
  • 函數類型
  • 枚舉類型
  • 原始值和關聯值
  • 嵌套類型
  • 子類
  • 常量,變量,屬性,下標
  • getter和setter
  • 初始化
  • 默認初始化方法
  • 結構體的默認成員初始化方法
  • 協議
  • 協議繼承
  • 遵循協議
  • 擴展
  • 通過擴展遵循協議
  • 泛型
  • 類型別名

訪問控制(access control) 限定其他源文件或模塊中的代碼你的代碼的某些部分的訪問.
這個特性可以讓你 隱藏代碼的實現細節 , 并且可以提供接口(interface)來訪問和使用代碼

你可以 給某個的類型(類,結構體,枚舉) 以及這些 類型的屬性,方法,構造器,下標等 設置 明確的 訪問級別(access level).
協議可以被 限定在一定的上下文中使用 , 全局常量,變量和函數 也是如此

Swift 不僅提供了不同的訪問級別 , 而且 通過為典型的場景提供默認的訪問級別的方式 減少了對于指定明確的訪問控制級別的需求.
事實上,如果只是開發一個單一目標(single-target)應用程序,你可以不用指定明確的訪問級別

注意:
可以將訪問控制應用于你的代碼的各個部分(properties/types/functions等)
并且在下面的章節中我們會以 實體(entity) 代替它們,為簡單起見.

模塊(module)和源文件(source file)

Swift中的 訪問控制模型(access control model) 是基于 模塊 和 源文件 這兩個概念

模塊單個的代碼分布單元
一個框架(framework)或一個應用程序(application) 被構建和發布單個單元(single unit)
并且 **可以被導入另外一個模塊 **通過使用Swift關鍵字 import

在Swift語法中,Xcode的每個 build target , 例如一個應用程序包或框架(app bundle or framework) , 都被視為獨立的模塊

如果你為了 封裝代碼在多個應用中重用代碼將代碼的各個部分打包成一個獨立的框架 , 那么當它被導入到某個應用程序或者其他框架時 , 你在框架中定義的內容都將屬于這個獨立的模塊

一個源文件就是一個模塊中的單個Swift源代碼文件
(實際上就是應用程序或者框架中的一單個文件)

盡管我們一般會將不同的類型分別定義在不同的源文件中
但是單個源文件也可以包含多個類型,函數等的定義

訪問級別(Access Level)

Swift 為 **代碼中的實體(entity) ** 提供了三種不同的 訪問級別
這些 訪問級別 不僅 與源文件中定義的實體相關 , 同時 也與源文件所屬的模塊相關

  • public access(公共訪問)
    實體 可以 在 **定義它的模塊之內的任意源文件中 **被使用
    實體 也可以 在 **導入定義它的模塊的任意源文件中 **被使用
    指定一個框架的公共接口 時 , 你通常使用 public access

  • internal access(內部訪問)
    實體 可以 在 **定義它的模塊之內的任意源文件中 **被使用
    實體 不可以 在 **定義它的模塊之外的任意源文件中 **被使用
    定義一個應用程序或一個框架的內部結構 時,你通常使用 internal access

  • private access(私有訪問)
    實體 只能定義它的源文件中 被使用。
    使用 private access 可以 隱藏某些功能的實現細節

public 為最高的(限制最寬松的)訪問級別
private 為最低的(限制最嚴格的)訪問級別

注意

Swift中的 private access 和大多數其他語言中的 private access 不同

Swift中的實體的訪問級別如果被設置為 private access ,那么實體的作用范圍是 實體所在的源文件 ,而不是實體所在的聲明

這就意味著,一個類型 可以訪問 其所在源文件中的所有private實體 , 但 定義在其他源文件中該類型的擴展 不能訪問 該類型的私有成員(private members)

訪問級別基本原則

Swift中的訪問級別遵循一個總基本原則

一個實體的定義不可以依賴訪問級別更低(限制更為嚴格)的另一個實體

例如:

  • 一個public變量的定義中不能包含internal或private類型
    因為 internal/private級別的類型 可能在任何使用該public變量的地方 都不可用

  • 一個函數的訪問級別不能高于其參數類型和返回值類型的訪問級別
    因為函數在被調用時 其參數類型或返回值類型可能會 不可用于周圍代碼

默認訪問級別

大多數情況下 , 如果你 不為代碼中的實體顯式指定訪問級別 的話 , 代碼中的實體 默認internal訪問級別

在某些情況下 , 實體的根據默認環境所擁有的默認訪問級別根據比較與其相關的實體的訪問級別所得出訪問級別 不匹配 時, 需要 顯式指定 訪問級別

單一目標(Single-Target)應用程序的訪問級別

當你 編寫一個單一目標應用程序 時,你的 應用中的代碼通常是自包含在應用中 并且 不需要給其他應用的模塊使用.
默認的internal訪問級別 已經滿足了這個需求,所以我們不需要設置訪問級別
然而你可以 為了對模塊中的其他代碼隱瞞一些功能的實現細節標記你的一部分代碼為private

框架(Frameworks)的訪問級別

當你 開發框架 時,把框架的公開接口設置為public 以便它可以 被其他模塊(例如一個應用導入框架的應用) 查看和訪問.

這個公開的接口 就是 這個框架的 應用程序接口(Application Programming Interface)

注意

你的 框架的任何內部實現細節 仍可以使用 默認訪問級別internal , 或者 可被標記private 如果你想要 對框架內部的其他代碼部分隱藏 的話.

如果你想要 使一個實體變成你框架的API的一部分 ,你需要將它 標記為public

單元測試目標(Unit Test Targets)的訪問級別

當你的 應用程序包含Unit Test Target 時,你的 **應用程序中的代碼需要被提供給該模塊 ** 以便被測試.
默認 情況下只有標記為 public的實體 才可以 被其他模塊 訪問。

然而一個 單元測試目標(unit test target) 可以 訪問任何internal實體 ,如果你使用 @testable屬性 來標記一個 產品模塊導入聲明 并將其編譯為具有測試功能的產品模塊

訪問控制語法

通過 修飾符public,internal,private定義實體的訪問級別
eg.
<pre><code>`
public class SomePublicClass {}
internal class SomeInternalClass {}
private class SomePrivateClass {}

public var somePublicVariable = 0
internal let someInternalConstant = 0
private func somePrivateFunction() {}
`</code></pre>

若無其他規定,默認的訪問級別為internal
這意味著SomeInternalClass和someInternalConstant在不使用修飾符顯式聲明訪問級別的情況下仍然擁有internal訪問級別:
<pre><code>class SomeInternalClass {} //隱式的訪問級別 internal var someInternalConstant = 0 //隱式的訪問級別 internal</code></pre>

自定義類型

如果想為一個自定義類型指定訪問級別,在定義類型時進行指定即可
新類型只能在它的訪問級別允許的范圍內被訪問
例如,你定義了一個private的類,那這個類就只能在定義它的源文件中可以被用作一個屬性,函數參數或者返回值的類型

類型的訪問控制的級別 也會 影響到 類型的成員(屬性,方法,構造器,下標)的默認訪問級別

  • 如果 將類型指定為public, 那么 該類型的所有成員的默認訪問級別將是internal
    (但是可以單獨設置某個成員為internal或private訪問級別)

  • 如果 將類型指定為internal(或者不明確指定訪問級別而使用默認的internal) ,那么 該類型的所有成員的默認訪問級別將是internal
    (但是可以單獨設置某個成員為private訪問級別)

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

注意

上面提到,一個public類型的所有成員的訪問級別 默認為internal , 而不是public

如果你想 將類型的某個成員指定為public級別 ,那么你 必須顯式指定
這樣確保了 該類型的公開的API是你選定公開的 ,并 避免了將一個類型的internal內容作為公開的API來呈現 的錯誤

<pre><code>`
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 類成員
}
`</code></pre>

元組類型

元組類型的訪問級別元組中使用的多個類型的訪問級別最嚴格的那個訪問級別
例如,如果你構建了一個包含兩種不同類型的元組,其中一個類型為internal級別,另一個類型為private級別,那么這個元組的訪問級別為private

注意

元組不像類,結構體,枚舉,函數那樣有單獨的定義。

元組的訪問級別在它被使用時會被自動推斷出來,并且不能被顯式的指定

函數類型

函數的訪問級別函數的參數類型和返回值類型的訪問級別最嚴格的那個訪問級別

如果這種 經過比較得出的訪問級別函數根據環境所擁有的默認訪問級別 不匹配,那么就需要 顯式地指定該函數的訪問級別 作為函數定義的一部分

下面的例子定義了一個名為someFunction的全局函數,并且沒有明確地指定其訪問級別.
也許你會認為該函數擁有默認的訪問級別internal,但事實并非如此
事實上,如果按下面這種寫法,代碼將無法通過編譯
<pre><code>func someFunction() -> (SomeInternalClass, SomePrivateClass) { // 此處是函數實現部分 }</code></pre>

這個函數的返回類型是一個元組,該元組中包含兩個自定義的類(在前面定義過),其中一個類的訪問級別是internal,另一個類的訪問級別是private
根據元組訪問級別的總原則,該元組的訪問級別是private
所以你必須使用private修飾符明確指定該函數的訪問級別
<pre><code>private func someFunction() -> (SomeInternalClass, SomePrivateClass) { // 此處是函數實現部分 }</code></pre>

將該函數指定為public或internal,或者使用默認的訪問級別internal都是錯誤的,因為如果把該函數設置為public或internal級別可能會無法訪問返回值類型-private類。

枚舉類型

枚舉的成員的訪問級別枚舉類型的訪問級別 相同
不能為枚舉成員 指定 不同的訪問級別

如下,枚舉類型CompassPoint被顯式指定為public級別,它的成員North,South,East,West的訪問級別同樣也是public
<pre><code>public enum CompassPoint { case North case South case East case West }</code></pre>

原始值(rawValue)和關聯值(associatedValue)

枚舉定義中的任何原始值或關聯值的類型的訪問級別高于或等于枚舉類型的訪問級別
例如,你不能使用private級別的類型作為一個internal訪問級別的枚舉類型的原始值類型

嵌套類型

private類型中定義的嵌套類型自動擁有private訪問級別
public或者internal級別類型中定義的嵌套類型自動擁有internal訪問級別
如果想讓public級別類型中的嵌套類型擁有public訪問級別,那么需要明確指定該嵌套類型的訪問級別為public

子類

可以繼承 任何可以在當前上下文中被訪問的類

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

例如,你不能編寫一個父類的訪問級別為internal的public子類

此外,你可以在符合一定的訪問級別的條件下重寫任意可獲得的類成員(方法,屬性,構造器,下標等)

重寫 可以使 繼承來的類成員 相比于父類中的版本 更容易被訪問

如下所示,類A的訪問級別是public,包含一個訪問級別為private方法someMethod().類B繼承自類A,訪問級別為internal.
但是在類B中重寫了類A中訪問級別為private的方法someMethod(),并指定為internal級別(比someMethod()的原是實現的訪問級別更高)
<pre><code>`
public class A {
private func someMethod() {}
}

internal class B: A {
override internal func someMethod() {}
}
`</code></pre>

只要是在被允許的訪問級別上下文中,一個比父類成員訪問權限更高的子類成員調用該父類成員是有效的
<pre><code>`
public class A {
private func someMethod() {}
}

internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}
`</code></pre>

因為父類A和子類B定義在同一源文件中,所以在子類B的someMethod()方法中調用super.someMethod()是有效的

常量,變量,屬性,下標

常量,變量,屬性不能擁有比它們的類型更高的訪問級別。
例如,定義一個的public訪問級別的屬性,但是其類型的訪問級別為private是無效的
同樣,一個下標也不能擁有比它的索引類型或返回值類型更高的訪問級別。

如果一個常量,變量,屬性,下標的類型是private類型級別,那么它們也必須被顯式指定指定為private訪問級別
<pre><code>private var privateInstance = SomePrivateClass()</code></pre>

getter和setter

常量,變量,屬性,下標的getter和setter自動接收和 它們所屬類型的相同的訪問級別

與getter的訪問級別相比 你可以 給予setter一個更低的訪問級別

這樣就 可以限定變量,屬性或下標的讀寫范圍

你可以通過 在 varsubscript 之前寫 private(set)internal(set) 來設置一個更低的訪問級別

注意

此規則適用于 存儲型屬性 和 計算型屬性.

即使你不顯式地指定存儲型屬性的getter和setter,Swift仍然會隱式地生成getter和setter為你提供用對該屬性的后備存儲的訪問.

使用private(set)和internal(set)可以改變這種setter的訪問級別,這對計算型屬性的顯式的setter也是如此

如下所示定義了一個名為TrackedString的結構體,它記錄了一個字符串屬性被修改的次數
<pre><code>`
struct TrackedString //隱式的internal結構體
{
private(set) var numberOfEdits = 0
//顯式的private結構體成員的setter和隱式的internal結構體成員的getter

var value: String = "" //隱式的internal結構體成員
{
    didSet 
    {
        numberOfEdits += 1
    }
}

}
`</code></pre>

TrackedString結構體定義了一個存儲String值的屬性value,并將初始值設為""(一個空字符串).該結構體還定義了另一個存儲Int值的屬性numberOfEdits,用于記錄屬性value被修改的次數.記錄修改通過屬性value的didSet觀察器實現,每當給 value賦新值時numberOfEdits的值就會加一。

結構體TrackedString和屬性value均沒有顯式指定訪問級別,所以它們都擁有默認的訪問級別internal.但是該結構體的numberOfEdits屬性使用了private(set)修飾符,這意味著numberOfEdits屬性只能在定義該結構體的源文件中賦值.
numberOfEdits屬性的getter仍然有默認的internal訪問級別,但是他的setter只能用于結構體TrackedString的定義所在的源文件中.這使結構體TrackedString的numberOfEdits屬性只在當前的源文件中是可讀寫的,但當在同一個模塊中的其他源文件使用時,將該屬性呈現為只讀屬性.

如果你實例化TrackedString結構體,并多次對value屬性的值進行修改,你就會看到numberOfEdits的值會隨著修改次數而變化:
<pre><code>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”</pre></code>

雖然你可以在其他的源文件中實獲取到numberOfEdits屬性的值,但是你不能對其進行賦值.這一限制保護了該記錄功能的實現細節,同時還提供了方便的訪問方式.

注意如果有必要,你可以為getter和setter顯式指定訪問級別.下面的例子將TrackedString結構體明確指定為了public訪問級別.因此結構體的成員擁有默認的訪問級別internal.你可以結合public和private(set)修飾符把結構體中numberOfEdits屬性的getter的訪問級別設置為public,而setter的訪問級別設置為private:
<pre><code>public struct TrackedString { public private(set) var numberOfEdits = 0 public var value: String = "" { didSet { numberOfEdits += 1 } } public init() {} }</code></pre>

構造器

自定義構造器的訪問級別低于或等于它們初始化的類型的訪問級別
唯一的例外是 必要構造器的訪問級別必須和所屬類型的訪問級別相同

如同函數或方法的參數,構造器參數的訪問級別也不能低于構造器本身的訪問級別.

默認構造器

如同在默認構造器所述的,Swift會為結構體和類提供一個默認的無參數的構造器,只要它們為所有存儲型屬性設置了默認初始值,并且未提供自定義的構造器.

默認構造器的訪問級別與所屬類型的訪問級別相同,除非類型的訪問級別是public.
如果一個類型被指定為public級別,那么默認構造器的訪問級別將為internal.
如果你希望一個public級別的類型也能在其他模塊中使用這種無參數的默認構造器,你必須提供一個public訪問級別的無參數構造器.

結構體默認的逐一成員構造器

如果結構體中任意存儲型屬性的訪問級別為private,那么該結構體默認的成員逐一構造器的訪問級別就是private,否則,這種構造器的訪問級別是internal.

如同前面提到的默認構造器,如果你希望一個public級別的有默認逐一成員構造器結構體也能在其他模塊中被使用,你必須自己提供一個public訪問級別的逐一成員構造器.

協議

如果想 **為一個協議類型明確地指定訪問級別 , 在定義協議時指定 **即可.
這能使你 創建 只能在特定的代碼環境中 被遵循的協議.
(private級別的protocol,只能被同一源文件中的類型遵循
internal級別的protocol,只能被同一模塊中的類型遵循
public級別的protocol,可以被同一模塊中和其他模塊中的類型遵循)

你不能對協議中的要求設置訪問級別(如果設置了訪問級別標識符,Xcode會報錯:'xxx'modifier cannot be used in protocols,并提示你刪除).
這樣能確保該協議的所有要求都可以被任意遵循者都實現

協議繼承

如果定義了一個繼承自其他協議的新協議,新協議擁有的訪問級別最高也只能和被繼承協議的訪問級別相同.
例如,你不能將繼承自internal的協議的新協議定義為public的協議.

遵循協議

當一個類型能夠遵循的一個指定訪問級別的協議時 , 協議的要求在類型中的實現的訪問級別 必須高于或等于 該類型的訪問級別 和 該協議的訪問級別最低的訪問級別 并且 低于或等于 該類型的訪問級別 和 該協議的訪問級別最高的訪問級別

在類型的定義中實現協議的要求時,Xcode會在要求的名稱前補全相應的修飾符為 該類型的訪問級別 和 該協議的訪問級別最低的訪問級別

注意
Swift和Objective-C一樣,協議的一致性是全局的,在同一程序中一個類型不可能用兩種不同的方式遵循同一個協議.

擴展

你可以在類,結構體,枚舉的訪問級別允許的代碼環境中對類,結構體,枚舉進行擴展.
擴展中添加的成員具有和原始類型中的成員默認一致的訪問級別.
如果你擴展了一個public或者internal類型,擴展中的任何新成員將具有默認的internal訪問級別.如果你擴展了一個private類型,擴展中任意新成員則擁有默認的private訪問級別.

或者,你可以明確標記擴展的訪問級別(例如,private extension)來給該擴展中的所有成員指定一個新的默認訪問級別.
這個新的默認訪問級別仍然可以在擴展中被個別的類型成員的顯式的訪問級別覆蓋

<pre>
<code>
`
internal extension Int//只為擴展中所有新的成員指定默認訪問級別internal
{
var valueInString :String//默認的internal
{
get
{
return String(self)
}
}

private var doubledValue :Int//用顯式的private覆蓋隱式的internal
{
    get
    {
        return self * 2
    }
}

}
`
</code>
</pre>

通過擴展遵循協議

如果你想通過擴展使某類型遵循協議,你不能在顯式的為該擴展指定訪問級別.(Xcode報錯:'xxx' modifier cannot be used with extensions that declare protocol conformances)
在 協議-遵循協議 中有描述:

當一個類型能夠遵循的一個指定訪問級別的協議時 , 協議的要求在類型中的實現的訪問級別 必須高于或等于 該類型的訪問級別 和 該協議的訪問級別 中 最低的訪問級別 并且 低于或等于 該類型的訪問級別 和 該協議的訪問級別 中 最高的訪問級別

在類型的定義中實現協議的要求時,Xcode會在要求的名稱前補全相應的修飾符為 該類型的訪問級別 和 該協議的訪問級別 中 最低的訪問級別

因此 當某類型想要通過擴展來遵循協議時 , 協議的要求在該類型的此擴展中的實現的訪問級別 也取決于 協議的訪問級別 , 所以 如果你想通過擴展使某類型遵循協議,你不能在顯式的為該擴展指定訪問級別

泛型

泛型類型或泛型函數的訪問級別其本身的訪問級別類型參數的類型約束的訪問級別最低的訪問級別

類型別名

你定義的任何類型別名都會被當作獨特的類型,以便于進行訪問控制.
類型別名的訪問級別低于或等于其表示的類型的訪問級別.
例如,private級別的類型別名可以作為public,internal,private類型的別名,但是public級別的類型不能作為internal或private類型的別名。

注意
這條規則也適用于 為滿足協議一致性而將類型別名用于關聯類型的情況 。


OVER
我自己都看吐了~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容