Swift中的訪問控制與內存管理

自定義Description內容打印

  • 通過遵守 CustomStringConvertibleCustomDebugStringConvertible 協議來自定義實例打印字符串
    1. 遵守 CustomStringConvertibleCustomDebugStringConvertible 協議
    2. 實現協議中的 description: StringdebugDescription: String類型變量 get 方法來確保內容自定義
  • 需要注意的點
    1. print 調用的是 CustomStringConveritable 協議的description
    2. debugPrintpo 調用的是 CustomDebugStringConveritable 協議的 debugDescription

關于 Self 的使用

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

關于斷言 assert

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

關于 fatalError 錯誤

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

訪問控制

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

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

    注: 絕大部分實體默認都是 internal 級別

  • 訪問級別的使用準則

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

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

  • 成員嵌套類型

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

注:

  1. 父類的訪問權限要小于子類
  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的訪問權限低于父類
}

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

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

*/
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 // 會報訪問錯誤
}
}

  • gettersetter 訪問權限

    1. 兩者默認自動接收他們所屬環境的訪問級別
    2. 可以給 setter 單獨設置一個比 getter 更低的訪問級別,用以限制寫的權限
    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 }
        }
    
    }
    
  • 公開部分方法的訪問權限可以通過 public 修飾

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

  • 間接訪問權限影響

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

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

初始化器

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

注意:

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

枚舉類型的 case 訪問權限

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

協議的訪問權限

  • 特點
    1. 協議中定義的內容的權限,將自動接收協議的訪問級別,不能單獨設置訪問級別
    2. 協議實現的訪問級別必須 >= 類型的訪問級別,或者 >= 協議的訪問級別

注意:
public 修飾的類型/結構體,成員變量以及方法的默認類型是 internal 類型,權限比public權限小,所以此時遵守的協議的權限是 public ,所以會報錯

public protocol Runnable {
    func run()
}

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

擴展的訪問權限

  • 特點

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

    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()
    }
}

  • 技巧:方法的拆分存儲調用

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

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

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

當存在兩個重名的類型方法與實例方法的時候,在取值的時候,設定取值結果的類型來確定當前的靜態方法或者實例方法

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)

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

內存管理

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

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

閉包表達式默認會對用到的外層對象產生額外的強引用(對外層對象進行 retain 操作)


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

func test() {
    let p = Person()
    
    //會導致循環引用問題
    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去除循環引用問題
    lazy var fn: (()->()) = {[weak p = self] in 
        p?.run()
    }
    func run() { print("run") }
    deinit { print("deinit") }
}

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

逃逸閉包 @escaping

  • 特點
    1. 非逃逸閉包、逃逸閉包,一般都是當做參數傳遞給函數
    2. 非逃逸閉包:閉包調用發生在函數結束前,閉包調用在其作用域范圍內
    3. 逃逸閉包:閉包有可能會在函數結束后調用,閉包調用逃離了函數的作用域,需要通過 @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(){
    //這一塊可以根據具體業務來定,如果想要在異步線程執行完成之后強制執行當前dispatch所在區域方法,
    //那么這里直接引用self確保當前作用域范圍內能夠執行完成并不會立即銷毀,可以保證在執行完成dispatch之后銷毀內容達到延遲銷毀的目的
    //如果需要立即中斷,則可以使用弱引用的方式來完成
    DispatchQueue.global().async {[weak p = self] in 
        p?.fn()
    }
}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容