鞏固—Swift 5.0 基礎知識(三)

下標

  • 語法: 下標允許你通過在實例名稱后面的方括號寫入一個或多個值來查詢類型的實例。類似于計算屬性語法。下標可以讀寫或只讀,getter和setter決定

      struct TimesTable {
          let multiplier: Int
          subscript(index: Int) -> Int { //只讀
              return multiplier * index
          }
      }
      let threeTimesTable = TimesTable(multiplier: 3)
      print("six times three is \(threeTimesTable[6])")
    
  • 下標可以采用任意數量的輸入參數,這些輸入參數可以是任意類型。也可以返回任何類型。

      struct Matrix {
          let rows: Int, columns: Int
          var grid: [Double]
          init(rows: Int, columns: Int) {
              self.rows = rows
              self.columns = columns
              grid = Array(repeating: 0.0, count: rows * columns)
          }
          func indexIsValid(row: Int, column: Int) -> Bool {
              return row >= 0 && row < rows && column >= 0 && column < columns
          }
          subscript(row: Int, column: Int) -> Double {
              get {
                  assert(indexIsValid(row: row, column: column), "Index out of range")
                  return grid[(row * columns) + column]
              }
              set {
                  assert(indexIsValid(row: row, column: column), "Index out of range")
                  grid[(row * columns) + column] = newValue
              }
          }
      }
      
      //創建實例
      var matrix = Matrix(rows:2,columns:2)
      //是否下標訪問
      matrix[0,1] = 1.5
      matric[1.0] = 3.2
    

繼承

  • 定義:類可以從另一個類繼承方法,屬性和其他特征。
  • 重寫:用關鍵字override向父方法澄清覆蓋此方法的實現。
  • 可以通過super.(方法名or屬性)來調用方法或屬性,實現父類部分功能。
  • 重點:無論是存儲屬性還是計算屬性, 都只能重寫為計算屬性
    • 可以覆蓋父類的只讀屬性為讀寫,但不能覆蓋父類的讀寫屬性為只讀。
    • 不能把不可變存儲類型重寫為可變存儲類型
    • 不能把不可變存儲類型重寫為讀寫計算類型
    • 能把可變存儲類型重寫為讀寫計算類型(但不能是只讀計算類型)
    • 能把只讀/讀寫計算屬性重寫為讀寫為讀寫計算類型
    • 能給變量型存儲變量重寫屬性觀察器(不可變不行,沒有意義)
    • 能給讀寫型計算屬性重寫屬性觀察器(只讀的不行,沒有意義)
    • 可以給繼承來的屬性添加屬性觀察者,但不能給繼承來的常量存儲屬性或只讀計算屬性添加屬性觀察者,因為這些屬性的值不能設置,不適合重寫willSet和didSet,同時,你也不能對同一個屬性同時提供屬性觀察和setter方法。
  • 可以通過關鍵字final來標記為最終來防止方法,屬性或下標被覆蓋。

初始化

  • 定義:是指準備要使用的類,結構體或枚舉實例化的過程。

  • 默認屬性值:如果屬性始終采用相同的初始值,請提供默認值,而不是在初始化中設置值。

      struct Fahrenheit {
          var temperature = 32.0
      }
    
  • 與函數和方法參數一樣,初始化參數可以在初始化程序體內部使用的參數名稱和調用程序時使用的參數標簽。如果你不提供一個初始化器,swift會為每個參數自動提供參數標簽

  • 指定構造器和便利構造器:

  • 規則:

    1. 指定構造器必須從它的直系父類調用指定構造器
    2. 便利構造器必須從相同的類里調用另一個初始化構造器
    3. 便利構造器最終必須調用一個指定構造器
      • 指定構造器必須總是向上委托
      • 便利構造器必須總是橫向委托
  • 兩段式初始化:第一階段,每個存儲屬性被引入類為分配一個初始值;第二階段,每個類都有機會在新的實例準備使用之前來定制它的存儲屬性。

  • 重要:

    • 指定構造器必須保證在向上委托給父類初始化之前,自身的所有屬性都要初始化完成。

    • 便利構造器必須先委托同類的初始化器,然后才能給自身的其他屬性賦新值。

        init(parameters){        
            聲明
        } 
        convenient init(參數){
            聲明
        }
      

