巩固—Swift 4.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(_ a: inout T, _ b: inout T) {
          (a,b) = (b,a)
      }
    
  • 扩展一个泛型类型:不需要在扩展汇总提供类型参数列表,原始类型定义的类型参数可以在扩展中直接使用。

  • 类型约束:可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。

    • 类型约束语法:

            func someFunction(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 (_ 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: 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和继承。

你可能感兴趣的:(巩固—Swift 4.0 基础知识(三))