Swift5.0 - day7-擴展、訪問控制、內(nèi)存管理

一、擴展(Extension)

  • 1.1、擴展介紹

    • Swift中的擴展,有點類似于OC中的分類(Category)
    • 擴展可以為枚舉、結(jié)構(gòu)體、類、協(xié)議添加新功能;可以添加方法、計算屬性、下標、(便捷)初始化器、嵌套類型、協(xié)議等等
    • 擴展不能辦到的事情
      • 不能覆蓋原有的功能
      • 不能添加存儲屬性,不能向已有的屬性添加屬性觀察器 ;原因是:不允許改變原有的內(nèi)存結(jié)構(gòu)
      • 不能添加父類 ;因為牽扯到繼承也會改變內(nèi)存結(jié)構(gòu)的,所以不能添加父類
      • 不能添加指定初始化器,不能添加反初始化器
      • ...
  • 1.2、計算屬性、下標、方法、嵌套類型

    extension Double {
        var km: Double { self * 1_000.0 }
        var m: Double { self }
        var dm: Double { self / 10.0 }
        var cm: Double { self / 100.0 }
        var mm: Double { self / 1_000.0 }
    }
    extension Int {
        func repetitions(task: () -> Void) {
            for _ in 0..<self {
               task()
            }
        }
        mutating func square() -> Int { self = self * self
            return self
        }
        enum Kind { case negative, zero, positive }
    
        var kind: Kind {
            switch self {
            case 0: return .zero
            case let x where x > 0: return .positive default: return .negative
            }
        }
        subscript(digitIndex: Int) -> Int {
            var decimalBase = 1
            for _ in 0..<digitIndex { 
                 decimalBase *= 10 
            }
            return (self / decimalBase) % 10
        }
    }
    extension Array {
        subscript(nullable idx: Int) -> Element? {
            if (startIndex..<endIndex).contains(idx) {
                return self[idx]
            }
            return nil
        }
    }
    
  • 1.3、協(xié)議、初始化器

    • 如果希望自定義初始化器的同時,編譯器也能夠生成默認初始化器(可以在擴展中編寫自定義初始化器),如下面的 Point 除了系統(tǒng)生成的 4 個初始化器,還多了擴展中的 1 個初始化器

    • required初始化器也不能寫在擴展中

      class Person {
          var age: Int
          var name: String
          init(age: Int, name: String) {
              self.age = age
              self.name = name
          }
      }
      extension Person : Equatable {
         static func == (left: Person, right: Person) -> Bool { 
             left.age == right.age && left.name == right.name
         }
         convenience init() {
             self.init(age: 0, name: "") 
         }
      }
      struct Point {
         var x: Int = 0
         var y: Int = 0 
      }
      extension Point {
         init(_ point: Point) {
            self.init(x: point.x, y: point.y) 
         }
      }
      
      var p1 = Point()
      var p2 = Point(x: 10)
      var p3 = Point(y: 20)
      var p4 = Point(x: 10, y: 20)
      var p5 = Point(p4)
      
  • 1.4、協(xié)議

    • 如果一個類已經(jīng)實現(xiàn)了協(xié)議的所有要求,但是還沒有聲明它遵守了這個協(xié)議,可以通過擴展來讓它遵守這個協(xié)議,如下

      protocol TestProtocol {
          func test()
      }
      class TestClass {
          func test() {
             print("test")
          }
      }
      extension TestClass: TestProtocol {}
      
    • 編寫 一個 函數(shù),判斷一個整數(shù)是否為奇數(shù)?

      extension BinaryInteger {
          func isOdd() -> Bool {
              self % 2 != 0
          }
      }
      

      提示:整數(shù)是繼承于BinaryInteger,在擴展里面寫比較好,這樣只要是遵守 BinaryInteger 協(xié)議的都可以調(diào)用

    • 擴展可以為協(xié)議提供默認實現(xiàn),也間接實現(xiàn) [可選協(xié)議] 的效果

      protocol TestProtocol {
          func test1()
      }
      extension TestProtocol {
          func test1() { 
              print("TestProtocol test1")
          }
          func test2() {
              print("TestProtocol test2") 
          }
          // 擴展類方法
          static func test3() {
              print("TestProtocol test3") 
          }
      }
      
      var cls = TestClass()
      cls.test1() // TestClass test1
      cls.test2() // TestClass test2
      var cls2: TestProtocol = TestClass()
      cls2.test1() // TestClass test1
      cls2.test2() // TestProtocol test2
      

      提示:這里主要說下 cls2.test2() 為什么打印 TestProtocol test2

      • 這是一個細節(jié)在協(xié)議中沒有聲明一個方法,在協(xié)議擴展里面實現(xiàn)了一個新的方法如上面extension TestProtocol里面的 test2(),但是在遵守協(xié)議的類里面實現(xiàn)了協(xié)議中沒聲明的方法test2(),在 var cls2: TestProtocol = TestClass(),特指出協(xié)議TestProtocol,那么在 cls2.test2()調(diào)用的時候,如下協(xié)議里面沒聲明 test2(),就會去調(diào)用協(xié)議擴展里面實現(xiàn)的test2() 方法
  • 1.5、泛型

    class Stack<E> {
       var elements = [E]()
       func push(_ element: E) {
          elements.append(element) 
       }
       func pop() -> E { 
          elements.removeLast() 
       }
       func size() -> Int { 
          elements.count 
       }
    }
    

    擴展中依然可以使用原類型中的泛型類型 extension Stack {

    func top() -> E { elements.last! } }
    

    符合條件才擴展

    extension Stack : Equatable where E : Equatable {
        static func == (left: Stack, right: Stack) -> Bool { 
            left.elements == right.elements
        } 
    }
    

