Swift學習之 存取控制

來源:互聯網
上載者:User

標籤:

存取控制可以限定你在源檔案或模組中存取碼的層級,也就是說可以控制哪些代碼你可以訪問,哪些代碼你不能訪問。這個特性可以讓我們隱藏功能實現的一些細節,並且可以明確的指定我們提供給其他人的介面中哪些部分是他們可以使用的,哪些是他們看不到的。
你可以明確的給類、結構體、枚舉、設定存取層級,也可以給屬性、函數、初始化方法、基本類型、下標索引等設定存取層級。協議也可以被限定在一定的範圍內使用,包括協議裡的全域常量、變數和函數。
在提供了不同存取層級的同時,Swift 並沒有規定我們要在任何時候都要在代碼中明確指定存取層級。其實,如果我們作為獨立開發人員在開發我們自己的 app,而不是在開發一些Framework的時候,我們完全可以不用明確的指定代碼的存取層級。
注意:為方便起見,在代碼中可以設定存取層級的它們(屬性、基本類型、函數等)在下面的章節中我們稱之為“實體”。

模組和源檔案
Swift 中的存取控制模型基於模組和源檔案這兩個概念。
模組指的是Framework或App bundle。在 Swift 中,可以用import關鍵字引入自己的工程。
在 Swift 中,Framework或App bundle被作為模組處理。如果你是為了實現某個通用的功能,或者是為了封裝一些常用方法而將代碼打包成Framework,這個Framework在 Swift 中就被稱為模組。不論它被引入到某個 App 工程或者其他的Framework,它裡面的一切(屬性、函數等)都屬於這個模組。
源檔案指的是 Swift 中的Swift File,就是編寫 Swift 代碼的檔案,它通常屬於一個模組。通常一個源檔案包含一個類,在類中又包含函數、屬性等類型。

存取層級
Swift 提供了三種不同的存取層級。這些存取層級相對於源檔案中定義的實體,同時也相對於這些源檔案所屬的模組。
? Public:可以訪問自己模組或應用中源檔案裡的任何實體,別人也可以訪問引入該模組中源檔案裡的所有實體。通常情況下,某個介面或Framework是可以被任何人使用時,你可以將其設定為public層級。
? Internal:可以訪問自己模組或應用中源檔案裡的任何實體,但是別人不能訪問該模組中源檔案裡的實體。通常情況下,某個介面或Framework作為內部結構使用時,你可以將其設定為internal層級。
? Private:只能在當前源檔案中使用的實體,稱為私人實體。使用private層級,可以用作隱藏某些功能的實現細節。
Public為最進階存取層級,Private為最低級存取層級。

存取層級的使用原則
在 Swift 中,存取層級有如下使用原則:存取層級統一性。 比如說:
? 一個public存取層級的變數,不能將它的類型定義為internal和private的類型。因為變數可以被任何人訪問,但是定義它的類型不可以,所以這樣就會出現錯誤。
? 函數的存取層級不能高於它的參數、傳回型別的存取層級。因為如果函數定義為public而參數或者傳回型別定義為internal或private,就會出現函數可以被任何人訪問,但是它的參數和傳回型別不可以,同樣會出現錯誤。

預設存取層級
代碼中的所有實體,如果你不明確的定義其存取層級,那麼它們預設為internal層級。在大多數情況下,我們不需要明確的設定實體的存取層級,因為我們大多數時候都是在開發一個 App bundle。

單目標應用程式的存取層級
當你編寫一個單目標應用程式時,該應用的所有功能都是為該應用服務,不需要提供給其他應用或者模組使用,所以我們不需要明確設定存取層級,使用預設的存取層級internal即可。但是如果你願意,你也可以使用private層級,用於隱藏一些功能的實現細節。

Framework的存取層級
當你開發Framework時,就需要把一些實體定義為public層級,以便其他人匯入該Framework後可以正常使用其功能。這些被你定義為public的實體,就是這個Framework的API。
注意:Framework的內部實現細節依然可以使用預設的internal層級,或者也可以定義為private層級。只有你想將它作為 API 的實體,才將其定義為public層級。

存取控制文法
通過修飾符public、internal、private來聲明實體的存取層級:
public class SomePublicClass {}
internal class SomeInternalClass {}
private class SomePrivateClass {}

