Swift 存取控制


存取控制可以限定其他原始檔案或模組中程式碼對你程式碼的存取等級。

你可以明確地為單一類型(類別、結構體、枚舉)設定存取級別,也可以給這些類型的屬性、函數、初始化方法、基本類型、下標索引等設定存取級別。

協定也可以限定在一定的範圍內使用,包括協定裡的全域常數、變數和函數。

存取控制是基於模組與來源檔案。

模組指的是以獨立單元建置和發佈的Framework或Application。在Swift 中的一個模組可以使用import關鍵字引入另一個模組。

原始檔是單一原始碼文件,它通常屬於一個模組, 原始檔可以包含多個類別和函數 的定義。

Swift 為程式碼中的實體提供了三種不同的存取等級:public、internal、private。

存取等級定義
#Public可以存取自己模組中來源文件裡的任何實體,別人也可以透過引入該模組來存取原始檔案裡的所有實體。
Internal:可以存取自己模組中原始檔案裡的任何實體,但是別人不能存取該模組中原始檔案裡的實體。
Private只能在目前原始檔中使用的實體,稱為私有實體。

public為最高級存取級別,private為最低級存取級別。

語法

透過修飾符public、internal、private來宣告實體的存取層級:

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

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

除非有特殊的說明,否則實體都使用預設的存取等級internal 。


函數類型存取權

函數的存取等級需要根據該函數的參數類型和傳回類型的存取等級得出。

下面的範例定義了一個名為someFunction全域函數,並且沒有明確地申明其存取等級。

func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // 函数实现
}

函數中其中一個類別 SomeInternalClass 的存取等級是internal,另一個 SomePrivateClass 的存取等級是private。所以根據元組存取等級的原則,該元組的存取等級是private。

因為函數傳回類型的存取等級是private,所以你必須使用private修飾符,明確的宣告該函數:
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
    // 函数实现
}

將該函數申明為public或internal,或是使用預設的存取等級internal都是錯誤的。


枚舉類型存取權

枚舉中成員的存取等級繼承自該枚舉,你不能為枚舉中的成員單獨申明不同的存取等級。

實例

例如下面的例子,枚舉Student 被明確的申明為public 級別,那麼它的成員Name,Mark 的存取級別同樣也是public:

public enum Student {
    case Name(String)
    case Mark(Int,Int,Int)
}

var studDetails = Student.Name("Swift")
var studMarks = Student.Mark(98,97,95)

switch studMarks {
case .Name(let studName):
    print("学生名: \(studName).")
case .Mark(let Mark1, let Mark2, let Mark3):
    print("学生成绩: \(Mark1),\(Mark2),\(Mark3)")
}

以上程式執行輸出結果為:

学生成绩: 98,97,95

子類別存取權限

子類別的存取等級不得高於父類別的存取等級。比方說,父類別的存取等級是internal,子類別的存取等級就不能申明為public。

public class SuperClass {
    private func show() {
        print("超类")
    }
}

// 访问级别不能低于超类 internal > public
internal class SubClass: SuperClass  {
    override internal func show() {
        print("子类")
    }
}

let sup = SuperClass()
sup.show()

let sub = SubClass()
sub.show()

以上程式執行輸出結果為:

超类
子类

常數、變數、屬性、下標存取權限

常數、變數、屬性不能擁有比它們的類型更高的存取等級。

比如說,你定義一個public等級的屬性,但是它的型別是private等級的,這是編譯器所不允許的。

同樣,下標也不能擁有比索引類型或傳回類型更高的存取等級。

如果常數、變數、屬性、下標索引的定義型別是private層級的,那麼它們必須要明確的申明存取等級為private:

private var privateInstance = SomePrivateClass()

Getter 和Setter訪問權限

常數、變數、屬性、下標索引的Getters和Setters的存取等級繼承自它們所屬成員的存取等級。

Setter的存取級別可以低於對應的Getter的存取級別,這樣就可以控制變數、屬性或下標索引的讀寫權限。

class Samplepgm {
    private var counter: Int = 0{
        willSet(newTotal){
            print("计数器: \(newTotal)")
        }
        didSet{
            if counter > oldValue {
                print("新增加数量 \(counter - oldValue)")
            }
        }
    }
}

let NewCounter = Samplepgm()
NewCounter.counter = 100
NewCounter.counter = 800

以上程式執行輸出結果為:

计数器: 100
新增加数量 100
计数器: 800
新增加数量 700

建構器和預設建構器存取權限

初始化

我們可以給自訂的初始化方法申明存取級別,但是要不高於它所屬類別的存取級別。但必要構造器例外,它的存取等級必須和所屬類別的存取等級相同。

如同函數或方法參數,初始化方法參數的存取等級也不能低於初始化方法的存取等級。

預設初始化方法