二、訪問控制(Access Control)

  • 2.1、訪問權限的 5 個級別
    在訪問權限這塊,Swift提供了 5 個不同的訪問級別(以下是從 高 -> 低 排列,實體指被訪問級別修飾的內(nèi)容,模塊等同于文件)

    • open:允許在定義實體的模塊、其他模塊中訪問,允許其他模塊進行繼承、重寫(open 只能用在類、類成員上)
    • public:允許在定義實體的模塊、其他模塊中訪問,不允許其他模塊進行繼承、重寫
    • internal:只允許在定義實體的模塊中進行訪問,不允許在其他模塊中進行訪問

      提示:internal只允許在當前的項目訪問,不允許成為一個庫,在其他模塊進行使用

    • fileprivate:只允許在定義實體的原文件中訪問

      提示:fileprivate 代表只能在當前的 .swift 文件中進行訪問

    • private:只允許在定義實體的封閉聲明中訪問

      提示:private:只允許在封閉的實體內(nèi)訪問的意思是,如下:age 只能在person 的大括號內(nèi)進行訪問

      class person {
          private var age: Int = 0
      }
      

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

  • 2.2、訪問級別的使用準側(cè),一個實體不可以被更低訪問級別的實體定義,如下

    • 變量/常量類型 變量/常量,如下:Person的類型(fileprivate)是小于 person 變量類型(internal)的

    • 參數(shù)類型、返回值類型 函數(shù),如下, 默認:Int、Double 都是 public

      fileprivate  func test(_ num: Int) -> Double  {
            return 2.0
      }
      
    • 父類 子類,這個比較好理解,因為我們訪問子類的時候,必然會用到父類

    • 父協(xié)議 子協(xié)議

    • 原類型 typealias

    • 原始值類型、關聯(lián)值類型 枚舉類型

    • 定義類型 A 時用到其他類型 類型A

  • 2.3、元組類型的訪問級別是所有成員類型最低的那個,也就是下面的 data1data2 的級別要小于右邊元祖中最小的級別

    internal struct Dog {}
    fileprivate class Person {}
    // (Dog, Person)的訪問級別是fileprivate 
    fileprivate var data1: (Dog, Person) 
    private var data2: (Dog, Person)
    
  • 2.4、泛型類型
    泛型類型的訪問級別是 類型的訪問級別 以及 所有泛型類型參數(shù)的訪問級別 中最低的那個

    internal class Car {}
    fileprivate class Dog {}
    public class Person<T1, T2> {}
    fileprivate var p = Person<Car, Dog>()
    

    Person<Car, Dog>的訪問級別是 fileprivate

  • 2.5、成員、嵌套類型
    類型的訪問級別會影響成員(屬性、方法、初始化器、下標)、嵌套類型的默認訪問級別

    • 一般情況下,類型為 privatefileprivate,那么成員\嵌套類型默認也是 privatefileprivate

    • 一般情況下,類型為 internalpublic,那么成員\嵌套類型默認是internal

      public class PublicClass {
          public var p1 = 0 // public
          var p2 = 0 // internal
          fileprivate func f1() {} // fileprivate private
          func f2() {} // private
      }
      class InternalClass { // internal
          var p = 0 // internal
          fileprivate func f1() {} // fileprivate private
          func f2() {} // private
      }
      fileprivate class FilePrivateClass { // fileprivate
          func f1() {} // fileprivate
          private func f2() {} // private
      }
      private class PrivateClass { // private
          func f() {} // private
      }
      
  • 2.6、成員的重寫

    • 子類重寫成員的訪問級別必須 ≥ 子類的訪問級別,或者 ≥ 父類被重寫成員的訪問級別
    • 父類的成員不能被成員作用域外定義的子類重寫
  • 2.7、觀察編譯代碼

    • 下面的代碼能過不能通過,要看放的位置,如果:在一個文件內(nèi)編譯器不報錯,不在同一個文件內(nèi)會報錯

      private class Person {}
      fileprivate class Student : Person {}
      
      private struct Dog {
          var age: Int = 0
          func run() {}
      }
      fileprivate struct Person {
          var dog: Dog = Dog()
          mutating func walk() {
               dog.run()
               dog.age = 1
          }
      }
      
    • 特別指出一下下面的代碼: private struct Dog 里面的 agerun() 其實也是 private ,只是在訪問域是 和 Dog一個等級,所以在 Person 里面也可以訪問

      class test {
           private struct Dog {
               var age: Int = 0
               func run() {}
           }
           fileprivate struct Person {
               var dog: Dog = Dog()
               mutating func walk() {
                   dog.run()
                   dog.age = 1
               }
           }
      }
      

      提示:如果我們可以在 var age: Int = 0前面加 private,那么 dog.age = 1 會直接報錯

  • 2.8、getter、setter

    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 }
        }
     }
    
    var person = Person()
    person.age = 100
    print(person.age)
    

    提示:person.age = 100 會直接報錯,因為我們在 private(set) var age = 0 設置的是 private(set),這樣屬性只能訪問不能修改

    定義一個全局變量,只能在該文件內(nèi)進行修改,其他文件內(nèi)只能讀,如下

    fileprivate(set) public var num = 10
    
  • 2.9、初始化器

    • 如果一個 public 類想在另一個模塊調(diào)用編譯生成的默認無參初始化器,必須顯式提供public的無參初始化器;因為public類的默認初始化器是internal級別;如下如果 Person 是在一個 .dylib 里面的,那么想要訪問 var person = Person(),Person 類里面的 init() {} 前面必須加 public

      public  class Person {
           public init() {
           }
      }
      var person = Person()
      
    • required 初始化器 ≥ 它的默認訪問級別

    • 如果結(jié)構(gòu)體有 private\fileprivate 的存儲實例屬性,那么它的成員初始化器也是private\fileprivate;否則默認就是internal

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

      提示:在其他文件 var point = Point(x: 10,y: 20) 會報錯,因為fileprivate var x = 0 設置后,整個指定初始化器都是 fileprivate

  • 2.10、枚舉類型的 case

    • 不能給enum的每個case單獨設置訪問級別

    • 每個case自動接收enum的訪問級別;public enum定義的case也是public

      public enum  Season {
          case spring
          case summer
      }
      
  • 2.11、協(xié)議

    • 協(xié)議中定義的要求自動接收協(xié)議的訪問級別,不能單獨設置訪問級別;public協(xié)議定義的要求也是public

    • 協(xié)議實現(xiàn)的訪問級別必須 ≥ 類型的訪問級別,或者 ≥ 協(xié)議的訪問級別

      public protocol Runnable {
          func run()
      }
      fileprivate class Person : Runnable {
          fileprivate func run() {}
      }
      

      提示:fileprivate func run() {} 的權限要大于等于 類 Person協(xié)議Runnable 中的一個

  • 2.12、擴展

    • 如果有顯式設置擴展的訪問級別,擴展添加的成員自動接收擴展的訪問級別,如下:run()的權限就是 fileprivate

      class Person {}
      fileprivate extension Person {
           func run() {
           }
      }
      
    • 如果沒有顯式設置擴展的訪問級別,擴展添加的成員的默認訪問級別,跟直接在類型中定義的成員一樣 ,如下: func run() 寫在 類 和 擴展里面沒啥區(qū)別

      class Person {
          func run() {
           }
      }
      extension Person {
      }
      
    • 可以單獨給擴展添加的成員設置訪問級別,如下面給 func run() 設置 fileprivate

      class Person {}
      extension Person {
         fileprivate  func run() {
           }
      }      
      
    • 不能給用于遵守協(xié)議的擴展顯式設置擴展的訪問級別,如下:不能在 extension Person 前面設置訪問級別

      protocal Runnable {}
      class Person {}
      extension Person:  Runnable {
          func run() {
           }
      }
      
    • 在同一文件中的擴展,可以寫成類似多個部分的類型聲明

      • 在原本的聲明中聲明一個私有成員,可以在同一文件的擴展中訪問它

      • 在擴展中聲明一個私有成員,可以在同一文件的其他擴展中、原本聲明中訪問它

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

        提示:如果上面的段代碼在同一個文件內(nèi),那么后面兩個代碼的功能類似寫在第一段代碼里面,所有雖然寫的 private,但是可以訪問

  • 2.13、講方法賦值給let或者var
    方法也可以像函數(shù)那樣,賦值給一個let或者var

    struct Person {
         var age: Int
         func run(_ v: Int) { print("func run", age, v) }
         static func run(_ v: Int) { print("static func run", v) }
    }
    let fn1 = Person.run
    fn1(10) // static func run 10
    
    let fn2: (Int) -> () = Person.run
    fn2(20) // static func run 20
    
    let fn3: (Person) -> ((Int) -> ()) = Person.run
    fn3(Person(age: 18))(30) // func run 18 30
    

