Swift 新特性 – 訪問控制(Access Control)

DevTalking發表於2015-06-15

蘋果在釋出了Xcode 6 Bate 4後為Swift新增了新的特性–訪問控制(Access Control),並且更新了The Swift Programming Language文件,我抽空把這篇文件翻譯了一下,下面讓我們來詳細瞭解一下Access Control。

訪問控制

訪問控制可以限定你在原始檔或模組中訪問程式碼的級別,也就是說可以控制哪些程式碼你可以訪問,哪些程式碼你不能訪問。這個特性可以讓我們隱藏功能實現的一些細節,並且可以明確的指定我們提供給其他人的介面中哪些部分是他們可以使用的,哪些是他們看不到的。

你可以明確的給類、結構體、列舉、設定訪問級別,也可以給屬性、函式、初始化方法、基本型別、下標索引等設定訪問級別。協議也可以被限定在一定的範圍內使用,包括協議裡的全域性常量、變數和函式。

在提供了不同訪問級別的同時,Swift並沒有規定我們要在任何時候都要在程式碼中明確指定訪問級別。其實,如果我們作為獨立開發者在開發我們自己的app,而不是在開發一些Framework的時候,我們完全可以不用明確的指定程式碼的訪問級別。

注意:為方便起見,在程式碼中可以設定訪問級別的它們(屬性、基本型別、函式等)在下面的章節中我們稱之為“實體”。

模組和原始檔

Swift中的訪問控制模型基於模組和原始檔這兩個概念。

模組指的是FrameworkApp bundle。在Swift中,可以用import關鍵字引入自己的工程。

在Swift中,FramewordkApp bundle被作為模組處理。如果你是為了實現某個通用的功能,或者是為了封裝一些常用方法而將程式碼打包成Framework,這個Framework在Swift中就被稱為模組。不論它被引入到某個App工程或者其他的Framework,它裡面的一切(屬性、函式等)都屬於這個模組。

原始檔指的是Swift中的Swift File,就是編寫Swift程式碼的檔案,它通常屬於一個模組。通常一個原始檔包含一個,在中又包含函式屬性等型別。

訪問級別

Swift提供了三種不同的訪問級別。這些訪問級別相對於原始檔中定義的實體,同時也相對於這些原始檔所屬的模組。

  • Public:可以訪問自己模組或應用中原始檔裡的任何實體,別人也可以訪問引入該模組中原始檔裡的所有實體。通常情況下,某個介面或Framework是可以被任何人使用時,你可以將其設定為public級別。
  • Internal:可以訪問自己模組或應用中原始檔裡的任何實體,但是別人不能訪問該模組中原始檔裡的實體。通常情況下,某個介面或Framework作為內部結構使用時,你可以將其設定為internal級別。
  • Private:只能在當前原始檔中使用的實體,稱為私有實體。使用private級別,可以用作隱藏某些功能的實現細節。

Public為最高階訪問級別,Private為最低階訪問級別。

訪問級別的使用原則

在Swift中,訪問級別有如下使用原則:訪問級別統一性。
比如說:

  • 一個public訪問級別的變數,不能將它的型別定義為internalprivate的型別。因為變數可以被任何人訪問,但是定義它的型別不可以,所以這樣就會出現錯誤。
  • 函式的訪問級別不能高於它的引數、返回型別的訪問級別。因為如果函式定義為public而引數或者返回型別定義為internalprivate,就會出現函式可以被任何人訪問,但是它的引數和返回型別不可以,同樣會出現錯誤。

預設訪問級別

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

單目標應用程式的訪問級別

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

Framework的訪問級別

當你開發Framework時,就需要把一些實體定義為public級別,以便其他人匯入該Framework後可以正常使用其功能。這些被你定義為public的實體,就是這個Framework的API。

注意:Framework的內部實現細節依然可以使用預設的internal級別,或者也可以定義為private級別。只有你想將它作為API的實體,才將其定義為public級別。

訪問控制語法

通過修飾符publicinternalprivate來宣告實體的訪問級別:

public class SomePublicClass {}
internal class SomeInternalClass {}
private class SomePrivateClass {}

public var somePublicVariable = 0
internal let someInternalConstant = 0
private func somePrivateFunction() {}

除非有特殊的說明,否則實體都使用預設的訪問級別internal,可以查閱預設訪問級別這一節。這意味著SomeInternalClasssomeInternalConstant不用明確的使用修飾符宣告訪問級別,但是他們任然擁有隱式的訪問級別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
}

將該函式申明為publicinternal,或者使用預設的訪問級別internal都是錯誤的,因為如果把該函式當做publicinternal級別來使用的話,是無法得到private級別的返回值的。

列舉型別

列舉中成員的訪問級別繼承自該列舉,你不能為列舉中的成員指定訪問級別。
比如下面的例子,列舉CompassPoint被明確的申明為public級別,那麼它的成員NorthSouthEastWest的訪問級別同樣也是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

常量、變數、屬性、下標索引的GettersSetters的訪問級別繼承自它們所屬成員的訪問級別。

Setter的訪問級別可以低於對應的Getter的訪問級別,這樣就可以控制變數、屬性或下標索引的讀寫許可權。在varsubscript定義作用域之前,你可以通過private(set)internal(set)先為它門的寫許可權申明一個較低的訪問級別。

注意:這個規定適用於用作儲存的屬性或用作計算的屬性。即使你不明確的申明儲存屬性的GetterSetter,Swift也會隱式的為其建立GetterSetter,用於對該屬性進行讀取操作。使用private(set)internal(set)可以改變Swift隱式建立的Setter的訪問級別。在計算屬性中也是同樣的。

下面的例子中定義了一個結構體名為TrackedString,它記錄了value屬性被修改的次數:

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

TrackedString結構體定義了一個用於儲存的屬性名為value,型別為String,並將初始化值設為""(即一個空字串)。該結構體同時也定義了另一個用於儲存的屬性名為numberOfEdits,型別為Int,它用於記錄屬性value被修改的次數。這個功能的實現通過屬性valuedidSet方法實現,每當給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級別的型別別名可以設定給一個publicinternalprivate的型別,但是一個public級別的型別別名只能設定給一個public級別的型別,不能設定給internalprivate的類型別。

注意:這條規則也適用於為滿足協議一致性而給相關型別命名別名。

本文首發地址:Swift新特性 — 訪問控制(Access Control)

相關文章