自動引用計數器

  • 自動引用計數:強引用實例一次,則引用計數器+1,弱引用不會使引用計數器的變化,銷毀引用的實例時,則引用計數器-1,知道引用計數=0時,則此對象徹底被銷毀。
  • 解決實例之間的循環強引用:
    • 弱引用:在要造成循環引用的實例面前加個關鍵字weak.弱引用總是可選類型
    • 無主引用:加上關鍵字unowned,和弱引用不同,無主引用比其他實例有相同或更長的生命周期。注意:1. 使用無主引用必須確保引用始終只指向一個未銷毀的實例;2. 如果試圖在實例被銷毀后,訪問該實例的無主引用,會觸發運行時錯誤。
  • 使用場景介紹:
    • Person和Apartment的例子展示了兩個屬性的值都允許為nil,并會潛在的產生循環強引用。這種場景最適合用弱引用來解決。

    • Customer和CreditCard的例子展示了一個屬性的值允許為nil,而另一個屬性的值不允許為nil,這也可能會產生循環強引用。這種場景最適合通過無主引用來解決。

    • 存在著第三種場景,在這種場景中,兩個屬性都必須有值,并且初始化完成后永遠不會為nil。在這種場景中,需要一個類使用無主屬性,而另外一個類使用隱式解析可選屬性。

        class Country {
            let name: String
            var capitalCity: City! //隱式解析可選屬性
            init(name: String, capitalName: String) {
                self.name = name
                self.capitalCity = City(name: capitalName, country: self)
            }
        }
        
        class City {
            let name: String
            unowned let country: Country //無主引用類型
            init(name: String, country: Country) {
                self.name = name
                self.country = country
            }
        }  
      
    • 解決閉包引起的循環強引用:在定義閉包的時候同時定義捕獲列表作為閉包的一部分。只要在閉包內使用self的成員,就要用self.someProperty或者self.someMethod()(而不只是someProperty或someMethod())。這提醒你可能會一不小心就捕獲了self。

        //如果閉包有參數列表和返回類型,把捕獲列表放在他們前面:
        lazy var someClosure: (Int, String) -> String = {
            [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
            // 這里是閉包的函數體
        }
        //如果閉包沒有指明參數或者返回類型:
        lazy var someClosure: Void -> String = {
            [unowned self, weak delegate = self.delegate!] in
            // 這里是閉包的函數體
        }
      
      • 使用環境:
        • 在閉包和捕獲的實例總是互相引用并且總是同時銷毀時,將閉包內的捕獲定義為無主引用(unowned)
        • 相反的,在被捕獲的引用可能會變成nil的情況下,將閉包內的捕獲定義為弱引用。因為弱引用總是可選類型。
        • 如果被捕獲的引用絕對不會變為nil,應該用無主引用,而不是弱引用。

可選鏈

  • 定義:可選鏈式調用是一種可以在當前值可能為nil的可選值上請求和調用屬性、方法及下標的方法。如果可選值有值,那么調用就會成功;如果可選值是nil,那么調用將返回nil。多個調用可以連接在一起形成一個調用鏈,如果其中任何一個節點為nil,整個調用鏈都會失敗,即返回nil。
  • 注意:
    • 可選鏈只要其中某一鏈為nil,則整個式子都為nil。以此來檢驗可選鏈是否調用成功
    • 不論你調用的屬性、下標、方法等的原值是不是可選值,調用可選值的返回結果是與原類型相同的可選類型。
    • john.residence?.address = createAddress():假如可選鏈式調用失敗時,等號右側的代碼不會被執行
  • 使用:
    • 通過可選鏈式訪問屬性:john.residence?.numberOfRooms

    • 通過可選鏈式調用方法:john.residence?.printNumberOfRooms()

    • 通過可選鏈式訪問下標(應該將問號放在下標方括號的前面而不是后面):john.residence?[0].name

    • 訪問可選類型的下標:

        var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
        testScores["Dave"]?[0] = 91
        testScores["Bev"]?[0] += 1
        testScores["Brian"]?[0] = 72
        // "Dave" 數組現在是 [91, 82, 84],"Bev" 數組現在是 [80, 94, 81] 
      
    • 連接多層可選鏈式調用(不會增加返回值的可選層級,Int??...是不會出現的):john.residence?.address?.street

錯誤處理

  • 定義:是響應錯誤以及從錯誤匯總恢復的過程。

  • 格式:func 函數名() throws -> 返回值 {} //為了表示一個函數、方法或構造器可以拋出錯誤,在函數聲明的參數列表之后加上throws關鍵字。

  • 處理錯誤格式:

      do {
          try(try? try!) expression
          statements
      } catch pattern 1 {
          statements
      } catch pattern 2 where condition {
          statements
      }
    
  • defer: 即將離開當前代碼塊時執行一系列語句(該語句能做一些必要的清理工作,不管以何種方法離開當前代碼塊的)。

類型轉換

  • 定義:可以判斷實例的類型,也可以將實例看做是其父類或子類的實例。(as(強制轉換)和is(判斷其所屬類型))
    • as?(當你不確定向下轉型一定會成功時,總是返回一個可選值) or as!(確定向下轉型一定會成功時用,否則會error)
  • Any 和 AnyObject 的類型轉換
    • Any可以表示任何類型,包括函數類型等
    • AnyObject可以表示任何類類型的實例

擴展

  • 定義:擴展就是為一個已有的類、結構體、枚舉類型或者協議類型添加新功能。關鍵字extension
  • 擴展范圍(可以添加新功能,但不能重寫已有的功能):
    • 添加計算型屬性和計算型類型屬性(不能添加存儲屬性,也不可以為已有屬性添加屬性觀察器)
    • 定義實例方法和類型方法
    • 提供新的便利構造器(不能添加指定構造器)
    • 定義下標
    • 定義和使用新的嵌套類型
    • 使一個已有類型符合某個協議

協議

  • 定義:規定了用來實現某一特定任務或功能的方法、屬性以及其他需要的東西。

  • 規范:

      protocol 協議名 {
          //協議的定義部分
      }
    
  • 要求:

    • 屬性要求:要求遵循協議的類型提供特定名稱和類型的實例屬性或類型屬性。協議不指定屬性是存儲型屬性還是計算型屬性,它只指定屬性的名稱和類型。此外,協議還指定屬性是可讀的還是可讀可寫的。(總是var類型)
    • 方法要求:協議可以要求遵循協議的類型實現某些指定的實例方法或類方法。這些方法作為協議的一部分,像普通方法一樣放在協議的定義中,但是不需要大括號和方法體。可以在協議中定義具有可變參數的方法,和普通方法的定義方式相同。但是,不支持為協議中的方法的參數提供默認值。
    • mutating方法要求:在值類型(即結構體和枚舉)的實例方法中,將mutating關鍵字作為方法的前綴,寫在func關鍵字之前,表示可以在該方法中修改它所屬的實例以及實例的任意屬性的值---類中可不用寫,結構體和枚舉則必須要
    • 構造器要求:只寫申明,不寫花括號和構造器的實體。遵循協議的類中實現構造器時,都必須為構造器前面加上required。也可以加上final關鍵字表示,如果子類重寫了遵守協議的某個方法,則必須同時加上requied,override
  • 協議作為類型

    • 作為函數、方法或構造器的參數類型或返回值類型
    • 作為常量、變量或屬性的類型
    • 作為數組、字典或其他容器里的元素類型
  • 使用:

    • 委托(代理)模式

    • 通過擴展遵守協議

    • 協議的繼承

    • 在協議名后添加關鍵字class:限制只能被類類型遵守

    • 協議合成:格式----SomeProtocol & AnotherProtocol

    • 使用is,as來檢查協議一致性,即是否符合某協議。

    • 可選的協議要求:@objc關鍵字來引用OC的代碼。

        @objc protocol CounterDataSource {
            @objc optional func incrementForCount(count: Int) -> Int
            @objc optional var fixedIncrement: Int { get }
        } 
      
    • 協議擴展:可以通過擴展來為遵循協議的類型提供屬性、方法以及下標的實現

    • 提供默認實現:可以通過協議擴展來為協議要求的屬性、方法以及下標提供默認的實現。

    • 為協議擴展添加限制條件:在擴展協議的時候,可以指定一些限制條件,只有遵循協議的類型滿足這些限制條件時,才能獲得協議擴展提供的默認實現。

        extension Collection where Iterator.Element: TextRepresentable {
            var textualDescription: String {
                let itemsAsText = self.map { $0.textualDescription }
                return "[" + itemsAsText.joined(separator: ", ") + "]"
            }
        }
      

泛型

  • 作用:泛型代碼讓你能夠根據自定義的需求,編寫出適用于任意類型、靈活可重用的函數及類型。它能讓你避免代碼的重復,用一種清晰和抽象的方式來表達代碼的意圖。

  • 實例:

      func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
          (a,b) = (b,a)
      }
    
  • 擴展一個泛型類型:不需要在擴展匯總提供類型參數列表,原始類型定義的類型參數可以在擴展中直接使用。

  • 類型約束:可以指定一個類型參數必須繼承自指定類,或者符合一個特定的協議或協議組合。

    • 類型約束語法:

            func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
            // 這里是泛型函數的函數體部分
        } 
      
  • 關聯類型:聲明一個或多個關聯類型作為協議定義的一部分,關聯類型為協議中的某個類型提供了一個占位名或別名,其代表的實際類型在協議被采納才會被指定。關鍵字associatedtype

      protocol Container {
          associatedtype ItemType //關聯類型,可在定義時指定類型(如:typealias ItemType = Int)
          mutating func append(_ item: ItemType)
          var count: Int { get }
          subscript(i: Int) -> ItemType { get }
      }
    
  • 可以通過擴展來遵守指定關聯類型

  • 泛型where語句

      //實例
      func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
          //函數體
      }
      //理解:
      1. someContainer 是一個 C1 類型的容器。
      2. anotherContainer 是一個 C2 類型的容器。
      3. someContainer 和 anotherContainer 包含相同類型的元素。
      4. someContainer 中的元素可以通過不等于操作符(!=)來檢查它們是否彼此不同。
    
  • 具有泛型where子句的擴展

      extension Stack where Element: Equatable {
          func isTop(_ item: Element) -> Bool {
              guard let topItem = items.last else {
                  return false
              }
              return topItem == item
          }
      }
      //注意:
      1. 如果`Stack`的定義沒有要求它的元素是符合Equatable協議的,那么使用`==`運算符會導師error。
      2. 使用泛型 where 子句可以為擴展添加新的條件,因此只有當棧中的元素符合 Equatable 協議時,擴展才會添加 isTop(_:) 方法。
    
  • 具有泛型where子句的關聯類型

          protocol Container {
              associatedtype Item
              mutating func append(_ item: Item)
              var count: Int { get }
              subscript(i: Int) -> Item { get }
          
              associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
              func makeIterator() -> Iterator //要求:無論迭代器是什么類型,迭代器中的元素類型,必須和容器項目的類型保持一致。makeIterator() 則提供了容器的迭代器的訪問接口。
          }
    
  • 泛型下標

      //實例
      extension Container {
          subscript<Indices: Sequence>(indices: Indices) -> [Item]
              where Indices.Iterator.Element == Int {
                  var result = [Item]()
                  for index in indices {
                      result.append(self[index])
                  }
                  return result
          }
      }
      //注意: 
      1. 在尖括號中的泛型參數 Indices,必須是符合標準庫中的 Sequence 協議的類型。
      2. 下標使用的單一的參數,indices,必須是 Indices 的實例。
      3. 泛型 where 子句要求 Sequence(Indices)的迭代器,其所有的元素都是 Int 類型。這樣就能確保在序列(Sequence)中的索引和容器(Container)里面的索引類型是一致的。
    

