訪問控制(Access Control)
- 在訪問權限控制這塊,Swift提供了5個不同的訪問級別(以下是從高到低排列, 實體指被訪問級別修飾的內容)
- open:允許在定義實體的模塊(工程中所有創建的swift文件)、其他模塊(三方庫)中訪問,允許其他模塊進行繼承、重寫(open只能用在類、類成員上)
- public:允許在定義實體的模塊、其他模塊中訪問,不允許其他模塊進行繼承、重寫
- internal:只允許在定義實體的模塊中訪問,不允許在其他模塊中訪問
- fileprivate:只允許在定義實體的源文件中訪問(也就是隻允許在當前swift文件中訪問)
- private:只允許在定義實體的封閉聲明中訪問(只允許在本類中訪問,一般是用來修飾屬性)
- 絕大部分實體默認都是internal 級別
訪問級別的使用準則
- 一個實體不可以被更低訪問級別的實體定義,比如 :
- 變量\常量類型 ≥ 變量\常量
上圖變量類型的訪問級別爲 fileprivate 小於 變量的訪問級別internal,所以會報錯
2. 參數類型、返回值類型 ≥ 函數
3. 父類 ≥ 子類
4. 父協議 ≥ 子協議
5. 原類型 ≥ typealias
上圖原類型的訪問級別爲 fileprivate 小於typealiad的訪問級別internal,所以會報錯
6. 原始值類型、關聯值類型 ≥ 枚舉類型
上圖關聯值類型的訪問級別爲 fileprivate 小於enum的訪問級別public,所以會報錯
7. 定義類型A時用到的其他類型 ≥ 類型A
8. ......
元祖類型
- 元組類型的訪問級別是所有成員類型最低的那個
這裏我們可以看到元祖(Dog,Person)中Dog的訪問級別是internal,Person的訪問級別是fileprivate,Person的訪問級別較低,所以元祖的訪問級別是所有成員類型最低的fileprivate
泛型類型
- 泛型類型的訪問級別是 類型的訪問級別 以及 所有泛型類型參數的訪問級別 中最低的那個
由上圖可以看出,類型的訪問級別 Person 是 public,泛型類型參數的訪問級別 Dog 是fileprivate,Car是interval,這三個中最小的是fileprivate,所以泛型類型的訪問級別是fileprivate,等於變量p的訪問級別,所以是對的
成員、嵌套類型
- 類型的訪問級別會影響成員(屬性、方法、初始化器、下標)、嵌套類型的默認訪問級別
- 一般情況下,類型爲private或fileprivate,那麼成員\嵌套類型默認也是private或fileprivate
- 一般情況下,類型爲internal或public,那麼成員\嵌套類型默認是internal
成員的重寫
- 子類重寫成員的訪問級別必須 ≥ 子類的訪問級別,或者 ≥ 父類被重寫成員的訪問級別
- 父類的成員不能被成員作用域外定義的子類重寫
上圖的age成員屬性作用域只在person類中,子類不能使用,應都寫在Person類中,寫成下圖形式
下面的代碼能否重寫?
1.
當放在同一個方法體時會報錯,但是都放在全局作用域中就不會報錯,因爲在全局作用域中private和fileprivate訪問級別相同都是整個.swift文件可以訪問, 如下兩圖:
2.
與1的情況一致,看是否在全局作用域
3.
這段代碼任何情況下,包括全局作用域下也會報錯,因爲Dog類雖然和Person在全局作用域下的訪問級別一致,都可以認爲是fileprivate,但是run和age的訪問級別已經設置爲private,始終低於Person,所以會報錯,如下圖:
getter、setter
- getter、setter默認自動接收它們所屬環境的訪問級別
- 可以給setter單獨設置一個比getter更低的訪問級別,用以限制寫的權限
由上圖可知,age的setter訪問級別,只能在本類中使用,外部無法使用,所以不可以通過setter 給age賦值
初始化器
- 如果一個public類想在另一個模塊調用編譯生成的默認無參初始化器,必須顯式提供public的無參初始化器,因爲public類的默認初始化器是internal級別
public class Person {
//顯式提供public的無參初始化器
public init() {
}
}
- required初始化器 ≥ 它的默認訪問級別
- 如果結構體有private\fileprivate的存儲實例屬性,那麼它的成員初始化器也是private\fileprivate,否則默認就是internal
由上圖可知,Point除了默認初始化器,其他所有的初始化器都變爲private級別,不能訪問
枚舉類型的case
- 不能給enum的每個case單獨設置訪問級別
可以看出給case單獨設置訪問級別,只能通過接收enum的訪問級別
- 每個case自動接收enum的訪問級別
- public enum定義的case也是public
協議
協議中定義的(方法等)要求自動接收協議的訪問級別,不能單獨設置訪問級別
public協議定義的(方法等)要求也是public
協議實現的(方法等)訪問級別必須 ≥ 類型的訪問級別,或者 ≥ 協議的訪問級別
協議定義的方法實現訪問級別小於協議的訪問級別,所以報錯
協議實現的(方法等)訪問級別 ≥ 協議的訪問級別,正確
協議實現的(方法等)訪問級別 ≥ 類型的訪問級別,正確
下面代碼能編譯通過麼?
不能,因爲Person類型裏的run()方法默認的訪問級別爲internal,internal小於類型和協議的訪問級別public,所以錯誤,會報錯
擴展
- 如果有顯式設置擴展的訪問級別,擴展添加的成員自動接收擴展的訪問級別
class Person {}
fileprivate extension Person {
//這裏的run()方法的訪問界別默認接受擴展的訪問級別,爲fileprivate
func run() {}
}
- 如果沒有顯式設置擴展的訪問級別,擴展添加的成員的默認訪問級別,跟直接在類型中定義的成員一樣
class Person {}
extension Person {
//沒有顯示給擴展設置訪問級別,run()方法的訪問級別和Person類型一致
func run() {}
}
- 可以單獨給擴展添加的成員設置訪問級別
class Person {}
fileprivate extension Person {
//單獨給run()方法設置private訪問級別
private func run() {}
}
- 不能給用於遵守協議的擴展顯式設置擴展的訪問級別
- 在同一文件中的擴展,可以寫成類似多個部分的類型聲明
- 在原本的聲明中聲明一個私有成員,可以在同一文件的擴展中訪問它
- 在擴展中聲明一個私有成員,可以在同一文件的其他擴展中、原本聲明中訪問它
public class Person {
private func run0() { }
private func eat0() {
run1()
}
}
extension Person {
private func run1() { }
private func eat1() {
run0()
}
}
extension Person {
private func eat2() {
run1()
}
}
上面代碼裏extension中的代碼可以看作是寫在Person類的原聲明中,所以即使是私有的,原有類和extension也可以互相訪問
將方法賦值給var\let
- 方法也可以像函數那樣,賦值給var、let
當 run() 是實例方法,如下:
struct Person {
var age: Int
func run(_ v: Int) {
print("func run", age, v)
}
}
// fn1類型是(Person) -> ((Int) -> ()),接收一個Person實例,返回一個函數
var fn1 = Person.run
//fn2類型(Int) -> (),接收一個Int參數,無返回值
var fn2 = fn1(Person(age: 10))
fn2(20) //func run 10 20
Person(age: 10).run(20) //func run 10 20
由上圖可以看到,將方法賦值給var、let時,需要先傳給var、let一個初始化的實例,再傳入方法需要的參數,實際的結果與Person(age: 10).run(20) 這種方式相同
當 run() 是類方法,如下:
struct Person {
var age: Int
static func run(_ v: Int) {
print("func run", v)
}
}
//當run是類方法,fn1的類型是(Int) -> ()
var fn1 = Person.run
fn1(20) //func run 20
當實例方法和類方法同名的時候,在給var, let賦值會優先類方法,想要優先實例方法,必須指明給var, let賦值的類型爲實例方法類型,如下:
struct Person {
var age: Int
func run(_ v: Int) {
print("func run", age, v)
}
static func run(_ v: Int) {
print("func run", v)
}
}
//fn類型是(int) -> (),也就是默認是類方法的run()
var fn = Person.run
//fn1類型是(Person) -> (Int) -> (),想要run是實例方法,必須指明方法類型
var fn1: (Person) -> (Int) -> () = Person.run
do設置代碼塊
- Swift中不能直接用大括號{ }設置代碼塊:
Swift中可以用do設置代碼塊: