
Swfit中提供了5個(gè)不同的訪問(wèn)級(jí)別,按照訪問(wèn)權(quán)限的高低排序如下:
-
open: 允許在定義實(shí)體的模塊,其他模塊訪問(wèn).允許其他模塊繼承,重寫.open 只能用在 類 , 類成員 上.
什么是模塊?一個(gè)可執(zhí)行文件就是一個(gè)模塊,我們平常的項(xiàng)目就是一個(gè)模塊.導(dǎo)入的其他動(dòng)態(tài)庫(kù)也是一個(gè)模塊.
public: 允許在定義實(shí)體的模塊,其他模塊訪問(wèn).但是不允許其他模塊繼承,重寫.internal: 只允許在定義實(shí)體的模塊中訪問(wèn),不允許其他模塊訪問(wèn).fileprivate: 只允許在定義實(shí)體的源文件中訪問(wèn).private: 只允許在定義實(shí)體的作用域中使用.
絕大部分實(shí)體默認(rèn)是 internal訪問(wèn)級(jí)別,也就是在當(dāng)前模塊都可以訪問(wèn).
訪問(wèn)級(jí)別的使用準(zhǔn)則:一個(gè)實(shí)體不可以被更低訪問(wèn)級(jí)別的實(shí)體定義
這句話什么意思呢?我的理解就是訪問(wèn)實(shí)體 A ,就會(huì)直接或者間接訪問(wèn)實(shí)體 B,所 B 的訪問(wèn)級(jí)別 一定要 ≥ A 的訪問(wèn)級(jí)別
這種情況大概分為以下幾種情況:

我們針對(duì)上述幾種情況分別舉例說(shuō)明:
- 變量\常量類型要 ≥ 變量\常量訪問(wèn)級(jí)別:

變量的訪問(wèn)級(jí)別是internal,而變量類型Person的訪問(wèn)級(jí)別是fileprivate.也就是說(shuō)我們可以在整個(gè)模塊的其他文件中訪問(wèn)變量xiaoMing卻無(wú)法訪問(wèn)Person.顯然這是矛盾的.
- 參數(shù)類型,返回值類型要 ≥ 函數(shù)的訪問(wèn)級(jí)別:

同上,可以在其他文件調(diào)用test方法,卻無(wú)法在其他文件訪問(wèn)Person,矛盾.
父類的訪問(wèn)級(jí)別要 ≥ 子類的訪問(wèn)級(jí)別:
因?yàn)槲覀冊(cè)L問(wèn)子類時(shí)肯定也就訪問(wèn)了父類中的東西.所以父類的訪問(wèn)級(jí)別不能小于子類的訪問(wèn)級(jí)別.父協(xié)議 ≥ 子協(xié)議
我們知道協(xié)議也是可以繼承的,和父類子類的情況相同.原類型 ≥ typealias

可以在其他模塊中訪問(wèn)MyDog,但是卻無(wú)法在其他模塊中訪問(wèn)Dog,矛盾.
- 原始值類型 \ 關(guān)聯(lián)值類型 ≥ 枚舉類型:

這個(gè)很好理解,訪問(wèn)枚舉就會(huì)訪問(wèn)到枚舉中的關(guān)聯(lián)類型,所以關(guān)聯(lián)類型的訪問(wèn)級(jí)別不能小于枚舉的訪問(wèn)級(jí)別.
訪問(wèn)級(jí)別的水桶效應(yīng)
元組和泛型的訪問(wèn)級(jí)別取決于所有成員中級(jí)別最低的一個(gè):
元組的訪問(wèn)級(jí)別:

泛型的訪問(wèn)級(jí)別:

person的訪問(wèn)級(jí)別取決于Person , Dog , Cat中訪問(wèn)級(jí)別最低的一個(gè).
成員(屬性 , 方法 , 下標(biāo) , 初始化器)的訪問(wèn)級(jí)別
一般情況下,類型為
private 或 fileprivate,那么成員也是private 或 fileprivate一般情況下,類型為
internal 或 public,那么成員默認(rèn)是internal子類重寫父類的成員,必須大于子類的訪問(wèn)級(jí)別,或者大于父類被重寫成員的訪問(wèn)級(jí)別.

在全局作用域下private等價(jià)于fileprivate

從上圖可以看到,在test方法作用域內(nèi),父類Person的訪問(wèn)權(quán)限小于子類Student的訪問(wèn)權(quán)限,結(jié)果報(bào)錯(cuò).
如果我們把Person和Student寫在方法外面,全局作用域中呢?

可以看到,在全局作用域下,private等價(jià)于fileprivate.代碼并不會(huì)報(bào)錯(cuò).
補(bǔ)充
上面說(shuō)過(guò),類型為private 或 fileprivate,那么成員也是private 或 fileprivate.但是下面這種情況就是例外:

Dog和Student是寫在全局作用域的.也就是說(shuō)Dog的訪問(wèn)權(quán)限等價(jià)于fileprivate,所以Dog內(nèi)部成員的訪問(wèn)權(quán)限跟隨Dog的訪問(wèn)權(quán)限.所以也是fileprivate.所以能在Student內(nèi)部調(diào)用.
相當(dāng)于這樣:

但是如果顯示的寫明訪問(wèn)權(quán)限,就不一樣了:

getter,setter
getter , setter默認(rèn)接受他們所屬環(huán)境的訪問(wèn)級(jí)別,但是我們可以給setter設(shè)置更低的訪問(wèn)級(jí)別,用來(lái)限制寫的權(quán)限.
像下面的sex跟隨Person的訪問(wèn)級(jí)別internal:

我們可以單獨(dú)給sex的setter方法設(shè)置一個(gè)更低的訪問(wèn)權(quán)限,禁止外部更改:

注意: setter 的訪問(wèn)級(jí)別可以比 getter 的訪問(wèn)級(jí)別低,但是 getter 的訪問(wèn)級(jí)別不能比 setter 的訪問(wèn)級(jí)別低:

初始化器
- 如果一個(gè)
public類想在其他模塊調(diào)用系統(tǒng)生成的無(wú)參初始化器,那么必須顯示的提供public無(wú)參初始化器.因?yàn)樯厦嬉呀?jīng)說(shuō)過(guò)類型為 public 或 internal , 成員默認(rèn)是 internal.
-
required初始化器必須≥它的默認(rèn)訪問(wèn)級(jí)別:

- 如果結(jié)構(gòu)體有
private / fileprivate的存儲(chǔ)實(shí)例屬性,那么它帶有成員的初始化器也是private / fileprivate,否則默認(rèn)就是internal:

枚舉的訪問(wèn)級(jí)別:
- 不能給枚舉的
case單獨(dú)設(shè)置訪問(wèn)級(jí)別:

- 每個(gè)
case自動(dòng)接收枚舉的訪問(wèn)級(jí)別.如果枚舉的訪問(wèn)級(jí)別是public,那么case的訪問(wèn)級(jí)別也是public,因?yàn)椴荒軉为?dú)給case設(shè)置訪問(wèn)級(jí)別.
協(xié)議的訪問(wèn)級(jí)別:
同枚舉一樣,協(xié)議中定義的要求自動(dòng)接收協(xié)議的訪問(wèn)級(jí)別,不能為協(xié)議中的成員單獨(dú)設(shè)置訪問(wèn)級(jí)別,如果協(xié)議的訪問(wèn)級(jí)別是
public,協(xié)議中成員的訪問(wèn)級(jí)別也是public協(xié)議實(shí)現(xiàn)的訪問(wèn)級(jí)別必須
≥協(xié)議的訪問(wèn)級(jí)別 ; 或者≥遵守協(xié)議實(shí)體的訪問(wèn)級(jí)別.

擴(kuò)展的訪問(wèn)級(jí)別
- 如果顯示的設(shè)置擴(kuò)展的訪問(wèn)級(jí)別,擴(kuò)展添加的成員自動(dòng)接收擴(kuò)展的訪問(wèn)級(jí)別:

擴(kuò)展的run()接收擴(kuò)展的訪問(wèn)級(jí)別fileprivate
- 如果沒(méi)有顯示的設(shè)置擴(kuò)展的訪問(wèn)級(jí)別,擴(kuò)展添加的成員的訪問(wèn)級(jí)別,跟隨類型的訪問(wèn)級(jí)別:

可以單獨(dú)給擴(kuò)展添加的成員設(shè)置訪問(wèn)級(jí)別.
不能給遵守了協(xié)議的擴(kuò)展,顯示設(shè)置訪問(wèn)級(jí)別

- 在同一個(gè)文件中的多個(gè)擴(kuò)展,可以理解為一個(gè)類的聲明拆分多個(gè)部分:

相當(dāng)于這樣:

方法賦值給常量\變量
我們知道函數(shù)可以賦值給一個(gè)常量或者變量,然后直接調(diào)用:

方法也可以賦值給變量\常量,只不過(guò)麻煩一些:

如圖所示,Person類中有一個(gè)實(shí)例方法run,現(xiàn)在我們把run方法賦值給一個(gè)變量fn:

可以看到,fn的類型是接收一個(gè)Person參數(shù),返回一個(gè)函數(shù),返回的函數(shù)就是run函數(shù).

直接調(diào)用fn2:

weak , unowned
weak引用計(jì)數(shù)不會(huì)+1,同 OC 一樣,當(dāng)實(shí)例對(duì)象銷毀后,weak會(huì)自動(dòng)將指針置為nil.所以weak修飾的引用必須是var的可選項(xiàng).unowned:無(wú)主引用,不會(huì)對(duì)引用計(jì)數(shù)+1,當(dāng)實(shí)例對(duì)象銷毀后,不會(huì)將指針置為nil.類似于 OC 中的unsafe_unretainedweak 和 unowned只能用在類實(shí)例上面unowned要比weak少一些性能消耗,因?yàn)樗粫?huì)置為nil
閉包的循環(huán)引用
閉包表達(dá)式默認(rèn)會(huì)對(duì)外層對(duì)象產(chǎn)生額外的強(qiáng)引用,所以在使用時(shí)要格外注意:
先看一下正常情況:

再來(lái)看一下閉包引用外層對(duì)象,導(dǎo)致循環(huán)引用的情況:

person強(qiáng)引用了fn,fn內(nèi)部又對(duì)p產(chǎn)生了強(qiáng)引用,形成了循環(huán)引用.
要解決這種循環(huán)引用的問(wèn)題,需要在閉包表達(dá)式的捕獲列表聲明weak , unowned:

如果在定義閉包屬性的同時(shí)引用了self ,那么這個(gè)閉包必須是lazy的:

因?yàn)榇藭r(shí)self還未初始化完成,不能使用self.可以把閉包定義為lazy的,等到self初始化完成后,用到閉包的時(shí)候在初始化閉包:

這時(shí)候又出現(xiàn)了另一個(gè)錯(cuò)誤,這是因?yàn)?編譯器認(rèn)為這里可能會(huì)出現(xiàn)循環(huán)引用,要求必須明確寫出self,提醒可能會(huì)強(qiáng)引用self:

解決循環(huán)引用,在閉包表達(dá)式的捕獲列表聲明weak:

看看下面這種情況:

同樣是閉包,為什么這里不用聲明捕獲列表,并且編譯器也沒(méi)有強(qiáng)制寫明self呢?因?yàn)檫@里是閉包的調(diào)用,直接把閉包的結(jié)果賦值給了fn,并且看清楚這里的fn是Int類型,相當(dāng)于lazy var fn: Int = 5.所以并不會(huì)產(chǎn)生強(qiáng)引用.
所以,如果 lazy 屬性是閉包調(diào)用的結(jié)果,那么并不會(huì)產(chǎn)生強(qiáng)引用,因?yàn)殚]包調(diào)用完后,閉包的生命周期就結(jié)束了.
逃逸閉包 , 非逃逸閉包
逃逸閉包,非逃逸閉包一般都是當(dāng)做參數(shù)傳遞給函數(shù).
- 非逃逸閉包: 閉包調(diào)用發(fā)生在函數(shù)結(jié)束前,閉包調(diào)用在函數(shù)作用域內(nèi):

- 逃逸閉包: 閉包調(diào)用在有可能在函數(shù)結(jié)束后調(diào)用,閉包逃離了函數(shù)作用域,需要通過(guò)
@escaping聲明:

聲明@escaping:

GCD 的 async
GCD 傳遞 async函數(shù)也是一個(gè)逃逸閉包:

所以如果在async內(nèi)部訪問(wèn)了實(shí)例成員(屬性, 方法),編譯器要求在async內(nèi)部要顯示的寫出self:

因?yàn)?code>async是一個(gè)逃逸閉包,它的調(diào)用逃離了函數(shù)的作用域,所以我們使用的時(shí)候就要考慮要不要聲明閉包表達(dá)式的捕獲列表:

逃逸閉包不能捕獲inout參數(shù)
逃逸閉包不能捕獲inout參數(shù),我們看看下面這種情形:

解讀一下為什么會(huì)這樣:因?yàn)樘右蓍]包逃離了函數(shù)的作用域,它的調(diào)用時(shí)機(jī)不確定,有可能 test() 函數(shù)執(zhí)行完畢后 , 才會(huì)調(diào)用逃逸閉包.而 test 函數(shù)調(diào)用完畢后 , age 變量已經(jīng)銷毀,此時(shí)逃逸閉包在訪問(wèn) age 肯定就會(huì)報(bào)錯(cuò).
所以,逃逸閉包不能捕獲 inout 參數(shù).
最后補(bǔ)充一下swift中的兩個(gè)打印
swfit中有兩個(gè)協(xié)議能實(shí)現(xiàn)自定義打印:
-
CustomStringConvertible的description -
CustomDebugStringConvertible的debugDescription
class Person: CustomStringConvertible,CustomDebugStringConvertible{
var description: String
var debugDescription: String
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
self.description = "release person's name \(name) , age is \(age)"
self.debugDescription = "degub person's name \(name) , age is \(age)"
}
}
var xiaoMing = Person(name: "小明", age: 18)
print(xiaoMing)
debugPrint(xiaoMing)
這兩個(gè)協(xié)議基本上沒(méi)什么區(qū)別,兩個(gè)協(xié)議在debug和release模式下都能打印.但是如果兩個(gè)協(xié)議都實(shí)現(xiàn)后,我們?cè)诳刂婆_(tái)使用po指令打印的是CustomDebugStringConvertible的打印信息:
