Swift基础语法-5

162 阅读13分钟

十九、扩展( Extension )

1、定义

  • Swift中的扩展,有点类似于OC中的分类( Category )
  • 扩展可以为枚举、结构体、类、协议添加新功能
    • 可以添加方法、计算属性、下标、( 便捷)初始化器、嵌套类型、协议等等
    • 扩展不能办到的事情
      • 不能覆盖原有的功能
      • 不能添加存储属性,不能向已有的属性添加属性观察器
      • 不能添加父类
      • 不能添加指定初始化器,不能添加反初始化器

2、语法

使用 extension 关键字声明扩展:

extension SomeType {
  // 在这里给 SomeType 添加新的功能
}

二十、协议

1、定义

  • 协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)
  • 协议中定义方法时不能有默认参数值
  • 默认情况下,协议中定义的内容必须全部都实现

2、协议中的属性

  • 协议中定义属性时必须用var关键字
  • 实现协议时的属性权限要不小于协议中定义的属性权限
    • 协议定义get、set ,用var存储属性或get、set计算属性去实现

    • 协议定义get ,用任何属性都可以实现

      protocol Drawable {
          func drwa()
          var x :Int {set get}
          var y :Int { get}
          subscript(index:Int) -> Int{get set}
      }
      
      class Person: Drawable {
          var x: Int = 0
          var y: Int = 0
          func drwa() {
              print("------Person drwa-----")
          }   
          subscript(index: Int) -> Int {
              get { index}
              set {  }
          }   
      }
      class Jerson: Drawable {
          var y : Int { 0 }
          var x: Int {get{0} set{} }  
          func drwa() { print("------Jerson drwa-----") }
          subscript(index: Int) -> Int {
              get {   index }
              set {}
          }
      }
      

3、static、class

  • 为了保证通用,协议中必须用static定义类型方法、类型属性、类型下标

4、mutating

  • 只有将协议中的实例方法标记为mutating
    • 才允许结构体、枚举的具体实现修改自身内存
    • 类在实现方法时不用加mutating,枚举、结构体猜需要加mutating

5、init

  • 协议中还可以定义初始化器init

  • 如果从协议实现的初始化器,刚好是重写了父类的指定初始化器

  • 非final类实现时必须加上required

  • 那么这个初始化必须同时加required、override

    //第一种
    protocol Drawable {
        init(x:Int,y:Int)
    }
    class Point: Drawable {
        required init(x: Int, y: Int) {}
    }
    final class Size :Drawable{
        init(x: Int, y: Int) {}
    }
    //第二种
    protocol Drawable {
        init(age: Int)
    }
    class Point{
        init(age: Int) { }
    }
    
    class Stu: Point ,Drawable {
        required override init(age: Int) {
            super.init(age: age)
        }
    }
    

6 init、init?、 init!

  • 协议中定义的init?、init! ,可以用init、 init?、 init !去实现
  • 协议中定义的init ,可以用init、init !去实现

7、协议的继承

  • 一个协议可以继承其他协议

8、Self

  • Self代表当前类型

  • Self一般用作返回值类型,限定返回值跟方法调用者必须是同一类型(也可以作为参数类型)

    class Person {
        var age = 1
        static var count = 2
        func run() {
            print(self.age)
            print(Self.count)
        }
    }
    

二十一、泛型

1、泛型作用

泛型可以将类型参数化,提高代码复用率,减少代码量

func swapValues<T>(_ a: inout T ,_ b:inout T) {
    (a,b) = (b,a)
    print(a,b)
}
var a = 10
var b = 20
swapValues(&a, &b)//20 10

2、关联类型(associatedtype)

  • 关联类型的作用:给协议中用到的类型定义一个占位名称
  • 协议中可以拥有多个关联类型

二十二、不透明类型

1、定义

具有不透明返回类型的函数或方法会隐藏返回值的类型信息

protocol Shape {
    func draw() -> String
}

struct Triangle: Shape {
    var size: Int
    func draw() -> String {
        var result = [String]()
        for length in 1...size {
            result.append(String(repeating: "*", count: length))
        }
        return result.joined(separator: "\n")

    }
}
let smallTriangle = Triangle(size: 3)
print(smallTriangle.draw())
// *
// **
// ***

struct FlippedShape<T: Shape>: Shape {
    var shape: T
    func draw() -> String {
        let lines = shape.draw().split(separator: "\n")
        return lines.reversed().joined(separator: "\n")
    }
}
let flippedTriangle = FlippedShape(shape: smallTriangle)
print(flippedTriangle.draw())
// ***
// **
// *

struct JoinedShape<T: Shape, U: Shape>: Shape {
    var top: T
    var bottom: U
    func draw() -> String {
        return top.draw() + "\n" + bottom.draw()
    }
}
let joinedTriangles = JoinedShape(top: smallTriangle, bottom: flippedTriangle)
print(joinedTriangles.draw())
// *
// **
// ***
// ***
// **
// *

2、使用some关键字声明一个不透明类型

func flip<T: Shape>(_ shape: T) -> some Shape {
    return FlippedShape(shape: shape)
}
func join<T: Shape, U: Shape>(_ top: T, _ bottom: U) -> some Shape {
    JoinedShape(top: top, bottom: bottom)
}

let opaqueJoinedTriangles = join(smallTriangle, flip(smallTriangle))
print(opaqueJoinedTriangles.draw())
// *
// **
// ***
// ***
// **
// *

3、some

some除了用在返回值类型上, 一般还可以用在属性类型上

protocol Runnable { associatedtype Speed }
class Dog : Runnable { typealias Speed = Double }
class Person {
    var pet: some Runnable {
        return Dog()
    }
}	

二十三、自动引用计数

1、自动引用的工作机制

  • 当你创建一个新的类实例时,ARC 会分配一块内存来储存该实例的信息。内存中会包含实例的类型信息,以及这个实例所关联的任何存储属性的值。
  • 当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。

2、类实例之间的循环强引用

  • 当我们可能会写出一个类实例的强引用数永远不能变成 0 的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的循环强引用。
  • 在生命周期中可能会变为nil的使用weak
  • 初始化赋值后再也不会变为nil的使用unowned

3、解决实例之间的循环强引用

  • 弱引用(weak reference):通过"weak"定义弱引用

    • 必须是可选类型的var ,因为实例销毁后, ARC会自动将弱引|用设置为nil
    • ARC自动给弱引用设置nil时,不会触发属性观察器
  • 无主引用(unowned reference):无主引用( unowned reference ) : 通过"unowned"定义无主引用

    • 不会产生强引用,实例销毁后仍然存储着实例的内存地址(类似于OC中的unsafe_ unretained )
    • 试图在实例销毁后访问无主引用,会产生运行时错误(野指针)
  • weak、unowned的使用限制

    • 只能用在类实例上面

4、闭包的循环强引用

  • 闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行了retain操作)
  • 在闭包表达式的捕获列表声明weak或unowned引用,解决循环引用问题

5、解决闭包的循环的强引用

class Person {
    var fn:(()->())?
    func run() {
        print("run")
    }
    deinit {
        print("deinit")
    }
}
func test() {
    let p = Person()
//    p.fn = {
//        [weak p] in
//        p?.run()
//    }
    p.fn = {
        [unowned p] in
        p.run()
    }
}
test() //deinit
  • 如果想在定义闭包属性的同时引用self ,这个闭包必须是lazy的(因为在实例初始化完毕之后才能引用self )

    class Person {
        lazy var fn:(()->()) = {
            [weak self] in
            self?.run()
        }
        func run() {
            print("run")
        }
        deinit {
            print("deinit")
        }  
    }
    
  • 如果lazy属性是闭包调用的结果,那么不用考虑循环引用的问题(因为闭包调用后,闭包的生命周期就结束了)

二十四、内存安全

1、内存访问冲突

内存访问冲突会在两个访问满足下列条件时发生:

  • 至少一个是写入操作
  • 它们访问的是同一块内存
  • 它们的访问时间重叠(比如在同一个函数内)

如果下面的条件可以满足,就说明重叠访问结构体的属性是安全的

  • 你只访问实例存储属性,不是计算属性或者类属性
  • 结构体是局部变量而非全局变量
  • 结构体要么没有被闭包捕获要么只被非逃逸闭包捕获

二十五、访问控制

1、访问级别

  • open :允许在定义实体的模块、其他模块中访问,允许其他模块进行继承、重写( open只能用在类、类成员上)
  • public :允许在定义实体的模块、其他模块中访问,不允许其他模块进行继承、重写
  • internal :只允许在定义实体的模块中访问,不允许在其他模块中访问
  • fileprivate :只允许在定义实体的源文件中访问
  • private :只允许在定义实体的封闭声明中访问
    • 绝大部分实体默认都是internal级别

2、访问级别的使用准则

  • 一个实体不可以被更低访问级别的实体定义,比如
    • 变量\常量类型≥变量\常量
    • 参数类型、返回值类型≥函数
    • 父类≥子类
    • 父协议≥子协议
    • 原始值类型、关联值类型≥枚举类型
    • 定义类型A时用到的其他类型≥类型A

3、元组类型

  • 元组类型的访问级别是所有成员类型最低的那个

4、泛型类型

  • 泛型类型的访问级别是类型的访问级别以及所有泛型类型参数的访问级别中最低的那个

5、成员、嵌套类型

  • 类型的访问级别会影响成员(属性、方法、初始化器、下标) 、嵌套类型的默认访问级别
    • 一般情况下,类型为private或fileprivate ,那么成员\嵌套类型默认也是private或fileprivate
    • 一般情况下,类型为internal或public ,那么成员\嵌套类型默认是internal

6、成员的重写

  • 子类重写成员的访问级别必须≥子类的访问级别,或者≥父类被重写成员的访问级别
  • 父类的成员不能被成员作用域外定义的子类重写

7、getter、setter

  • getter、setter默认自动接收它们所属环境的访问级别
  • 可以给setter单独设置一个比getter更低的访问级别,用以限制写的权限

8、初始化器

  • 如果一个public类想在另-一个模块调用编译生成的默认无参初始化器,必须显式提供public的无参初始化器
    • 因为public类的默认初始化器是internal级别
    • required初始化器≥它的默认访问级别
  • 如果结构体有private\fileprivate的存储实例属性,那么它的成员初始化器也是private\fileprivate
    • 否则默认就是internal

9、枚举类型的case

  • 不能给enum的每个case单独设置访问级别
  • 每个case自动接收enum的访问级别
    • public enum定义的case也是public

10、协议

  • 协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别
    • public协议定义的要求也是public
  • 协议实现的访问级别必须≥类型的访问级别,或者≥协议的访问级别

11、扩展

  • 如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别
  • 如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样
  • 可以单独给扩展添加的成员设置访问级别
  • 不能给用于遵守协议的扩展显式设置扩展的访问级别
  • 在同一文件中的扩展,可以写成类似多个部分的类型声明
    • 在原本的声明中声明一个私有成员,可以在同一文件的扩展中访问它
    • 在扩展中声明一一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它

二十六、高级运算符

1、位运算符

位运算符可以操作数据结构中每个独立的比特位

a.Bitwise NOT Operator(按位取反运算符)

按位取反运算符(~)对一个数值的全部比特位(byte)进行取反

let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // 等于 0b11110000	

b.Bitwise AND Operator(按位与运算符)

按位与运算符(&) 对两个数的比特位进行合并。它返回一个新的数,只有当两个数的对应位都为 1 的时候,新数的对应位才为 1

let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8  = 0b00111111
let middleFourBits = firstSixBits & lastSixBits // 等于 00111100
c.Bitwise OR Operator(按位或运算符)

按位或运算符(|)可以对两个数的比特位进行比较。它返回一个新的数,只要两个数的对应位中有任意一个为 1 时,新数的对应位就为 1

let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits // 等于 11111110

d.Bitwise XOR Operator(按位异或运算符)

按位异或运算符,或称“排外的或运算符”(^),可以对两个数的比特位进行比较。它返回一个新的数,当两个数的对应位不相同时,新数的对应位就为 1,并且对应位相同时则为 0

let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits // 等于 00010001

e.Bitwise Left and Right Shift Operators(按位左移、右移运算符)

按位左移运算符(<<) 和 按位右移运算符(>>)可以对一个数的所有位进行指定位数的左移和右移,但是需要遵守下面定义的规则。

对一个数进行按位左移或按位右移,相当于对这个数进行乘以 2 或除以 2 的运算。将一个整数左移一位,等价于将这个数乘以 2,同样地,将一个整数右移一位,等价于将这个数除以 2。

1.无符号整数的移位运算

对无符号整数进行移位的规则如下:

  1. 已存在的位按指定的位数进行左移和右移。
  2. 任何因移动而超出整型存储范围的位都会被丢弃。
  3. 用 0 来填充移位后产生的空白位。

这种方法称为逻辑移位。

2.符号

符号位为 0 代表正数,为 1 代表负数。

//负数的存储方式略有不同。它存储 2 的 n 次方减去其实际值的绝对值,这里的 n 是数值位的位数。

1 1111100 ----->-4 = 124-128 = 2x2x2x2x2x2x2-4-128
1 1111111 ----->-1 = 127-128
1 1111011 ----->-5 = 127-4-128 

最大值~最小值
127~-128
01111111~10000000

2、溢出运算符

  • 当向一个整数类型的常量或者变量赋予超过它容量的值时,Swift 默认会报错,而不是允许生成一个无效的数。这个行为为我们在运算过大或者过小的数时提供了额外的安全性

  • Swift有溢出运算符( &+、&-、&*) ,用来支持溢出运算 var min = UInt8.min print(min &- 1) //255

    var max = uint8.max
    print(max &+ 1) //0
    print(max &* 2) // 254
    

3、优先级和结合性

4、运算符函数

  • prefix operator前缀运算符
  • postfix operator后缀运算符
  • infix operator中缀运算符:优先级组
  • precedencegroup优先级组 associativity:结合性(left\right\none)
    higherThan:比谁的优先级高
    lowerThan:比谁的优 先级低
    assignment:true代表在可选链操作中拥有跟赋值运算符一样的优先级 }

5、自定义运算符

新的运算符要使用 operator 关键字在全局作用域内进行定义,同时还要指定 prefix、infix 或者 postfix 修饰符

extension Vector2D {
    static prefix func +++ (vector: inout Vector2D) -> Vector2D {
        vector += vector
        return vector
    }
}

var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
let afterDoubling = +++toBeDoubled
// toBeDoubled 现在的值为 (2.0, 8.0)
// afterDoubling 现在的值也为 (2.0, 8.0)