public var somePublicVariable = 0
internal let someInternalConstant = 0
private func somePrivateFunction() {}
除非有特殊的說明,否則實體都使用預設的存取層級internal,可以查閱預設存取層級這一節。這意味著SomeInternalClass和someInternalConstant不用明確的使用修飾符聲明存取層級,但是他們任然擁有隱式的存取層級internal:
class SomeInternalClass {} // 隱式存取層級 internal
var someInternalConstant = 0 // 隱式存取層級 internal

自訂類型
如果你想為一個自訂類型指定一個明確的存取層級,那麼你要明確一點。那就是你要確保新類型的存取層級和它實際的範圍相匹配。比如說,如果某個類裡的屬性、函數、傳回值它們的範圍僅在當前的源檔案中,那麼你就可以將這個類申明為private類,而不需要申明為public或者internal類。
類的存取層級也可以影響到類成員(屬性、函數、初始化方法等)的預設存取層級。如果你將類申明為private類,那麼該類的所有成員的預設存取層級也會成為private。如果你將類申明為public或者internal類(或者不明確的指定存取層級,而使用預設的internal存取層級),那麼該類的所有成員的存取層級是internal。
注意:上面提到,一個public類的所有成員的存取層級預設為internal層級,而不是public層級。如果你想將某個成員申明為public層級,那麼你必須使用修飾符明確的申明該成員。這樣做的好處是,在你定義公用介面API的時候,可以明確的選擇哪些屬性或方法是需要公開的,哪些是內部使用的,可以避免將內部使用的屬性方法公開成公用API的錯誤。
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 類成員
}

元群組類型
元組的存取層級使用是所有類型的存取層級使用中最為嚴謹的。比如說,如果你構建一個包含兩種不同類型元素的元組,其中一個元素類型的存取層級為internal,另一個為private層級,那麼這個元組的存取層級為private。也就是說元組的存取層級遵循它裡面元組中最低級的存取層級。
注意:元組不同於類、結構體、枚舉、函數那樣有單獨的定義。元組的存取層級是在它被使用時自動推匯出的,而不是明確的申明。

函數類型
函數的存取層級需要根據該函數的參數類型存取層級、傳回型別存取層級得出。如果根據參數類型和傳回型別得出的函數存取層級不符合上下文,那麼就需要明確的申明該函數的存取層級。
下面的例子中定義了一個全域函數名為someFunction,並且沒有明確的申明其存取層級。你也許會認為該函數應該擁有預設的存取層級internal,但事實並非如此。事實上,如果按下面這種寫法,編譯器是無法編譯通過的:
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// function implementation goes here
}
我們可以看到,這個函數的傳回型別是一個元組,該元組中包含兩個自訂的類(可查閱自訂類型)。其中一個類的存取層級是internal,另一個的存取層級是private,所以根據元組存取層級的原則,該元組的存取層級是private(元組的存取層級遵循它裡面元組中最低級的存取層級)。
因為該函數傳回型別的存取層級是private,所以你必須使用private修飾符,明確的申請該函數:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// function implementation goes here
}
將該函數申明為public或internal,或者使用預設的存取層級internal都是錯誤的,因為如果把該函數當做public或internal層級來使用的話,是無法得到private層級的傳回值的。

枚舉類型
枚舉中成員的存取層級繼承自該枚舉,你不能為枚舉中的成員指定存取層級。
比如下面的例子,枚舉CompassPoint被明確的申明為public層級,那麼它的成員North,South,East,West的存取層級同樣也是public:
public enum CompassPoint {
case North
case South
case East
case West
}

原始值和關聯值
用於枚舉定義中的任何原始值,或關聯的實值型別必須有一個存取層級,至少要高於枚舉的存取層級。比如說,你不能在一個internal存取層級的枚舉中定義private層級的原始實值型別。

巢狀型別
如果在private層級的類型中定義巢狀型別,那麼該巢狀型別就自動擁有private存取層級。如果在public或者internal層級的類型中定義巢狀型別,那麼該巢狀型別自動擁有internal存取層級。如果想讓巢狀型別擁有public存取層級,那麼需要對該巢狀型別進行明確的存取層級申明。