Swift為結構體、類別都提供了一個預設的無參初始化方法,用於給它們的所有屬性提供賦值操作,但不會給出具體值。

預設初始化方法的存取等級與所屬類型的存取等級相同。

實例

在每個子類別的 init()  方法前使用 required 關鍵字宣告存取權限。


class classA {
    required init() {
        var a = 10
        print(a)
    }
}

class classB: classA {
    required init() {
        var b = 30
        print(b)
    }
}

let res = classA()
let show = classB()

以上程式執行輸出結果為:

10
30
10

協定存取權限

如果想為一個協定明確的申明存取級別,那麼需要注意一點,就是你要確保該協定只在你申明的存取級別作用域中使用。

如果你定義了一個public存取等級的協議,那麼實作該協議所提供的必要函數也會是public的存取等級。這一點不同於其他類型,例如,public存取等級的其他類型,他們成員的存取等級為internal。

public protocol TcpProtocol {
    init(no1: Int)
}

public class MainClass {
    var no1: Int // local storage
    init(no1: Int) {
        self.no1 = no1 // initialization
    }
}

class SubClass: MainClass, TcpProtocol {
    var no2: Int
    init(no1: Int, no2 : Int) {
        self.no2 = no2
        super.init(no1:no1)
    }
    
    // Requires only one parameter for convenient method
    required override convenience init(no1: Int)  {
        self.init(no1:no1, no2:0)
    }
}

let res = MainClass(no1: 20)
let show = SubClass(no1: 30, no2: 50)

print("res is: \(res.no1)")
print("res is: \(show.no1)")
print("res is: \(show.no2)")

以上程式執行輸出結果為:

res is: 20
res is: 30
res is: 50

擴展存取權限

你可以在條件允許的情況下對類別、結構體、枚舉進行擴展。擴展成員應該具有和原始類別成員一致的存取等級。例如你擴充了一個公共類型,那麼你新加的成員應該要有和原始成員一樣的預設的internal存取等級。


或者,你可以明確申明擴充功能的存取等級(例如使用private extension)給予該擴充功能內所有成員申明一個新的預設存取等級。這個新的預設存取等級仍然可以被單獨成員所申明的存取等級所覆蓋。


泛型存取權限

泛型類型或泛型函數的存取等級取泛型類型、函數本身、泛型類型參數三者中的最低存取等級。

public struct TOS<T> {
    var items = [T]()
    private mutating func push(item: T) {
        items.append(item)
    }
    
    mutating func pop() -> T {
        return items.removeLast()
    }
}

var tos = TOS<String>()
tos.push("Swift")
print(tos.items)

tos.push("泛型")
print(tos.items)

tos.push("类型参数")
print(tos.items)

tos.push("类型参数名")
print(tos.items)
let deletetos = tos.pop()

以上程式執行輸出結果為:

["Swift"]
["Swift", "泛型"]
["Swift", "泛型", "类型参数"]
["Swift", "泛型", "类型参数", "类型参数名"]

類型別名

任何你定義的類型別名都會被當作不同的類型,以便於進行訪問控制。一個類型別名的存取等級不可高於原類型的存取等級。

比如說,一個private等級的類型別名可以設定給一個public、internal、private的類型,但是一個public等級的類型別名只能設定給一個public等級的類型,不能設定給internal或private 等級的類型。

注意:這條規則也適用於為滿足協定一致性而為相關類型命名別名的情況。

public protocol Container {
    typealias ItemType
    mutating func append(item: ItemType)
    var count: Int { get }
    subscript(i: Int) -> ItemType { get }
}

struct Stack<T>: Container {
    // original Stack<T> implementation
    var items = [T]()
    mutating func push(item: T) {
        items.append(item)
    }
    
    mutating func pop() -> T {
        return items.removeLast()
    }
    
    // conformance to the Container protocol
    mutating func append(item: T) {
        self.push(item)
    }
    
    var count: Int {
        return items.count
    }
    
    subscript(i: Int) -> T {
        return items[i]
    }
}

func allItemsMatch<
    C1: Container, C2: Container
    where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
    (someContainer: C1, anotherContainer: C2) -> Bool {
        // check that both containers contain the same number of items
        if someContainer.count != anotherContainer.count {
            return false
        }
        
        // check each pair of items to see if they are equivalent
        for i in 0..<someContainer.count {
            if someContainer[i] != anotherContainer[i] {
                return false
            }
        }
        
        // all items match, so return true
        return true
}

var tos = Stack<String>()
tos.push("Swift")
print(tos.items)

tos.push("泛型")
print(tos.items)

tos.push("Where 语句")
print(tos.items)

var eos = ["Swift", "泛型", "Where 语句"]
print(eos)

以上程式執行輸出結果為:

["Swift"]
["Swift", "泛型"]
["Swift", "泛型", "Where 语句"]
["Swift", "泛型", "Where 语句"]