高級運算符

  • 位運算符
    • 按位取反(~):全部取反

        let initialBits: UInt8 = 0b00001111
        let invertedBits = ~initialBits // 等于 0b11110000 
      
    • 按位與(&):只有全為1才為1,有0則為0

        let firstSixBits: UInt8 = 0b11111100
        let lastSixBits: UInt8  = 0b00111111
        let middleFourBits = firstSixBits & lastSixBits // 等于 00111100
      
    • 按位或(|):只有全為0才為0,有1則為1

        let someBits: UInt8 = 0b10110010
        let moreBits: UInt8 = 0b01011110
        let combinedbits = someBits | moreBits // 等于 11111110
      
    • 按位異或(^):相同為0,不同為1

        let firstBits: UInt8 = 0b00010100
        let otherBits: UInt8 = 0b00000101
        let outputBits = firstBits ^ otherBits // 等于 00010001
      
    • 按位左移、右移(<<>>):按位左(相當于此數x2)、右(相當于此數/2)移動指定位數。

      • 無符號整數的位移運算
        • 已經存在的位按指定的位數進行左移和右移。

        • 任何因移動而超出整型存儲范圍的位都會被丟棄。

        • 用 0 來填充移位后產生的空白位。

            let shiftBits: UInt8 = 4 // 即二進制的 00000100
            shiftBits << 1           // 00001000 == 8 == 4*2
            shiftBits << 2           // 00010000 == 16 == 4*2*2
            shiftBits >> 2           // 00000001 == 1 == 4/2/2
          
      • 有符號整數的位移運算
        • 與無符號規則一樣,多增一條:當對整數進行按位右移運算時,遵循與無符號整數相同的規則,但是對于移位產生的空白位使用符號位進行填充,而不是用 0。
  • 溢出運算符
    • 溢出加法 &+

    • 溢出減法 &-

    • 溢出乘法 &*

        var unsignedOverflow = UInt8.max
        // unsignedOverflow 等于 UInt8 所能容納的最大整數 255
        unsignedOverflow = unsignedOverflow &+ 1
        // 此時 unsignedOverflow 等于 0
        
        var unsignedOverflow = UInt8.min
        // unsignedOverflow 等于 UInt8 所能容納的最小整數 0
        unsignedOverflow = unsignedOverflow &- 1
        // 此時 unsignedOverflow 等于 255
      
  • 優先級和結合性
    • 規則:1. 高優先級的運算符會先被計算。 2. 結合性決定相同優先級的運算符是如何結合的
  • 運算符函數
    • 類和結構體可以為現有的運算符提供自定義的實現,這通常被稱為運算符重載。
        struct Vector2D {
            var x = 0.0, y = 0.0
        }
        
        extension Vector2D {
            static func + (left: Vector2D, right: Vector2D) -> Vector2D {
                return Vector2D(x: left.x + right.x, y: left.y + right.y)
            }
        }
