Swift中的訪問控制與內(nèi)存管理

自定義Description內(nèi)容打印

  • 通過遵守 CustomStringConvertible、CustomDebugStringConvertible 協(xié)議來自定義實例打印字符串
    1. 遵守 CustomStringConvertibleCustomDebugStringConvertible 協(xié)議
    2. 實現(xiàn)協(xié)議中的 description: String 、debugDescription: String類型變量 get 方法來確保內(nèi)容自定義
  • 需要注意的點
    1. print 調(diào)用的是 CustomStringConveritable 協(xié)議的description
    2. debugPrint、po 調(diào)用的是 CustomDebugStringConveritable 協(xié)議的 debugDescription

關(guān)于 Self 的使用

  • 特點
    1. Self 代表當(dāng)前的類型
    2. Self 一般用作返回值類型,限定返回值跟方法調(diào)用者必須是同一類型(也可以作為參數(shù)類型)。比如協(xié)議中定義一個方法,方法的返回值可以是 Self 來確保實現(xiàn)協(xié)議的類型描述

關(guān)于斷言 assert

  • 特點
    1. 常用于調(diào)試階段,默認(rèn)debug模式生效,發(fā)布模式會忽略
    2. 通過在 other Swift flags中設(shè)置 Debug 模式下的 -assert-coinfig Release 來強制關(guān)閉斷言
    3. 通過在 other Swift flags中設(shè)置 Release 模式下的 -assert-coinfig debug 來強制關(guān)閉斷言
//條件滿足true的時候才會繼續(xù)執(zhí)行,否則中斷進程,打印錯誤
assert(fasle, "斷言錯誤")

關(guān)于 fatalError 錯誤

  • 特點
    1. fatalError 錯誤是無法被 do-catch 所捕獲的
    2. 使用了 fatalError 函數(shù),不需要再寫 return 返回值了
    3. 在某些不得不實現(xiàn)、但是又不希望別人調(diào)用的方法,可以考慮內(nèi)部使用 fatalError 函數(shù)

訪問控制

  • Swift提供了五個不同層面的訪問限制

    1. open: 允許其他模塊訪問,并允許其他模塊進行繼承、重寫(open 只能用在類、類成員上)
    2. public: 允許在定義實體的模塊、其他模塊中訪問,不允許其他模塊進行繼承、重寫
    3. internal: 只允許在定義實體的模塊訪問,不允許在其他模塊訪問
    4. fileprivate:只允許定義實體的源文件中訪問
    5. private:只允許定義實體的封閉聲明中訪問

    注: 絕大部分實體默認(rèn)都是 internal 級別

  • 訪問級別的使用準(zhǔn)則

    1. 一個實體不能被更低訪問級別的實體定義
    2. 變量/常量類型 >= 變量/常量
    3. 父類 >= 子類
    4. 父協(xié)議 >= 子協(xié)議
    5. 原類型 >= typealias
    6. 原始值類型、關(guān)聯(lián)值類型 >= 枚舉類型
    7. 定義類型A時用到的其他類型 >= 類型A
class Person{}
public typealias MuyPerson = Person //該語句會報錯,原因是public訪問級別低于class Person 的默認(rèn)訪問級別 internal
  • 元組類型
    元組類型的訪問級別是所有成員類型最低的那個

  • 泛型類型
    泛型類型的訪問級別是 類型的訪問級別 以及 所有泛型類型參數(shù)的訪問級別 中最低的那個

  • 成員嵌套類型

    1. 類型的訪問級別會影響成員(屬性、方法、初始化器、下標(biāo))、嵌套類型的默認(rèn)訪問級別
    2. 一般情況下,類型為 private 或者 fileprivate,那么成員/嵌套類型默認(rèn)也是private 或者 fileprivate
    3. 一般情況下,類型為 internal 或者 public,那么成員/嵌套類型默認(rèn)是 internal

注:

  1. 父類的訪問權(quán)限要小于子類
  2. 直接在全局作用域下定義的 private 等價于 fileprivate
public class PublicClass {
    public var p1 = 0 
    var p2 = 0 //internal
    fileprivatre func f1() {} //fileprivate
    private func f2() {} //private
}


class InternalClass { // internal
    var p = 0 // internal 
    fileprivate func f1() {} //fileprivate
    private func f2 () {} //private
}

class Test {
    private class Person {} //private所作用的范圍是外面的大括號
    fileprivate class Student : Person {} //會報錯,因為子類Student的訪問權(quán)限低于父類
}

private class Person {} //private所作用的范圍是整個文件,由于文件聲明在全局區(qū)域,所以private作用域等同于fileprivate
fileprivate class Student : Person {} //不會報錯,此時兩者的權(quán)限是一致的

/*
報錯原因:
1. Dog中的成員變量是由private修飾,所以在其所在的作用域內(nèi)是能夠訪問的,由于Person中walk方法在Dog成員變量的作用域外,所以不能夠訪問其成員變量以及方法
2. 由于 Dog 定義在全局區(qū),即使使用關(guān)鍵字 private 修飾,也等同于 fileprivate 修飾,所以 Person 類與其成員變量 Dog 類型訪問權(quán)限一致,所以能夠正常實例

*/
private struct Dog {
    private var age: Int = 0
    private func run() {}
}

fileprivate struct Person {
    var dog: Dog = Dog()
    mutating func walk() {
    dog.run() // 會報訪問錯誤
    dog.age = 1 // 會報訪問錯誤
}
}

  • getter 、setter 訪問權(quán)限

    1. 兩者默認(rèn)自動接收他們所屬環(huán)境的訪問級別
    2. 可以給 setter 單獨設(shè)置一個比 getter 更低的訪問級別,用以限制寫的權(quán)限
    fileprivate(set) public var num = 10 
    class Person {
        private(set) var age = 0
        fileprivate(set) public var weight: Int {
            set {}
            get { 10 }
        }
        
        internal(set) public subscript(index: Int) -> Int {
            set {}
            get { index }
        }
    
    }
    
  • 公開部分方法的訪問權(quán)限可以通過 public 修飾

    如果想讓某個類的方法能夠提供給其他模塊使用,可以通過 public 修飾方法,表示其可以提供給其他模塊使用

  • 間接訪問權(quán)限影響

    如果在賦值的時候設(shè)置了當(dāng)前的變量訪問權(quán)限,那么其他的沒有被修飾的成員變量也會因此收到同樣的權(quán)限管理,均為修飾變量一樣的訪問權(quán)限

    struct Point {
        fileprivate var x = 0
        var y = 0
    }
    var p = Point(x: 10, y: 20)
    
    

初始化器

如果一個public類想要在另一個模塊調(diào)用編譯生成的默認(rèn)無參初始化器,必須顯示提供public的無參初始化器,因為public類的默認(rèn)初始化器是 internal 訪問級別

注意:

  1. required 初始化器必須跟它所屬的類擁有相同的訪問級別
  2. 如果結(jié)構(gòu)體有 private/fileprivate 的存儲實例屬性,那么它的成員初始化器也是 private/fileprivate,否則默認(rèn)就是 internal

枚舉類型的 case 訪問權(quán)限

  • 特點
    1. 不能給 enum 的每個case 單獨設(shè)置訪問級別
    2. 每個 case 自動接收 enum 的訪問級別
    3. public enum 定義的 case 也是 public

協(xié)議的訪問權(quán)限

  • 特點
    1. 協(xié)議中定義的內(nèi)容的權(quán)限,將自動接收協(xié)議的訪問級別,不能單獨設(shè)置訪問級別
    2. 協(xié)議實現(xiàn)的訪問級別必須 >= 類型的訪問級別,或者 >= 協(xié)議的訪問級別

注意:
public 修飾的類型/結(jié)構(gòu)體,成員變量以及方法的默認(rèn)類型是 internal 類型,權(quán)限比public權(quán)限小,所以此時遵守的協(xié)議的權(quán)限是 public ,所以會報錯

public protocol Runnable {
    func run()
}

public class Person : Runable {
    internal func run {}
}

擴展的訪問權(quán)限

  • 特點

    1. 如果有顯示設(shè)置擴展的訪問級別,擴展添加的成員自動接收擴展級別
    2. 如果沒有顯示設(shè)置擴展的訪問級別,擴展添加的成員默認(rèn)訪問級別,跟直接在類型中定義的成員一樣
    3. 可以單獨給擴展添加的成員設(shè)置訪問級別
    4. 不能給用于遵守協(xié)議的擴展顯示設(shè)置擴展的訪問級別
  • 擴展的多重聲明

    1. 在同一個文件中的擴展,可以寫成類似多個部分的類型聲明
    2. 在原本的聲明中聲明一個私有成員,可以在同一文件的擴展中訪問它
    3. 擴展中聲明一個私有成員,可以在同一個文件的其他擴展中、原本聲明中訪問它
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()
    }
}

  • 技巧:方法的拆分存儲調(diào)用

有時候需要將某些方法暫存到變量中,等到合適的實際動態(tài)去調(diào)用它,可以參照如下的方法:

  1. 將需要調(diào)用的方法取出來并存到變量中
  2. 實例方法所在的類并傳入到第一步的方法中,并存儲到變量中
  3. 通過第二步存儲的方法進行相關(guān)調(diào)用
struct Person {
    var age: Int
    func run(_ v: Int) { print("func run"), age, v }
}

/*
調(diào)用步驟
*/
var fn1 = Person.run
var fn2 = fn1(Person(age: 10))
fn2(20)

當(dāng)存在兩個重名的類型方法與實例方法的時候,在取值的時候,設(shè)定取值結(jié)果的類型來確定當(dāng)前的靜態(tài)方法或者實例方法

struct Person {
    var age: Int
    func run(_ v: Int) { print("func run", age, v) }
    static func run(_ v: Int) { print("static func run", v) }
}

//取實例方法
var fn: (Person) -> (Int) -> () = Person.run
fn(Person(age: 20))(20)

//取靜態(tài)方法
var fn1: (Int)->() = Person.run
fn1(20)

內(nèi)存管理

  • 特點
    1. 跟OC一樣,Swift 也是采用引用技術(shù)ARC內(nèi)存管理技術(shù)方案來管理內(nèi)存的(針對于堆空間)
    2. 引用默認(rèn)都是強引用
    3. 通過 weak 定義弱引用(ARC自動給弱引用設(shè)置為nil的時候,不會觸發(fā)屬性觀察器)
    4. 無主引用(unowned reference):通過 unowned 定義無主引用,改引用不安全,會產(chǎn)生野指針
    5. weak、unowned 的使用只能使用在類的實例上面
  • 循環(huán)引用

    1. weak、unowned 可以解決循環(huán)引用問題, unowned 要比 weak 少一些性能損耗
    2. 在生命周期中可能會變成 nil 的時候使用 weak
    3. 初始化賦值之后再也不會變成 nil 的時候使用 unowned
  • 閉包的循環(huán)引用問題

閉包表達(dá)式默認(rèn)會對用到的外層對象產(chǎn)生額外的強引用(對外層對象進行 retain 操作)


/*
案例一
*/
class Person {
    var fn: (()->())?
    func run() { print("run") }
    deinit { print("deinit") }
}

func test() {
    let p = Person()
    
    //會導(dǎo)致循環(huán)引用問題
    p.fn = { p.run() }
    
    //解決方法1:weak
    p.fn = {[weak p] in
        p?.run()
    }
    
    //解決方法2:unowned
    p.fn = {[unowned p] in
        p?.run()
    }
    
    //聲明多個弱引用
    p.fn = {[weak wp = p, unowned up = p, a = 10 + 20] in
        wp?.run()
    }
}


/*
案例二:閉包中引用self, self中引用閉包
*/
class Person1 {
    //使用weak去除循環(huán)引用問題
    lazy var fn: (()->()) = {[weak p = self] in 
        p?.run()
    }
    func run() { print("run") }
    deinit { print("deinit") }
}

/*
案例三:閉包中引用self之后直接實例運行,不差生循環(huán)引用
以下不會產(chǎn)生引用循環(huán):
閉包聲明之后立即調(diào)用,調(diào)用完成之后,閉包銷毀,返回閉包結(jié)果,此時getAge不會對閉包產(chǎn)生引用,引用的是閉包返回的值結(jié)果,所以此種情況下不會出現(xiàn)循環(huán)引用問題
*/
class Person2 {
    var age: Int = 0
    //使用weak去除循環(huán)引用問題
    lazy var getAge: Int = {
        self.age
    }()
    func run() { print("run") }
    deinit { print("deinit") }
}

逃逸閉包 @escaping

  • 特點
    1. 非逃逸閉包、逃逸閉包,一般都是當(dāng)做參數(shù)傳遞給函數(shù)
    2. 非逃逸閉包:閉包調(diào)用發(fā)生在函數(shù)結(jié)束前,閉包調(diào)用在其作用域范圍內(nèi)
    3. 逃逸閉包:閉包有可能會在函數(shù)結(jié)束后調(diào)用,閉包調(diào)用逃離了函數(shù)的作用域,需要通過 @escaping 聲明修飾
typealias Fn = ()->()

var gFn: Fn?
//非逃逸閉包
func test(_ fn: Fn) { fn() }

//fn逃逸閉包
func test2(_ fn: @escaping Fn) { gFn = fn }

//fn放在dispatch中同樣是逃逸閉包
func test3(_ fn: @escaping Fn) { 
    DispatchQueue.global().async {
        fn()
    }
}

func run(){
    //這一塊可以根據(jù)具體業(yè)務(wù)來定,如果想要在異步線程執(zhí)行完成之后強制執(zhí)行當(dāng)前dispatch所在區(qū)域方法,
    //那么這里直接引用self確保當(dāng)前作用域范圍內(nèi)能夠執(zhí)行完成并不會立即銷毀,可以保證在執(zhí)行完成dispatch之后銷毀內(nèi)容達(dá)到延遲銷毀的目的
    //如果需要立即中斷,則可以使用弱引用的方式來完成
    DispatchQueue.global().async {[weak p = self] in 
        p?.fn()
    }
}

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

相關(guān)閱讀更多精彩內(nèi)容

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