三、內(nèi)存管理

  • 3.1、內(nèi)存管理

    • 跟OC一樣,Swift也是采取基于引用計數(shù)的ARC內(nèi)存管理方案(針對堆空間)
    • Swift的ARC中有3種引用
      • 強引用(strong reference):默認情況下,引用都是強引用

      • 弱引用(weak reference):通過weak定義弱引用

        提示:弱引用

        • 必須是可選類型的var,因為實例銷毀后,ARC會自動將弱引用設置為nil
        • ARC自動給弱引用設置nil時,不會觸發(fā)屬性觀察器
      • 無主引用(unowned reference):通過unowned定義無主引用

        • 不會產(chǎn)生強引用,實例銷毀后仍然存儲著實例的內(nèi)存地址(類似于OC中的 unsafe_unretained)
        • 試圖在實例銷毀后訪問無主引用,會產(chǎn)生運行時錯誤(野指針)
        • Fatal error: Attempted to read an unowned reference but object 0x0 was already deallocated
  • 3.2、weak 和 unowned 的使用限制
    weakunowned 只能用在類實例上面,類是放在 堆空間

    protocol Livable : AnyObject {}
    class Person {}
    
    weak var p0: Person?  
    weak var p1: AnyObject?  //  AnyObject  代表所有類的實例
    weak var p2: Livable? // 寫上 : AnyObject 代表這個協(xié)議只能被類遵守
    
    unowned var p10: Person?
    unowned var p11: AnyObject?
    unowned var p12: Livable?
    
  • 3.3、autoreleasepool,有時候為了緩解內(nèi)存壓力,我們可以把創(chuàng)建實例放在自動釋放池里面

    public func autoreleasepool<Result>(invoking body: () throws -> Result) rethrows -> Result
    autoreleasepool {
         let p = MJPerson(age: 20, name: "Jack")
         p.run()
    }
    
  • 3.4、循環(huán)引用 (Reference Cycle)

    • weak 和 unowned 都能解決循環(huán)引用的問題,unowned 要比 weak 少一些性能消耗
    • 在生命周期中可能會變?yōu)?nil 的使用 weak
    • 初始化賦值后再也不會變?yōu)?nil 的使用 unowned
  • 3.5、閉包的循環(huán)引用

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

    • 下面的代碼會造成循環(huán)引用,導致 Person 對象無法釋放(看不到 Person 的 deinit 被調(diào)用)

      class Person {
          var fn: (() -> ())?
          func run() { print("run") }
          deinit {
              print("deinit") 
          }
      }
      func test() {
          let p = Person()
          p.fn = { p.run() }
      }
      test()
      

      提示:

      • 對象 p 強引用閉包表達式,而閉包表達式又強引用對象 p,從而造成相互強引用(循環(huán)引用)

      • 解決循環(huán)引用的辦法- 捕獲列表

      • 辦法一:[weak 對象],這樣就代表閉包表達式對 對象p 進行弱引用

        p.fn = { 
             [weak p] in 
             p?.run()
        }
        
      • 辦法二:[unowned 對象],這樣就代表閉包表達式對 對象p 進行弱引用

        p.fn = { 
             [unowned p] in 
             p.run()
        }
        
      • 如果閉包有參數(shù),比如上面的 var fn: ((Int) -> ())?,那么解決循環(huán)引用如下,age 是參數(shù)的名字,捕獲列表 [unowned p] 要寫在參數(shù)列表的前面,否則會報錯

        p.fn = { 
             [unowned p](age) in 
             p.run()
        }
        
    • 如果想在定義閉包屬性的同時引用 self,這個閉包必須是 lazy的(因為在實例初始化完畢之后才能引用 self),下面的閉包 fn 內(nèi)部如果用到了實例成員(屬性、方法),編譯器會強制要求明確寫出self

      class Person {
          lazy var fn: (() -> ()) = {
               [weak self] in
               self?.run()
          }
          func run() { print("run") }
          deinit { print("deinit") }
      }
      
      func test() {
          let person = Person()
          person.fn()
      }
      
      test()
      
    • 如果 lazy 屬性是閉包調(diào)用的結(jié)果,那么不用考慮循環(huán)引用的問題(因為閉包調(diào)用后,閉包的生命周期就結(jié)束了)

      class Person {
          var age: Int = 0
          lazy var getAge: Int = {
               self.age
          }()
          deinit { print("deinit") }
      }
      

      提示 : lazy var getAge: Int = { self.age }() 雖然閉包表達式對 self 進行了強引用,但是這個閉包表達式是直接 () 執(zhí)行的,對self的強引用也就結(jié)束了,等同于 lazy var getAge: Int = self.age,所以不會產(chǎn)生循環(huán)引用

  • 3.6、@escaping

    • 非逃逸閉包、逃逸閉包,一般都是當做參數(shù)傳遞給函數(shù)

    • 非逃逸閉包:閉包調(diào)用發(fā)生在函數(shù)結(jié)束前,閉包調(diào)用在函數(shù)作用域內(nèi),函數(shù)結(jié)束之前閉包就會調(diào)用結(jié)束

    • 逃逸閉包:閉包有可能在函數(shù)結(jié)束后調(diào)用,閉包調(diào)用逃離了函數(shù)的作用域,需要通過 @escaping 聲明,也就說函數(shù)結(jié)束后閉包也可能沒有進行調(diào)用

      import Dispatch
      typealias Fn = () -> ()
      // fn是非逃逸閉包
      func test1(_ fn: Fn) { fn() }
      // fn是逃逸閉包
      var gFn: Fn?
      func test2(_ fn: @escaping Fn) { gFn = fn }
      // fn是逃逸閉包
      func test3(_ fn: @escaping Fn) {
          DispatchQueue.global().async {
             fn()
          }
      }
      
    • DispatchQueue.global().async也是一個逃逸閉包

      class Person {
          var fn: Fn
          // fn是逃逸閉包
          init(fn: @escaping Fn) {
              self.fn = fn
          }
          func run() {
              // DispatchQueue.global().async也是一個逃逸閉包
              // 它用到了實例成員(屬性、方法),編譯器會強制要求明確寫出self 
              DispatchQueue.global().async {
                  self.fn()
              }
      }
      

      提示:DispatchQueue.global().async { self.fn() } 里面使用self不會造成循環(huán)引用,因為是閉包表達式強引用 self,而 self 沒有對閉包表達式進行強引用,單向強引用不會造成循環(huán)引用

  • 3.7、逃逸閉包的注意點:逃逸閉包不可以捕獲 inout 參數(shù)

    typealias Fn = () -> ()
    func other1(_ fn: Fn) { fn() }
    func other2(_ fn: @escaping Fn) { fn() }
    func test(value: inout Int) -> Fn {
         other1 { value += 1 } 
    
         // error: 逃逸閉包不能捕獲inout參數(shù)
         other2 { value += 1 }
    
         func plus() { value += 1 }
         // error: 逃逸閉包不能捕獲inout參數(shù) 
         return plus
    }
    
    逃逸閉包不可以捕獲 inout 參數(shù)
    • 逃逸閉包不可以捕獲 inout 參數(shù),假如可以捕獲,我們可以試想一下代碼,abc()函數(shù)結(jié)束后 x 變量就銷毀了,但是 other2 里面的代碼可能還沒執(zhí)行,就會造成內(nèi)存壞訪問

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

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

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