子類
子類的存取層級不得高於父類的存取層級。比如說,父類的存取層級是internal,子類的存取層級就不能申明為public。
此外,在滿足子類不高於父類存取層級以及遵循各存取層級範圍(即模組或源檔案)的前提下,你可以重寫任意類成員(方法、屬性、初始化方法、下標索引等)。
如果我們無法直接存取某個類中的屬性或函數等,那麼可以繼承該類,從而可以更容易的訪問到該類的類成員。下面的例子中,類A的存取層級是public,它包含一個函數someMethod,存取層級為private。類B繼承類A,並且存取層級申明為internal,但是在類B中重寫了類A中存取層級為private的方法someMethod,並重新申明為internal層級。通過這種方式,我們就可以訪問到某類中private層級的類成員,並且可以重新申明其存取層級,以便其他人使用:
public class A {
private func someMethod() {}
}

internal class B: A {
override internal func someMethod() {}
}
只要滿足子類不高於父類存取層級以及遵循各存取層級範圍的前提下(即private的範圍在同一個源檔案中,internal的範圍在同一個模組下),我們甚至可以在子類中,用子類成員訪問父類成員,哪怕父類成員的存取層級比子類成員的要低:
public class A {
private func someMethod() {}
}

internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}
因為父類A和子類B定義在同一個源檔案中,所以在類B中可以在重寫的someMethod方法中調用super.someMethod()。

常量、變數、屬性、下標
常量、變數、屬性不能擁有比它們的類型更高的存取層級。比如說,你定義一個public層級的屬性,但是它的類型是private層級的,這是編譯器不允許的。同樣,下標也不能擁有比索引類型或傳回型別更高的存取層級。
如果常量、變數、屬性、下標索引的定義類型是private層級的,那麼它們必須要明確的申明存取層級為private:
private var privateInstance = SomePrivateClass()

Getter和Setter
常量、變數、屬性、下標索引的Getters和Setters的存取層級繼承自它們所屬成員的存取層級。
Setter的存取層級可以低於對應的Getter的存取層級,這樣就可以控制變數、屬性或下標索引的讀寫權限。在var或subscript定義範圍之前,你可以通過private(set)或internal(set)先為它門的寫入權限申明一個較低的存取層級。
注意:這個規定適用於用作儲存的屬性或用作計算的屬性。即使你不明確的申明儲存屬性的Getter、Setter,Swift也會隱式的為其建立Getter和Setter,用於對該屬性進行讀取操作。使用private(set)和internal(set)可以改變Swift隱式建立的Setter的存取層級。在計算屬性中也是同樣的。
下面的例子中定義了一個結構體名為TrackedString,它記錄了value屬性被修改的次數:
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = “” {
didSet {
numberOfEdits++
}
}
}
TrackedString結構體定義了一個用於儲存的屬性名稱為value,類型為String,並將初始化值設為”“(即一個Null 字元串)。該結構體同時也定義了另一個用於儲存的屬性名稱為numberOfEdits,類型為Int,它用於記錄屬性value被修改的次數。這個功能的實現通過屬性value的didSet方法實現,每當給value賦新值時就會調用didSet方法,給numberOfEdits加一。
結構體TrackedString和它的屬性value均沒有明確的申明存取層級,所以它們都擁有預設的存取層級internal。但是該結構體的numberOfEdits屬性使用private(set)修飾符進行申明,這意味著numberOfEdits屬性只能在定義該結構體的源檔案中賦值。numberOfEdits屬性的Getter依然是預設的存取層級internal,但是Setter的存取層級是private,這表示該屬性只有在當前的源檔案中是可讀可寫的,在當前源檔案所屬的模組中它只是一個可讀的屬性。
如果你執行個體化TrackedString結構體,並且多次對value屬性的值進行修改,你就會看到numberOfEdits的值會隨著修改次數更改:
var stringToEdit = TrackedString()
stringToEdit.value = “This string will be tracked.”
stringToEdit.value += ” This edit will increment numberOfEdits.”
stringToEdit.value += ” So will this one.”
println(“The number of edits is (stringToEdit.numberOfEdits)”)
// prints “The number of edits is 3”
雖然你可以在其他的源檔案中執行個體化該結構體並且擷取到numberOfEdits屬性的值,但是你不能對其進行賦值。這樣就能很好的告訴使用者,你只管使用,而不需要知道其實現細節。