* 前綴(`prefix`)和后綴(`postfix`)運算符

        extension Vector2D {
            static prefix func - (vector: Vector2D) -> Vector2D {
                return Vector2D(x: -vector.x, y: -vector.y)
            }
        }
* 復合賦值運算符

        extension Vector2D {
            static func += (left: inout Vector2D, right: Vector2D) {
                left = left + right
            }
        }
* 注意:不能對默認的賦值運算符(=)進行重載。只有組合賦值運算符可以被重載。同樣地,也無法對三目條件運算符`(a ? b : c)`進行重載。
* 等價運算符:“相等”運算符(==)與“不等”運算符(!=)

        extension Vector2D {
            static func == (left: Vector2D, right: Vector2D) -> Bool {
                return (left.x == right.x) && (left.y == right.y)
            }
            static func != (left: Vector2D, right: Vector2D) -> Bool {
                return !(left == right)
            }
        }
* 自定義運算符:系統的運算符的特點:1.有優先級  2.結合性(左結合,右結合) 3.運算符的位置(前面,后面,中間)在自定義運算的時候必須確定的元素
    * infix(運算符在被操作的數據的中間)(多目)
    * prefix(放在數據的前面)--> (單目)
    * postfix(放在數據的后面)--> (單目)
    * associativity left  左結合 ; associativity right 右結合
    * 優先級(precedence n)

            infix operator +- { associativity left precedence 140 }
            func +- (left: Vector2D, right: Vector2D) -> Vector2D {
                return Vector2D(x: left.x + right.x, y: left.y - right.y)
            }
            let firstVector = Vector2D(x: 1.0, y: 2.0)
            let secondVector = Vector2D(x: 3.0, y: 4.0)
            let plusMinusVector = firstVector +- secondVector
            // plusMinusVector 此時的值為 (4.0, -2.0)

訪問控制

  • 從高到低的權限控制順序:;open > public > interal > fileprivate > private
  • private: 所修飾的屬性、方法只能在當前類里訪問。
  • fileprivate: 所修飾的屬性、方法在當前的Swift源文件里可以訪問。
  • internal: 默認,所修飾的屬性、方法在源代碼所在的整個模塊都可以訪問。
  • public: 可以被任何人訪問,但其他模塊中不可以被override和繼承,本模塊中可以。
  • open: 可以被任何人使用,包括override和繼承.
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young閱讀 3,881評論 1 10
  • 132.轉換錯誤成可選值 通過轉換錯誤成一個可選值,你可以使用 try? 來處理錯誤。當執行try?表達式時,如果...
    無灃閱讀 1,282評論 0 3
  • 136.泛型 泛型代碼讓你可以寫出靈活,可重用的函數和類型,它們可以使用任何類型,受你定義的需求的約束。你可以寫出...
    無灃閱讀 1,513評論 0 4
  • 126.析構器 在一個類實例銷毀前,一個析構器會立即調用。使用deinit 關鍵字來表示析構器, 跟構造器寫法類似...
    無灃閱讀 845評論 0 4
  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile麗語閱讀 3,858評論 0 6