在訪問(wèn)控制這塊闹司,Swift提供了五個(gè)不同的訪問(wèn)級(jí)別(以下是從高到低排列柜去,實(shí)體指被訪問(wèn)級(jí)別修飾的內(nèi)容)
- open : 允許在定義實(shí)體的模塊、其他模塊中訪問(wèn)节仿。允許其他模塊進(jìn)行繼承钧惧、重寫(xiě)(open只能用在類、類成員上)
模塊概念如下圖所示:
TARGETS
下的Demo
就是程序編譯后的可執(zhí)行文件勾习,是一個(gè)獨(dú)立的模塊浓瞪。也就是說(shuō),在此模塊中巧婶,不管是main.swift
文件還是test.swift
文件乾颁,在此模塊下的任意文件用到其他任意文件中的類或者結(jié)構(gòu)體等其他數(shù)據(jù),都不用像OC中那樣import
頭文件艺栈。
當(dāng)然英岭,這里需要注意權(quán)限訪問(wèn)級(jí)別
,open
是允許所有模塊都可任意訪問(wèn)的湿右,不管是工程可執(zhí)行模塊還是系統(tǒng)庫(kù)模塊又或者是其他第三方庫(kù)模塊诅妹。
動(dòng)態(tài)庫(kù)的一些想要公開(kāi)出去給別人調(diào)用的類,一般聲明open 訪問(wèn)權(quán)限
毅人,如下:
open class Person {}
注意:open 只能用在類上吭狡,不能用在結(jié)構(gòu)體、枚舉
- public : 允許在定義實(shí)體的模塊丈莺、其他模塊中訪問(wèn)划煮,不允許其他模塊繼承、重寫(xiě)
- internal : 只允許在定義實(shí)體的模塊中訪問(wèn)缔俄,不允許在其他模塊中訪問(wèn)
- fileprivate : 只允許在定義實(shí)體的源文件中訪問(wèn)
如上面兩圖所示弛秋,
Person
類定義在main.swift
文件中器躏,且用fileprivate
訪問(wèn)級(jí)別修飾,就不能在test.swift
等其他文件中調(diào)用蟹略,它只允許在自己的定義源文件main.swift
中調(diào)用登失。
- private : 只允許在定義實(shí)體的封閉聲明中訪問(wèn)
如圖所示,變量
name
被private
修飾科乎,它的被訪問(wèn)權(quán)限僅限于封閉聲明中壁畸,也就是大括號(hào)內(nèi)。
通過(guò)上面這五個(gè)訪問(wèn)控制權(quán)限可以看出茅茂,Swift不同于其他語(yǔ)言捏萍,它是以模塊、文件為單位控制訪問(wèn)級(jí)別權(quán)限的空闲。
絕大部分實(shí)體默認(rèn)都是internal
級(jí)別
也就是說(shuō)令杈,不寫(xiě)訪問(wèn)控制級(jí)別的修飾關(guān)鍵字的話,默認(rèn)都是添加了
internal
碴倾,整個(gè)項(xiàng)目都可訪問(wèn)逗噩。
訪問(wèn)級(jí)別的使用準(zhǔn)則
- 一個(gè)實(shí)體不可以被更低訪問(wèn)級(jí)別的實(shí)體定義,例如:
- 變量\常量類型
>=
變量\常量(變量類型的訪問(wèn)級(jí)別要大于等于變量的訪問(wèn)級(jí)別跌榔,同理异雁,常量類型的訪問(wèn)級(jí)別要大于等于常量的訪問(wèn)級(jí)別)
如圖所示,變量person
的類型是Person
僧须,internal
的訪問(wèn)級(jí)別是高于fileprivate
纲刀,編譯報(bào)錯(cuò)。
也就是上面說(shuō)的担平,一個(gè)實(shí)體不可以被更低訪問(wèn)級(jí)別的實(shí)體定義
示绊,internal
修飾的實(shí)體:變量person
,不能被訪問(wèn)級(jí)別低于它的fileprivate
修飾的類型Person
定義暂论。
本質(zhì):其他地方訪問(wèn)類實(shí)例person
面褐,意味著也要能夠訪問(wèn)類Person
,就要保證Person
的訪問(wèn)級(jí)別不能低于其實(shí)例person
的訪問(wèn)級(jí)別取胎,否則展哭,訪問(wèn)實(shí)例的地方無(wú)法訪問(wèn)類信息,怎么用扼菠?很矛盾摄杂,無(wú)法正常使用
- 參數(shù)類型、返回值類型
>=
函數(shù)
同理:別的地方調(diào)用函數(shù)循榆,必然用到函數(shù)參數(shù)和返回值析恢,這就意味著參數(shù)類型和返回值類型的訪問(wèn)級(jí)別必須不低于函數(shù)的訪問(wèn)級(jí)別。
否則函數(shù)參數(shù)和返回值都無(wú)法訪問(wèn)秧饮,怎么能調(diào)用函數(shù)映挂?
- 父類
>=
子類
- 子類重寫(xiě)成員的訪問(wèn)級(jí)別必須 >= 子類的訪問(wèn)級(jí)別泽篮,或者>=父類被重寫(xiě)成員的訪問(wèn)級(jí)別(也就是說(shuō)大于等于子類和父類被重寫(xiě)成員兩者中的最低的那個(gè)訪問(wèn)級(jí)別)
- 父類的成員不能被成員作用域外定義的子類重寫(xiě)(比如:
private
修飾的父類成員,不能被父類{}的子類重寫(xiě)柑船,因?yàn)?code>private作用域僅限于父類{}內(nèi)帽撑,若是將子類放到父類{}內(nèi),則是沒(méi)問(wèn)題的)鞍时。
- 父協(xié)議
>=
子協(xié)議
- 原類型
>=
typealias
class Person {}
public typealias MyPerson = Person
這樣的是不行的亏拉,編譯報(bào)錯(cuò)(Type alias cannot be declared public because its underlying type uses a indternal type)
Person
定義MyPerson
,也就是定義typealias
逆巍,原類型Person
的訪問(wèn)級(jí)別必須大于typealias
的訪問(wèn)級(jí)別及塘,顯然這里的Person
的默認(rèn)訪問(wèn)級(jí)別是indternal
,是低于public
的锐极。
- 原始值類型笙僚、關(guān)聯(lián)值類型
>=
枚舉類型- 定義類型A時(shí)用到的其他類型
>=
類型A- ......
元組類型
- 元組類型的訪問(wèn)級(jí)別是所有成員類型最低的那個(gè)
internal class Person {}
fileprivate class Cat {}
fileprivate var aa: (Person, Cat)
internal var bb: (Person, Cat) //編譯報(bào)錯(cuò)Variable cannot be declared internal because its type uses a fileprivate type
如上代碼示例,(Person, Cat)
是元組類型灵再,它的訪問(wèn)級(jí)別是所有成員中類型最低的那個(gè)肋层,也就是此元組類型的訪問(wèn)級(jí)別是fileprivate
Swift 的訪問(wèn)控制級(jí)別規(guī)定,一個(gè)實(shí)體不可以被更低訪問(wèn)級(jí)別的實(shí)體定義
代碼中翎迁,變量aa
的訪問(wèn)的級(jí)別與定義它的元組類型的級(jí)別都是fileprivate
栋猖,是附和訪問(wèn)級(jí)別的使用準(zhǔn)則。
而變量bb
的訪問(wèn)級(jí)別internal
是高于定義它的元組類型(Person, Cat)
的訪問(wèn)級(jí)別fileprivate
汪榔,是不符合Swift訪問(wèn)級(jí)別語(yǔ)法規(guī)定的掂铐,所以編譯報(bào)錯(cuò)。
泛型類型
- 泛型類型的訪問(wèn)級(jí)別是
類型的訪問(wèn)級(jí)別
以及所有泛型類型參數(shù)的訪問(wèn)級(jí)別
中最低的那個(gè)
internal class Cat {}
fileprivate class Dog {}
public class Person<T1, T2> {}
//Person<Cat, Dog>的訪問(wèn)級(jí)別是fileprivate
fileprivate var p = Person<Cat, Dog>()
代碼示例中揍异,Person<Cat, Dog>
的訪問(wèn)級(jí)別是由類型Person
和泛型參數(shù)類型Cat
與Dog
三者中最低的那個(gè)決定的,最終確定為Dog
的訪問(wèn)級(jí)別fileprivate
Person<Cat, Dog>
的訪問(wèn)級(jí)別fileprivate
是大于等于變量p
的訪問(wèn)級(jí)別爆班,編譯通過(guò)衷掷。
成員、嵌套類型
- 類型的訪問(wèn)級(jí)別會(huì)影響成員(屬性柿菩、方法戚嗅、初始化器、下標(biāo))枢舶、嵌套類型的默認(rèn)訪問(wèn)級(jí)別
- 一般情況下懦胞,類型為
private
或fileprivate
,那么成員凉泄、嵌套類型默認(rèn)也是private
或fileprivate
- 一般情況下躏尉,類型為
internal
或public
,那么成員后众、嵌套類型默認(rèn)是internal
class Test {
private class Person {}
fileprivate class Student : Person {}//編譯報(bào)錯(cuò)Class cannot be declared fileprivate because its superclass is private
}
父類Person
的訪問(wèn)級(jí)別private
是低于子類Student
的fileprivate
胀糜,所以編譯報(bào)錯(cuò)颅拦。
但是將Test
中的類放到類外面,也就是源文件中教藻,全局作用域內(nèi)距帅,如下:
private class Person {}
fileprivate class Student : Person {}
是不報(bào)錯(cuò)的,編譯能通過(guò)括堤。
為什么呢碌秸?
因?yàn)?code>private訪問(wèn)級(jí)別僅限于封閉聲明中,此刻代碼在全局作用域內(nèi)悄窃,并沒(méi)有在某個(gè)代碼塊中讥电,所以,它的訪問(wèn)級(jí)別就是此源文件內(nèi)广匙,本質(zhì)上與
fileprivate
的訪問(wèn)級(jí)別等同了允趟,都是在實(shí)體源文件中。
private struct Dog {
private var name: String = "Dog"
private func eat() {};
}
fileprivate struct Cat {
var dog: Dog = Dog()
mutating func dosomethings() {
dog.name = "張三" //編譯報(bào)錯(cuò)'name' is inaccessible due to 'private' protection level
dog.eat() //編譯報(bào)錯(cuò)'eat' is inaccessible due to 'private' protection level
}
}
這段代碼為什么編譯報(bào)錯(cuò)呢鸦致?
因?yàn)?code>Dog成員訪問(wèn)級(jí)別是
private
潮剪,被限定在Dog
作用域內(nèi)了,外部是沒(méi)有權(quán)限訪問(wèn)的分唾。
private struct Dog {
var name: String = "Dog"
func eat() {};
}
fileprivate struct Cat {
var dog: Dog = Dog()
mutating func dosomethings() {
dog.name = "張三"
dog.eat()
}
}
此段代碼編譯通過(guò)抗碰,為什么呢?前面不是說(shuō)了類型為private或fileprivate绽乔,那么成員弧蝇、嵌套類型默認(rèn)也是private或fileprivate
的嗎?
既然Dog
訪問(wèn)級(jí)別為private
折砸,那么它的成員name
和eat()
的訪問(wèn)級(jí)別也應(yīng)該是private
看疗。
為什么編譯通過(guò)了呢?
因?yàn)槿肿饔糜騼?nèi)睦授,private
跟fileprivate
的訪問(wèn)權(quán)限一樣两芳,都是此源文件內(nèi)。
那么去枷,private
就是fileprivate
怖辆,上面代碼就等同于下面代碼:
fileprivate struct Dog {
fileprivate var name: String = "Dog"
fileprivate func eat() {};
}
fileprivate struct Cat {
var dog: Dog = Dog()
mutating func dosomethings() {
dog.name = "張三"
dog.eat()
}
}
注意:睜大眼睛,不要被
private
輕易迷惑删顶,要看作用域的竖螃。
直接在全局作用域下定義的private
等價(jià)于fileprivate
再看下邊代碼:
class Test {
private struct Dog {
var name: String = "Dog"
func eat() {};
}
private struct Cat {
var dog: Dog = Dog()
mutating func dosomethings() {
dog.name = "張三"
dog.eat()
}
}
}
此刻,Dog
不在全局作用域了逗余,按說(shuō)特咆,它的屬性name
和方法eat()
被默認(rèn)修飾為private
,不能被外部訪問(wèn)的录粱,也就是Cat
內(nèi)無(wú)法調(diào)用dog.name 和 dog.eat()
的坚弱。
為什么沒(méi)有編譯出錯(cuò)呢蜀备?
因?yàn)椋?code>Dog的訪問(wèn)級(jí)別
private
是限定在類Test
內(nèi)的,它的成員繼承了它的private
荒叶,這個(gè)private
是限定在類Test
內(nèi)的private
碾阁,不是其他private
。
也就是說(shuō)些楣,成員的private
和Dog
的private
是一樣的脂凶,都可以被Test
作用域內(nèi)訪問(wèn)。
若是手動(dòng)明確添加寫(xiě)上訪問(wèn)限定符private var name: String = "Dog"
愁茁,那么這里的private
和Dog
的private
是不一樣的蚕钦,它是明確告訴編譯器,這里的屬性name
是不能被外部訪問(wèn)的鹅很。
PS:有點(diǎn)繞嘶居,只可意會(huì)不可言傳,就當(dāng)是基因遺傳吧促煮。
getter邮屁、setter
- getter、setter默認(rèn)自動(dòng)接收它們所屬環(huán)境的訪問(wèn)級(jí)別
可以給setter單獨(dú)設(shè)置一個(gè)比getter更低的訪問(wèn)級(jí)別菠齿,用以限制寫(xiě)的權(quán)限
//其他文件可以獲取全局變量num的值佑吝,但是不能修改其值,只能在此文件內(nèi)可修改值
fileprivate(set) public var num = 10
class Person {
//age的setter訪問(wèn)級(jí)別被修飾為private绳匀,Person內(nèi)部可訪問(wèn)芋忿,Person外部無(wú)法訪問(wèn)
private(set) var age = 0
//weight的setter訪問(wèn)級(jí)別被修飾為fileprivate,main文件內(nèi)部可訪問(wèn)疾棵,main文件外部無(wú)法訪問(wèn)
fileprivate(set) public var weight: Int {
set {}
get { 10 }
}
//subscript的setter訪問(wèn)級(jí)別被修飾為internal戈钢,模塊內(nèi)可訪問(wèn),模塊外無(wú)法訪問(wèn)
internal(set) public subscript(index: Int) -> Int {
set {}
get { index }
}
}
var p: Person = Person()
p.age = 22 //編譯報(bào)錯(cuò)Cannot assign to property: 'age' setter is inaccessible
如上面代碼是尔,p.age = 22
編譯報(bào)錯(cuò)逆趣,因?yàn)榇鎯?chǔ)屬性age
的setter
訪問(wèn)級(jí)別被修飾為private
,所以嗜历,age
的寫(xiě)操作僅限于Person
內(nèi)部,其他地方無(wú)法使用寫(xiě)操作抖所,也就不能修改其值梨州。
由于age
的getter
沒(méi)有被明確修飾為某個(gè)訪問(wèn)級(jí)別,默認(rèn)是Person
的訪問(wèn)級(jí)別田轧,也就是internal
暴匠,所以,讀操作傻粘,模塊內(nèi)任何地方都是可以訪問(wèn)調(diào)用的每窖。
注意:
getter
訪問(wèn)級(jí)別是不能比setter
低的帮掉,上面案例中如private(set)
這樣的操作,只能用于setter
上窒典,不能用此方法設(shè)置getter
的訪問(wèn)級(jí)別蟆炊。
上面代碼中,若是將private(set) var age = 0
改為public(get) var age = 0
瀑志,為什么編譯會(huì)報(bào)錯(cuò)(Expected 'set' as subject of 'public' modifier)
因?yàn)榭磮?bào)錯(cuò)原因涩搓,編譯器告訴我們,這種單獨(dú)設(shè)置讀寫(xiě)的訪問(wèn)權(quán)限的方式劈猪,它只能用于set
昧甘,且訪問(wèn)級(jí)別不能低于get
的訪問(wèn)級(jí)別。
初始化器
- 如果一個(gè)
public
類 想在另一個(gè)模塊調(diào)用編譯生成的默認(rèn)無(wú)參初始化器战得,必須顯式提供public
的無(wú)參初始化器
因?yàn)?code>public類 的默認(rèn)初始化器是
internal
級(jí)別
public class Cat {
var name: String?
}
Cat
類的訪問(wèn)級(jí)別是public
充边,其內(nèi)部成員(屬性、方法常侦、下標(biāo))的訪級(jí)別默認(rèn)都是internal
浇冰,也就是說(shuō)默認(rèn)的初始化器的訪問(wèn)級(jí)別也是internal
。
所以刮吧,其他另一個(gè)模塊是無(wú)法訪問(wèn)此類默認(rèn)初始化器的湖饱。
required
初始化器 >= 它的默認(rèn)訪問(wèn)級(jí)別如果結(jié)構(gòu)體有
private/fileprivate
的存儲(chǔ)實(shí)例屬性,那么它的成員初始化器(帶有成員的初始化器)也是private/fileprivate
杀捻,否則默認(rèn)就是internal
public struct Cat {
private var name = "Cat"
var age = 1
}
//編譯報(bào)錯(cuò)井厌,因?yàn)閚ame的訪問(wèn)級(jí)別被限定在Cat{}內(nèi)部,外部無(wú)法訪問(wèn)
var cat = Cat(name: "張三", age: 22)
還是要注意訪問(wèn)級(jí)別的問(wèn)題致讥,默認(rèn)初始化器的訪問(wèn)級(jí)別是
internal
仅仆,但是存儲(chǔ)屬性的訪問(wèn)級(jí)別是private/fileprivate
了,默認(rèn)初始化器的訪問(wèn)級(jí)別也就是private/fileprivate
了垢袱,private
是在Cat類外無(wú)法調(diào)用默認(rèn)初始化器的墓拜,fileprivate
可以在源文件其他地方任意調(diào)用Cat類的默認(rèn)初始化器,但是其他文件沒(méi)有權(quán)限調(diào)用请契。
枚舉類型的case
- 不能給
enum
的每個(gè)case
單獨(dú)設(shè)置訪問(wèn)級(jí)別 - 每個(gè)
case
自動(dòng)接收enum
的訪問(wèn)級(jí)別
public enum
定義的case
也是public
協(xié)議
- 協(xié)議中定義的要求自動(dòng)接收協(xié)議的訪問(wèn)級(jí)別咳榜,不能單獨(dú)設(shè)置訪問(wèn)級(jí)別
-
public
協(xié)議定義的要求(協(xié)議方法)也是public
- 協(xié)議實(shí)現(xiàn)的訪問(wèn)級(jí)別必須 >= 類型的訪問(wèn)級(jí)別,或者 >= 協(xié)議的訪問(wèn)級(jí)別
就是說(shuō)爽锥,協(xié)議實(shí)現(xiàn)的訪問(wèn)級(jí)別必須是 >= 類型訪問(wèn)級(jí)別和協(xié)議訪問(wèn)級(jí)別中的最低的那個(gè)訪問(wèn)級(jí)別
fileprivate protocol Say {
func sayHello()
}
public struct Cat : Say {
fileprivate func sayHello() {}
}
如上代碼涌韩,在Cat
類內(nèi),協(xié)議的實(shí)現(xiàn)sayHello() {}
訪問(wèn)級(jí)別fileprivate
氯夷。
類型Cat
的訪問(wèn)級(jí)別是public
臣樱。
協(xié)議Say
的訪問(wèn)級(jí)別是fileprivate
。
協(xié)議實(shí)現(xiàn)的訪問(wèn)級(jí)別是大于等于類型Cat
和協(xié)議Say
的訪問(wèn)級(jí)別中的最低的那個(gè),編譯通過(guò)雇毫。
public protocol Say {
func sayHello()
}
public struct Cat : Say {
func sayHello() {}
}
上面這段代碼是編譯不通過(guò)的玄捕,為什么?
因?yàn)榍懊嬲f(shuō)了棚放,類型的訪問(wèn)級(jí)別是
public
枚粘,其內(nèi)部成員訪問(wèn)級(jí)別默認(rèn)都是internal
。
也就是說(shuō)席吴,func sayHello() {}
等同于internal func sayHello() {}
赌结,協(xié)議實(shí)現(xiàn)的訪問(wèn)級(jí)別internal
低于public
,編譯報(bào)錯(cuò)孝冒。
擴(kuò)展
- 如果有顯式設(shè)置擴(kuò)展的訪問(wèn)級(jí)別柬姚,擴(kuò)展添加的成員自動(dòng)接收擴(kuò)展的訪問(wèn)級(jí)別
//main.swift 文件
class Cat {}
private extension Cat {
func sayHello() {}
}
//test.swift 文件
var cat = Cat()
cat.sayHello()
//編譯報(bào)錯(cuò)'sayHello' is inaccessible due to 'fileprivate' protection level
- 如上面兩段代碼,在
test.swift
文件中調(diào)用庄涡,編譯報(bào)錯(cuò)量承,因?yàn)樵L問(wèn)級(jí)別不夠。main.swift
文件中穴店,Cat
擴(kuò)展的訪問(wèn)級(jí)別是private
撕捍,所以編譯器報(bào)錯(cuò)'sayHello' is inaccessible due to 'fileprivate' protection level
。
此處編譯器報(bào)錯(cuò)中提示的是fileprivate
泣洞,因?yàn)槿肿饔糜騼?nèi)忧风,private
等同于fileprivate
。- 所以球凰,猜測(cè)狮腿,應(yīng)該是編譯器做了優(yōu)化,凡是全局作用域的
private
呕诉,就按fileprivate
處理了缘厢。
如果沒(méi)有顯式設(shè)置擴(kuò)展的訪問(wèn)級(jí)別,擴(kuò)展添加的成員的默認(rèn)訪問(wèn)級(jí)別跟直接在類型中定義的成員一樣
可以單獨(dú)給擴(kuò)展添加的成員設(shè)置訪問(wèn)級(jí)別
不能給用于遵守協(xié)議的擴(kuò)展顯式設(shè)置擴(kuò)展的訪問(wèn)級(jí)別
在同一文件中的擴(kuò)展甩挫,可以寫(xiě)成類似多個(gè)部分的類型聲明
class Cat {
private func say0() {}
private func eat0() {
say1()
}
}
extension Cat {
private func say1() {}
private func eat1() {
eat0()
}
}
extension Cat {
private func eat2() {
say1()
}
}
編譯通過(guò)贴硫,為什么呢?私有private
成員的訪問(wèn)級(jí)別不都是限定在當(dāng)前定義的{}內(nèi)嗎伊者?
其實(shí)英遭,同一文件內(nèi)的后面兩個(gè)擴(kuò)展相當(dāng)于將類Cat
拆分成不同部分,也就是說(shuō)亦渗,上面的代碼等同于下面的代碼:
class Cat {
private func say0() {}
private func eat0() {
say1()
}
private func say1() {}
private func eat1() {
eat0()
}
private func eat2() {
say1()
}
}
- 在原本的聲明中聲明一個(gè)私有成員挖诸,可以在同一文件的擴(kuò)展中訪問(wèn)它
- 在擴(kuò)展中聲明的一個(gè)私有成員,可以在同一文件的其他擴(kuò)展中央碟、原本聲明中訪問(wèn)它
將方法賦值給let、var
- 方法也可以像函數(shù)那樣賦值給一個(gè)
let
或者var
class Cat {
func say(_ str: String) {
print("Cat say:", str)
}
static func eat(_ food: String) {
print("Cat eat:", food)
}
}
let cat = Cat()
var fn = cat.say //fn類型為:(String) ->()
fn("呵呵")
//打印結(jié)果Cat say: 呵呵
var fn1 = Cat.say //fn1類型為: (Cat) ->(String) ->()
var fn2 = fn1(Cat()) //fn2類型為:(String) ->()
fn2("哈哈")
//打印結(jié)果Cat say: 哈哈
var fn3 = Cat.eat
fn3("土豆") //fn3類型為:(String) ->()
//打印結(jié)果Cat eat: 土豆