初始化
我們可以給自訂的初始化方法指定存取層級,但是必須要低於或等於它所屬類的存取層級。但如果該初始化方法是必須要使用的話,那它的存取層級就必須和所屬類的存取層級相同。
如同函數或方法參數,初始化方法參數的存取層級也不能低於初始化方法的存取層級。

預設初始化方法
Swift為結構體、類都提供了一個預設的無參初始化方法,用於給它們的所有屬性提供賦值操作,但不會給出具體值。預設初始化方法可以參閱Default Initializers。預設初始化方法的存取層級與所屬類型的存取層級相同。
注意:如果一個類型被申明為public層級,那麼預設的初始化方法的存取層級為internal。如果你想讓無參的初始化方法在其他模組中可以被使用,那麼你必須提供一個具有public存取層級的無參初始化方法。

結構體的預設成員初始化方法
如果結構體中的任一儲存屬性的存取層級為private,那麼它的預設成員初始化方法存取層級就是private。儘管如此,結構體的初始化方法的存取層級依然是internal。
如果你想在其他模組中使用該結構體的預設成員初始化方法,那麼你需要提供一個存取層級為public的預設成員初始化方法。

協議
如果你想為一個協議明確的申明存取層級,那麼有一點需要注意,就是你要確保該協議只在你申明的存取層級範圍中使用。
協議中的每一個必須要實現的函數都具有和該協議相同的存取層級。這樣才能確保該協議的使用者可以實現它所提供的函數。
注意:如果你定義了一個public存取層級的協議,那麼實現該協議提供的必要函數也會是public的存取層級。這一點不同於其他類型,比如,public存取層級的其他類型,他們成員的存取層級為internal。

協議繼承
如果定義了一個新的協議,並且該協議繼承了一個已知的協議,那麼新協議擁有的存取層級最高也只和被繼承協議的存取層級相同。比如說,你不能定義一個public的協議而去繼承一個internal的協議。

協議一致性
類可以採用比自身存取層級低的協議。比如說,你可以定義一個public層級的類,可以讓它在其他模組中使用,同時它也可以採用一個internal層級的協議,並且只能在定義了該協議的模組中使用。
採用了協議的類的存取層級遵循它本身和採用協議中最低的存取層級。也就是說如果一個類是public層級,採用的協議是internal層級,那個採用了這個協議後,該類的存取層級也是internal。
如果你採用了協議,那麼實現了協議必須的方法後,該方法的存取層級遵循協議的存取層級。比如說,一個public層級的類,採用了internal層級的協議,那麼該類實現協議的方法至少也得是internal。
注意:在Swift中和Objective-C中一樣,協議的一致性保證了一個類不可能在同一個程式中用不同的方法採用同一個協議。

擴充
你可以在條件允許的情況下對類、結構體、枚舉進行擴充。擴充成員應該具有和原始類成員一致的存取層級。比如你擴充了一個公用類型,那麼你新加的成員應該具有和原始成員一樣的預設的internal存取層級。
或者,你可以明確申明擴充的存取層級(比如使用private extension)給該擴充內所有成員指定一個新的預設存取層級。這個新的預設存取層級仍然可以被單獨成員所指定的存取層級所覆蓋。

協議的擴充
如果一個擴充採用了某個協議,那麼你就不能對該擴充使用存取層級修飾符來申明了。該擴充中實現協議的方法都會遵循該協議的存取層級。

泛型
泛型型別或泛型函數的存取層級遵循泛型型別、函數本身、泛型型別參數三者中存取層級最低的層級。

類型別名
任何被你定義的類型別名都會被視作為不同的類型,這些類型用於存取控制。一個類型別名的存取層級可以低於或等於這個類型的存取層級。比如說,一個private層級的類型別名可以設定給一個public、internal、private的類型,但是一個public層級的類型別名只能設定給一個public層級的類型,不能設定給internal或private的類類型。
注意:這條規則也適用於為滿足協議一致性而給相互關聯類型命名別名。

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

Swift學習之 存取控制

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.