從零學(xué)習(xí)Swift 13:swift中的訪問(wèn)級(jí)別,weak的使用,閉包循環(huán)引用以及逃逸閉包,非逃逸閉包

總結(jié)

Swfit中提供了5個(gè)不同的訪問(wèn)級(jí)別,按照訪問(wèn)權(quán)限的高低排序如下:

  1. open: 允許在定義實(shí)體的模塊,其他模塊訪問(wèn).允許其他模塊繼承,重寫.open 只能用在 類 , 類成員 上.

什么是模塊?一個(gè)可執(zhí)行文件就是一個(gè)模塊,我們平常的項(xiàng)目就是一個(gè)模塊.導(dǎo)入的其他動(dòng)態(tài)庫(kù)也是一個(gè)模塊.

  1. public: 允許在定義實(shí)體的模塊,其他模塊訪問(wèn).但是不允許其他模塊繼承,重寫.

  2. internal: 只允許在定義實(shí)體的模塊中訪問(wèn),不允許其他模塊訪問(wèn).

  3. fileprivate: 只允許在定義實(shí)體的源文件中訪問(wèn).

  4. 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ō)明:

  1. 變量\常量類型要 ≥ 變量\常量訪問(wèn)級(jí)別:

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

  1. 參數(shù)類型,返回值類型要 ≥ 函數(shù)的訪問(wèn)級(jí)別:

同上,可以在其他文件調(diào)用test方法,卻無(wú)法在其他文件訪問(wèn)Person,矛盾.

  1. 父類的訪問(wèn)級(jí)別要 ≥ 子類的訪問(wèn)級(jí)別:
    因?yàn)槲覀冊(cè)L問(wèn)子類時(shí)肯定也就訪問(wèn)了父類中的東西.所以父類的訪問(wèn)級(jí)別不能小于子類的訪問(wèn)級(jí)別.

  2. 父協(xié)議 ≥ 子協(xié)議
    我們知道協(xié)議也是可以繼承的,和父類子類的情況相同.

  3. 原類型 ≥ typealias

可以在其他模塊中訪問(wèn)MyDog,但是卻無(wú)法在其他模塊中訪問(wèn)Dog,矛盾.

  1. 原始值類型 \ 關(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í)別
  1. 一般情況下,類型為private 或 fileprivate,那么成員也是private 或 fileprivate

  2. 一般情況下,類型為internal 或 public,那么成員默認(rèn)是internal

  3. 子類重寫父類的成員,必須大于子類的訪問(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ò).

如果我們把PersonStudent寫在方法外面,全局作用域中呢?

可以看到,在全局作用域下,private等價(jià)于fileprivate.代碼并不會(huì)報(bào)錯(cuò).

補(bǔ)充

上面說(shuō)過(guò),類型為private 或 fileprivate,那么成員也是private 或 fileprivate.但是下面這種情況就是例外:

DogStudent是寫在全局作用域的.也就是說(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ú)給sexsetter方法設(shè)置一個(gè)更低的訪問(wèn)權(quán)限,禁止外部更改:

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

初始化器
  1. 如果一個(gè)public類想在其他模塊調(diào)用系統(tǒng)生成的無(wú)參初始化器,那么必須顯示的提供public無(wú)參初始化器.因?yàn)樯厦嬉呀?jīng)說(shuō)過(guò)類型為 public 或 internal , 成員默認(rèn)是 internal.
  1. required初始化器必須 它的默認(rèn)訪問(wèn)級(jí)別:
  1. 如果結(jié)構(gòu)體有private / fileprivate的存儲(chǔ)實(shí)例屬性,那么它帶有成員的初始化器也是private / fileprivate,否則默認(rèn)就是internal:
枚舉的訪問(wèn)級(jí)別:
  1. 不能給枚舉的case單獨(dú)設(shè)置訪問(wèn)級(jí)別:
  1. 每個(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í)別:
  1. 同枚舉一樣,協(xié)議中定義的要求自動(dòng)接收協(xié)議的訪問(wèn)級(jí)別,不能為協(xié)議中的成員單獨(dú)設(shè)置訪問(wèn)級(jí)別,如果協(xié)議的訪問(wèn)級(jí)別是public,協(xié)議中成員的訪問(wèn)級(jí)別也是public

  2. 協(xié)議實(shí)現(xiàn)的訪問(wèn)級(jí)別必須 協(xié)議的訪問(wèn)級(jí)別 ; 或者 遵守協(xié)議實(shí)體的訪問(wèn)級(jí)別.

實(shí)現(xiàn)的訪問(wèn)級(jí)別必須≥ 1 , 2 中的一個(gè)
擴(kuò)展的訪問(wèn)級(jí)別
  1. 如果顯示的設(shè)置擴(kuò)展的訪問(wèn)級(jí)別,擴(kuò)展添加的成員自動(dòng)接收擴(kuò)展的訪問(wèn)級(jí)別:

擴(kuò)展的run()接收擴(kuò)展的訪問(wèn)級(jí)別fileprivate

  1. 如果沒(méi)有顯示的設(shè)置擴(kuò)展的訪問(wèn)級(jí)別,擴(kuò)展添加的成員的訪問(wèn)級(jí)別,跟隨類型的訪問(wèn)級(jí)別:
  1. 可以單獨(dú)給擴(kuò)展添加的成員設(shè)置訪問(wèn)級(jí)別.

  2. 不能給遵守了協(xié)議的擴(kuò)展,顯示設(shè)置訪問(wèn)級(jí)別

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

相當(dāng)于這樣:

方法賦值給常量\變量

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

函數(shù)賦值給變量

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

image.png

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

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

直接調(diào)用fn2:

weak , unowned
  1. weak引用計(jì)數(shù)不會(huì) +1,同 OC 一樣,當(dāng)實(shí)例對(duì)象銷毀后,weak會(huì)自動(dòng)將指針置為nil.所以weak修飾的引用必須是var的可選項(xiàng).

  2. unowned:無(wú)主引用,不會(huì)對(duì)引用計(jì)數(shù)+1,當(dāng)實(shí)例對(duì)象銷毀后,不會(huì)將指針置為nil.類似于 OC 中的 unsafe_unretained

  3. weak 和 unowned只能用在類實(shí)例上面

  4. 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)引用的情況:

循環(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í)候在初始化閉包:

閉包定義為 lazy

這時(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,并且看清楚這里的fnInt類型,相當(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ù).

  1. 非逃逸閉包: 閉包調(diào)用發(fā)生在函數(shù)結(jié)束前,閉包調(diào)用在函數(shù)作用域內(nèi):
閉包調(diào)用在函數(shù)作用域內(nèi)
  1. 逃逸閉包: 閉包調(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ù),我們看看下面這種情形:

逃逸閉包不能捕獲 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)自定義打印:

  1. CustomStringConvertibledescription
  2. CustomDebugStringConvertibledebugDescription

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é)議在debugrelease模式下都能打印.但是如果兩個(gè)協(xié)議都實(shí)現(xiàn)后,我們?cè)诳刂婆_(tái)使用po指令打印的是CustomDebugStringConvertible的打印信息:

po 指令打印的是debug
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容