05-📝Swift5常用核心语法|高级语法【可选链、协议、错误处理、泛型、String与Array、高级运算符、扩展、访问控制、内存管理、字面量、模式匹配】

1,997 阅读55分钟

一、概述

本系列文章旨在复习Swift5核心语法且适当进行底层原理探索,属于阶段性复习和巩固,以供日后进一步探索Swift语言的底层原理做铺垫。

整个系列文章如下,每一文章知识点独立成篇,欢迎各位按需或按兴趣点击阅读:

二、可选链(Optional Chaining)

看下面的示例代码:

class Person {
    var name: String = ""
    var dog: Dog = Dog()
    var car: Car? = Car()
    
    func age() -> Int { 18 }
    
    func eat() {
        print("Person eat")
    }
    
    subscript(index: Int) -> Int { index }
} 
    1. 如果可选项为nil,调用方法、下标、属性失败,结果为nil
    var person: Person? = nil
    var age = person?.age()
    var name = person?.name
    var index = person?[6]
    
    print(age, name, index) // nil, nil, nil 
    
    // 如果person为nil,都不会调用getName
    func getName() -> String { "jack" }
    
    var person: Person? = nil
    person?.name = getName() 
    
    1. 如果可选项不为nil,调用方法下标属性成功,结果会被包装成可选项
    var person: Person? = Person()
    var age = person?.age()
    var name = person?.name
    var index = person?[6]
    
    print(age, name, index) // Optional(18) Optional("") Optional(6) 
    
    1. 如果结果本来就是可选项,不会进行再次包装
    print(person?.car) // Optional(test_enum.Car) 
    
    1. 可以用可选绑定来判断可选项的方法调用是否成功
    let result: ()? = person?.eat()
    if let _ = result {
        print("调用成功")
    } else {
        print("调用失败")
    } 
    
    if let age = person?.age() {
        print("调用成功", age)
    } else {
        print("调用失败")
    } 
    
    1. 没有设定返回值的方法默认返回的就是元组类型 -w521
    1. 多个?可以连接在一起,组成可选链
    var dog = person?.dog
    var weight = person?.dog.weight
    var price = person?.car?.price 
    
    1. 可选链中不管中间经历多少层,只要有一个节点是可选项的,那么最后的结果就是会被包装成可选项的
    print(dog, weight, price) // Optional(test_enum.Dog) Optional(0) Optional(0)
    
    1. 如果链中任何一个节点是nil,那么整个链就会调用失败
      看下面示例代码
    var num1: Int? = 5
    num1? = 10
    print(num1)
    
    var num2: Int? = nil
    num2? = 10
    print(num2) 
    
    1. 给变量加上?是为了判断变量是否为nil,如果为nil,那么就不会执行赋值操作了,本质也是可选链
    var dict: [String : (Int, Int) -> Int] = [
        "sum" : (+),
        "difference" : (-)
    ]
    
    var value = dict["sum"]?(10, 20)
    print(value) 
    

从字典中通过key来取值,得到的也是可选类型,由于可选链中有一个节点是可选项,那么最后的结果也是可选项,最后的值也是Int?

三、协议(Protocol)

1. 基本概念

    1. 协议可以用来定义方法属性下标的声明
      协议可以被结构体枚举遵守
    protocol Drawable {
        func draw()
        var x: Int { get set } // get和set只是声明
        var y: Int { get }
        subscript(index: Int) -> Int { get set }
    } 
    
    1. 多个协议之间用逗号隔开
    protocol Test1 { }
    protocol Test2 { }
    protocol Test3 { }
    
    class TestClass: Test1, Test2, Test3 { } 
    
    1. 协议中定义方法时不能有默认参数值 -w633
    1. 默认情况下,协议中定义的内容必须全部都实现

2. 协议中的属性

    1. 协议中定义属性必须用var关键字
    1. 实现协议时的属性权限要不小于协议中定义的属性权限
    • 协议定义get、set,用var存储属性或get、set计算属性去实现
    • 协议定义get,用任何属性都可以实现
    protocol Drawable {
        func draw()
        var x: Int { get set }
        var y: Int { get }
        subscript(index: Int) -> Int { get set }
    }
    
    class Person1: Drawable {
        var x: Int = 0
        let y: Int = 0
    
        func draw() {
            print("Person1 draw")
        }
    
        subscript(index: Int) -> Int {
            set { }
            get { index }
        }
    }
    
    class Person2: Drawable {
        var x: Int {
            get { 0 }
            set { }
        }
    
        var y: Int { 0 }
    
        func draw() {
            print("Person2 draw")
        }
    
        subscript(index: Int) -> Int {
            set { }
            get { index }
        }
    }
    
    class Person3: Drawable {
        var x: Int {
            get { 0 }
            set { }
        }
    
        var y: Int {
            get { 0 }
            set { }
        }
    
        func draw() {
            print("Person3 draw")
        }
    
        subscript(index: Int) -> Int {
            set { }
            get { index }
        }
    } 
    

3. static、class

    1. 为了保证通用,协议中必须用static定义类型方法类型属性类型下标
    protocol Drawable {
        static func draw()
    }
    
    class Person1: Drawable {
        static func draw() {
            print("Person1 draw")
        }
    }
    
    class Person2: Drawable {
        class func draw() {
            print("Person2 draw")
        }
    } 
    
    • class声明的类中,用 static修饰 方法 和 用 class修饰方法 是等价的(都是标记为类型方法)

4. mutating

    1. 只有将协议中的实例方法标记为mutating,才允许结构体枚举的具体实现修改自身内存
    • 结构体枚举 属于值类型,从class属于引用类型
    • 只有声明值类型为变量(用var 修饰,而非let),且其的属性也是一个变量,才允许修改值类型的属性值
      struct Person {
          static var a: Int = 10
          var age: Int = 0
          mutating static func agePlusOne() {
              a += 1
          }
      }
      
      // let p = Person()//声明为常量,属性赋值会报错
      var p = Person()
      p.age = 10
      print(p.age)
      Person.agePlusOne()
      print(p.age)
      p.age = 20
      print(p.age)
      
    • 而声明引用类型为常量(用let 修饰),其的属性为变量,也允许修改其属性值.例如:
      class Person {
          static var a: Int = 10
          var age: Int = 0
          class func agePlusOne() {
              a += 1
          }
      }
      
      let p = Person()
      p.age = 10
      print(p.age)
      Person.agePlusOne()
      print(p.age)
      p.age = 20
      print(p.age)
      
    1. 在实现方法时不用加mutating结构体枚举才需要加mutating
    protocol Drawable {
        mutating func draw()
    }
    
    class Size: Drawable {
        var width: Int = 0
    
        func draw() {
            width = 10
        }
    }
    
    struct Point: Drawable {
        var x: Int = 0
        mutating func draw() {
            x = 10
        }
    } 
    

5. init

    1. 协议中还可以定义初始化器init,非final类实现时必须加上required
    1. 目的是为了让所有遵守这个协议的类都拥有初始化器,所以加上required强制子类必须实现,除非是加上final不需要子类的类
    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) {
    
        }
    } 
    
    1. 如果从协议实现的初始化器,刚好是重写了父类的指定初始化器,那么这个初始化必须同时加上required、override
    protocol Livable {
        init(age: Int)
    }
    
    class Person {
        init(age: Int) { }
    }
    
    class Student: Person, Livable {
        required override init(age: Int) {
            super.init(age: age)
        }
    } 
    
    1. 协议中定义的init?、init!,可以用init、init?、init!去实现
    protocol Livable {
        init()
        init?(age: Int)
        init!(no: Int)
    }
    
    class Person1: Livable {
        required init() {
    
        }
    
        required init?(age: Int) {
    
        }
    
        required init!(no: Int) {
    
        }
    }
    
    class Person2: Livable {
        required init() {
    
        }
    
        required init!(age: Int) {
    
        }
    
        required init?(no: Int) {
    
        }
    }
    
    class Person3: Livable {
        required init() {
    
        }
    
        required init(age: Int) {
    
        }
    
        required init(no: Int) {
    
        }
    } 
    
    1. 协议中定义的init,可以用init、init!去实现
    protocol Livable {
        init()
        init?(age: Int)
        init!(no: Int)
    }
    
    class Person4: Livable {
        required init!() {
    
        }
    
        required init?(age: Int) {
    
        }
    
        required init!(no: Int) {
    
        }
    }  
    

6. 协议的继承

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

protocol Runnable {
    func run()
}

protocol Livable: Runnable {
    func breath()
}

class Person: Livable {
    func breath() {

    }

    func run() {

    }
} 

7. 协议组合

协议组合可以包含一个类类型

protocol Runnable { }
protocol Livable { }
class Person { }

// 接收Person或者其子类的实例
func fn0(obj: Person) { }

// 接收遵守Livable协议的实例
func fn1(obj: Livable) { }

// 接收同时遵守Livable、Runnable协议的实例
func fn2(obj: Livable & Runnable) { }

// 接收同时遵守Livable、Runnable协议,并且是Person或者其子类的实例
func fn3(obj: Person & Livable & Runnable) { }

typealias RealPerson = Person & Livable & Runnable
func fn4(obj: RealPerson) { } 

8. CaseIterable

让枚举遵守CaseIterable协议,可以实现遍历枚举值

enum Season: CaseIterable {
    case spring, summer, autumn, winter
}

let seasons = Season.allCases
print(seasons.count)

for season in seasons {
    print(season)
} // spring, summer, autumn, winter 

9.CustomStringConvertible

    1. 遵守CustomStringConvertible、CustomDebugStringConvertible协议,都可以自定义实例的打印字符串
    class Person: CustomStringConvertible, CustomDebugStringConvertible {
        var age = 0
        var description: String { "person_(age)" }
        var debugDescription: String { "debug_person_(age)" }
    }
    
    var person = Person()
    print(person) // person_0
    debugPrint(person) // debug_person_0 
    
    1. print调用的是CustomStringConvertible协议的description
    1. debugPrint、po调用的是CustomDebugStringConvertible协议的debugDescription

-w529

四、Any、AnyObject与元类型

1. Any、AnyObject

    1. Swift提供了两种特殊的类型Any、AnyObject
    1. Any可以代表任意类型(枚举结构体,也包括函数类型
    var stu: Any = 10
    stu = "Jack"
    stu = Size() 
    
    var data = [Any]()
    data.append(1)
    data.append(3.14)
    data.append(Size())
    data.append("Jack")
    data.append({ 10 }) 
    
    1. AnyObject可以代表任意类类型
    1. 在协议后面写上: AnyObject,代表只有类能遵守这个协议 -w644
    1. 在协议后面写上: class,也代表只有类能遵守这个协议 -w642

2. is、as

  • is用来判断是否为某种类型
    protocol Runnable {
        func run()
    }
    
    class Person { }
    
    class Student: Person, Runnable {
        func run() {
            print("Student run")
        }
    
        func study() {
            print("Student study")
        }
    }
    
    var stu: Any = 10
    print(stu is Int) // true
    
    stu = "Jack"
    print(stu is String) // true
    
    stu = Student()
    print(stu is Person) // true
    print(stu is Student) // true
    print(stu is Runnable) // true 
    
    1. as用来做强制类型转换(as?as!as)
    protocol Runnable {
        func run()
    }
    
    class Person { }
    
    class Student: Person, Runnable {
        func run() {
            print("Student run")
        }
    
        func study() {
            print("Student study")
        }
    }
    
    var stu: Any = 10
    (stu as? Student)?.study() // 没有调用study
    
    stu = Student()
    (stu as? Student)?.study() // Student study
    (stu as! Student).study() // Student study
    (stu as? Runnable)?.run() // Student run 
    
    var data = [Any]()
    data.append(Int("123") as Any)
    
    var d = 10 as Double
    print(d) // 10.0 
    

3. X.self

    1. X.self是一个元类型的指针metadata存放着类型相关信息
    1. X.self属于X.Type类型
    class Person { }
    
    class Student: Person { }
    
    var perType: Person.Type = Person.self
    var stuType: Student.Type = Student.self
    perType = Student.self
    
    var anyType: AnyObject.Type = Person.self
    anyType = Student.self
    
    var per = Person()
    perType = type(of: per)
    print(Person.self == type(of: per)) // true 
    
    1. AnyClass的本质就是AnyObject.Type -w492
    var anyType2: AnyClass = Person.self
    anyType2 = Student.self 
    

4. 元类型的应用

class Animal {
    required init() {
        
    }
}

class Cat: Animal {
    
}

class Dog: Animal {
    
}

class Pig: Animal {
    
}

func create(_ clses: [Animal.Type]) -> [Animal] {
    var arr = [Animal]()
    for cls in clses {
        arr.append(cls.init())
    }
    
    return arr
}

print(create([Cat.self, Dog.self, Pig.self]))

// a1、a2、a3、a4的写法等价
var a1 = Animal()
var t = Animal.self
var a2 = t.init()
var a3 = Animal.self.init()
var a4 = Animal.self() 

5. Self

    1. Self代表当前类型
    class Person {
        var age = 1
        static var count = 2
    
        func run() {
            print(self.age)
            print(Self.count)
        }
    } 
    
    1. Self一般用作返回值类型,限定返回值和方法调用者必须是同一类型(也可以作为参数类型)
    protocol Runnable {
        func test() -> Self
    }
    
    class Person: Runnable {
    
        required init() {
    
        }
    
        func test() -> Self {
            type(of: self).init()
        }
    }
    
    class Student: Person {
    
    }
    
    var p = Person()
    print(p.test()) // test_enum.Person
    
    var stu = Student()
    print(stu.test()) // test_enum.Student 
    

6. 元类型的本质

我们可以通过反汇编来查看元类型的实现是怎样的

var p = Person()
var pType = Person.self 

我们发现最后存储到全局变量pType中的地址值就是一开始调用的地址 -w1031 再通过打印,我们发现pType的值就是Person实例对象的前8个字节的地址值,也就是类信息 -w1031 -w1032 我们再来看下面的示例代码

var p = Person()
var pType = type(of: p) 

通过分析我们可以看到type(of: p)本质不是函数调用,只是将Person实例对象的前8个字节存储到pType中,也证明了元类型的本质就是存储的类信息

-w1031 -w1030

我们还可以用以下方式来获取Swift的隐藏基类_TtCs12_SwiftObject

class Person {
    var age: Int = 0
}

class Student: Person {
    var no: Int = 0
}

print(class_getInstanceSize(Student.self)) // 32
print(class_getSuperclass(Student.self)!) // Person
print(class_getSuperclass(Student.self)!) // _TtCs12_SwiftObject
print(class_getSuperclass(NSObject.self)) // nil 

我们可以查看Swift源码来分析该类型

发现SwiftObject里面也有一个isa指针

-w686

五、错误处理

1. 错误处理

1.1 错误类型

开发过程中常见的错误有

  • 语法错误(编译报错)
  • 逻辑错误
  • 运行时错误(可能会导致闪退,一般也叫做异常)
  • ....

1.2 自定义错误

    1. Swift中可以通过Error协议自定义运行时的错误信息
enum SomeError: Error {
    case illegalArg(String)
    case outOffBounds(Int, Int)
    case outOfMemory
} 
    1. 函数内部通过throw抛出自定义Error,可能会抛出Error的函数必须加上throws声明
func divide(_ num1: Int, _ num2: Int) throws -> Int {
    if num2 == 0 {
        throw SomeError.illegalArg("0不能作为除数")
    }
    return num1 / num2
} 
    1. 需要使用try调用可能会抛出Error的函数
var result = try divide(20, 10)
    1. 抛出错误信息的情况 -w715

1.3 do—catch

    1. 可以使用do—catch捕捉Error
do {
    try divide(20, 0)
} catch let error {
    switch error {
    case let SomeError.illegalArg(msg):
        print("参数错误", msg)
    default:
        print("其他错误")
    }
} 
    1. 抛出Error后,try下一句直到作用域结束的代码都将停止运行
func test() {
    print("1")

    do {
        print("2")
        print(try divide(20, 0)) // 这句抛出异常后面的代码不会执行了
        print("3")
    } catch let SomeError.illegalArg(msg) {
        print("参数异常:", msg)
    } catch let SomeError.outOffBounds(size, index) {
        print("下标越界:", "size=(size)", "index=(index)")
    } catch SomeError.outOfMemory {
        print("内存溢出")
    } catch {
        print("其他错误")
    }

    print("4")
}

test()

//1
//2
//参数异常: 0不能作为除数
//4 
    1. catch作用域内默认就有error的变量可以捕获
do {
    try divide(20, 0)
} catch {
    print(error)
} 

2. 处理Error

    1. 处理Error的两种方式:
  • a. 通过do—catch捕捉Error
    do {
        print(try divide(20, 0))
    } catch is SomeError {
        print("SomeError")
    } 
    
  • b. 不捕捉Error,在当前函数增加throws声明,Error将自动抛给上层函数
    如果最顶层函数main函数依然没有捕捉Error,那么程序将终止
    func test() throws {
        print("1")
        print(try divide(20, 0))
        print("2")
    }
    
    try test()
    
    // 1
    // Fatal error: Error raised at top level 
    
    1. 调用函数如果是写在函数里面,没有进行捕捉Error就会报错,而写在外面就不会 -w648
    1. 然后我们加上do-catch发现还是会报错,因为捕捉Error的处理不够详细,要捕捉所有Error信息才可以 -w639
  • 这时我们加上throws就可以了
    func test() throws {
        print("1")
        do {
            print("2")
            print(try divide(20, 0))
            print("3")
        } catch let error as SomeError {
            print(error)
        }
    
        print("4")
    }
    
    try test()
    
    // 1
    // 2
    // illegalArg("0不能作为除数")
    // 4 
    
  • 或者再加上一个catch捕获其他所有Error情况
    func test() {
        print("1")
        do {
            print("2")
            print(try divide(20, 0))
            print("3")
        } catch let error as SomeError {
            print(error)
        } catch {
            print("其他错误情况")
        }
    
        print("4")
    }
    
    test() 
    
  • 看下面示例代码,执行后会输出什么
    func test0() throws {
        print("1")
        try test1()
        print("2")
    }
    
    func test1() throws {
        print("3")
        try test2()
        print("4")
    }
    
    func test2() throws {
        print("5")
        try test3()
        print("6")
    }
    
    func test3() throws {
        print("7")
        try divide(20, 0)
        print("8")
    }
    
    try test0() 
    
  • 执行后打印如下,并会抛出错误信息 -w717

3. try

    1. 可以使用try?、try!调用可能会抛出Error的函数,这样就不用去处理Error
func test() {
    print("1")

    var result1 = try? divide(20, 10) // Optional(2), Int?
    var result2 = try? divide(20, 0) // nil
    var result3 = try! divide(20, 10) // 2, Int

    print("2")
}

test() 
    1. a、b是等价的
var a = try? divide(20, 0)
var b: Int?

do {
     b = try divide(20, 0)
} catch { b = nil }

4. rethrows

  • rethrows表明,函数本身不会抛出错误,但调用闭包参数抛出错误,那么它会将错误向上抛
func exec(_ fn: (Int, Int) throws -> Int, _ num1: Int, _ num2: Int) rethrows {
print(try fn(num1, num2))
}

// Fatal error: Error raised at top level
try exec(divide, 20, 0)
  • 空合并运算符就是用了rethrows来进行声明的 -w609

5. defer

    1. defer语句,用来定义以任何方式(抛错误、return等)离开代码块前必须要执行的代码
    1. defer语句将延迟至当前作用域结束之前执行
func open(_ filename: String) -> Int {
print("open")
return 0
}

func close(_ file: Int) {
print("close")
}

func processFile(_ filename: String) throws {
let file = open(filename)
defer {
    close(file)
}

// 使用file
// .....
try divide(20, 0)

// close将会在这里调用
}

try processFile("test.txt")

// open
// close
// Fatal error: Error raised at top level
    1. defer语句的执行顺序与定义顺序相反
func fn1() { print("fn1") }
func fn2() { print("fn2") }
func test() {
defer { fn1() }
defer { fn2() }
}

test()

// fn2
// fn1 

6. assert(断言)

  • 很多编程语言都有断言机制,不符合指定条件就抛出运行时错误,常用于调试Debug阶段的条件判断 -w716
  • 默认情况下,Swift的断言只会在Debug模式下生效,Release模式下会忽略
  • 增加Swift Flags修改断言的默认行为
  • -assert-config Release:强制关闭断言
  • -assert-config Debug:强制开启断言 -w716

7. fatalError

    1. 如果遇到严重问题,希望结束程序运行时,可以直接使用fatalError函数抛出错误
    1. 这是无法通过do—catch捕获的错误
    1. 使用了fatalError函数,就不需要再写return
func test(_ num: Int) -> Int {
    if num >= 0 {
        return 1
    }
    fatalError("num不能小于0")
} 
    1. 在某些不得不实现,但不希望别人调用的方法,可以考虑内部使用fatalError函数
class Person { required init() {} }
class Student: Person {
    required init() {
        fatalError("don't call Student.init")
    }

    init(score: Int) {

    }
}

var stu1 = Student(score: 98)
var stu2 = Student()

8. 局部作用域

    1. 可以使用do实现局部作用域
do {
    let dog1 = Dog()
    dog1.age = 10
    dog1.run()
}

do {
    let dog2 = Dog()
    dog2.age = 10
    dog2.run()
} 

六、泛型(Generics)

1. 基本概念

  • 1.1 泛型可以将类型参数化
    提高代码复用率,减少代码量
func swapValues<T>(_ a: inout T, _ b: inout T) {
    (a, b) = (b, a)
}

var i1 = 10
var i2 = 20
swap(&i1, &i2)
print(i1, i2) // 20, 10

struct Date {
    var year = 0, month = 0, day = 0
}

var d1 = Date(year: 2011, month: 9, day: 10)
var d2 = Date(year: 2012, month: 10, day: 20)
swap(&d1, &d2)
print(d1, d2) // Date(year: 2012, month: 10, day: 20), Date(year: 2011, month: 9, day: 10) 
  • 1.2 泛型函数赋值给变量
func test<T1, T2>(_ t1: T1, _ t2: T2) {}
var fn: (Int, Double) -> () = test

2. 泛型类型

Case1

class Stack<E> {
var elements = [E]()

func push(_ element: E) {
    elements.append(element)
}

func pop() -> E {
    elements.removeLast()
}

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

func size() -> Int {
    elements.count
}
}

class SubStack<E>: Stack<E> {

}

var intStack = Stack<Int>()
var stringStack = Stack<String>()
var anyStack = Stack<Any>()

Case1 继续完善

struct Stack<E> {
var elements = [E]()

mutating func push(_ element: E) {
    elements.append(element)
}

mutating func pop() -> E {
    elements.removeLast()
}

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

func size() -> Int {
    elements.count
}
}

var stack = Stack<Int>()
stack.push(11)
stack.push(22)
stack.push(33)
print(stack.top()) // 33
print(stack.pop()) // 33
print(stack.pop()) // 22
print(stack.pop()) // 11
print(stack.size()) // 0

Case2

enum Score<T> {
case point(T)
case grade(String)
}

let score0 = Score<Int>.point(100)
let score1 = Score.point(99)
let score2 = Score.point(99.5)
let score3 = Score<Int>.grade("A")

3. 关联类型(Associated Type)

    1. 关联类型的作用: 给协议中用到的类型定义一个占位名称
protocol Stackable {
    associatedtype Element
    mutating func push(_ element: Element)
    mutating func pop() -> Element
    func top() -> Element
    func size() -> Int
}

struct Stack<E>: Stackable {
    var elements = [E]()

    mutating func push(_ element: E) {
        elements.append(element)
    }

    mutating func pop() -> E {
        elements.removeLast()
    }

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

    func size() -> Int {
        elements.count
    }
}

class StringStack: Stackable {
    var elements = [String]()

    func push(_ element: String) {
        elements.append(element)
    }

    func pop() -> String {
        elements.removeLast()
    }

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

    func size() -> Int {
        elements.count
    }
}

var ss = StringStack()
ss.push("Jack")
ss.push("Rose") 
    1. 协议中可以拥有多个关联类型
protocol Stackable {
    associatedtype Element
    associatedtype Element2
    mutating func push(_ element: Element)
    mutating func pop() -> Element
    func top() -> Element
    func size() -> Int
} 

4. 类型约束

protocol Runnable { }
class Person { }

func swapValues<T: Person & Runnable>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
} 
protocol Stackable {
associatedtype Element: Equatable
}

class Stack<E: Equatable>: Stackable {
typealias Element = E
}

func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element : Hashable {
 return false
}

var stack1 = Stack<Int>()
var stack2 = Stack<String>()
equal(stack1, stack2) 

5. 协议类型的注意点

看下面的示例代码来分析

protocol Runnable { }
class Person: Runnable { }
class Car: Runnable { }

func get(_ type: Int) -> Runnable {
if type == 0 {
    return Person()
}

return Car()
}

var r1 = get(0)
var r2 = get(1) 
  • 如果协议中有associatedtype
protocol Runnable {
associatedtype Speed
var speed: Speed { get }
}

class Person: Runnable {
var speed: Double { 0.0 }
}

class Car: Runnable {
var speed: Int { 0 }
} 
  • 这样写会报错,因为无法在编译阶段知道Speed的真实类型是什么 -w638
  • 可以用泛型来解决
protocol Runnable {
    associatedtype Speed
    var speed: Speed { get }
}

class Person: Runnable {
    var speed: Double { 0.0 }
}

class Car: Runnable {
    var speed: Int { 0 }
}

func get<T: Runnable>(_ type: Int) -> T {
    if type == 0 {
        return Person() as! T
    }

    return Car() as! T
}

var r1: Person = get(0)
var r2: Car = get(1) 
  • 还可以使用some关键字声明一个不透明类型
  • some限制只能返回一种类型
protocol Runnable {
    associatedtype Speed
    var speed: Speed { get }
}

class Person: Runnable {
    var speed: Double { 0.0 }
}

class Car: Runnable {
    var speed: Int { 0 }
}

func get(_ type: Int) -> some Runnable {
    return Car()
}

var r1 = get(0)
var r2 = get(1) 
  • some除了用在返回值类型上,一般还可以用在属性类型上
protocol Runnable {
    associatedtype Speed
}

class Dog: Runnable {
    typealias Speed = Double
}

class Person {
    var pet: some Runnable {
        return Dog()
    }
} 

6. 泛型的本质

  • 我们通过下面的示例代码来分析其内部具体是怎样实现的
func swapValues<T>(_ a: inout T, _ b: inout T) {
    (a, b) = (b, a)
}

var i1 = 10
var i2 = 20
swap(&i1, &i2)
print(i1, i2) // 20, 10

var d1 = 10.0
var d2 = 20.0
swap(&d1, &d2)
print(d1, d2) // 20.0, 10.0 
  • 反汇编之后 -w1000 -w1002
  • 从调用两个交换方法来看,最终调用的都是同一个函数,因为函数地址是一样的;
  • 但不同的是分别会将Int的metadataDouble的metadata作为参数传递进去
  • 所以推测metadata中应该分别指明对应的类型来做处理

7. 可选项的本质

    1. 可选项的本质的本质是enum类型
    1. 我们可以进到头文件中查看 -w1034
  • 我们平时写的语法糖的真正写法如下:
var age: Int? = 10

本质写法如下:
var ageOpt0: Optional<Int> = Optional<Int>.some(10)
var ageOpt1: Optional = .some(10)
var ageOpt2 = Optional.some(10)
var ageOpt3 = Optional(10) 
var age: Int? = nil

本质写法如下:
var ageOpt0: Optional<Int> = .none
var ageOpt1 = Optional<Int>.none 
var age: Int? = .none
age = 10
age = .some(20)
age = nil 
    1. switch中可选项的使用
switch age {
case let v?: // 加上?表示如果有值会解包赋值给v
    print("some", v)
case nil:
    print("none")
}

switch age {
case let .some(v):
    print("some", v)
case .none:
    print("none")
}
    1. 多重可选项
var age_: Int? = 10
var age: Int?? = age_
age = nil

var age0 = Optional.some(Optional.some(10))
age0 = .none
var age1: Optional<Optional> = .some(.some(10))
age1 = .none 
var age: Int?? = 10
var age0: Optional<Optional> = 10

七、String、Array的底层分析

1. String

1.1 关于String的思考

我们先来思考String变量占用多少内存

var str1 = "0123456789"
print(Mems.size(ofVal: &str1)) // 16
print(Mems.memStr(ofVal: &str1)) // 0x3736353433323130 0xea00000000003938 
    1. 我们通过打印可以看到String变量占用了16个字节,并且打印内存布局,前后各占用了8个字节
    1. 下面我们再进行反汇编来观察下:
      可以看到这两句指令正是分配了前后8个字节给了String变量 -w715

那String变量底层存储的是什么呢?

    1. 我们通过上面看到String变量的16个字节的值其实是对应转成的ASCII码值
    1. ASCII码表的地址:www.ascii-code.com -w1139
    1. 我们看上图就可以得知:
    • 左边对应的是0~9的十六进制ASCII码值
    • 又因为小端模式(高高低低)下高字节放高地址,低字节放低地址的原则,对比正是我们打印的16个字节中存储的数据
      0x3736353433323130 0xea00000000003938 
      

然后我们再看后8个字节前面的ea分别代表的是类型长度

  • 如果String的数据是直接存储在变量中的,就是用e来标明类型
  • 如果要是存储在其他地方,就会用别的字母来表示
  • 我们String字符的长度正好是10,所以就是十六进制的a
var str1 = "0123456789ABCDE"
print(Mems.size(ofVal: &str1)) // 16
print(Mems.memStr(ofVal: &str1)) // 0x3736353433323130 0xef45444342413938
  • 我们打印上面这个String变量,发现表示长度的值正好变成了f
  • 而后7个字节也都被填满了,所以也证明了这种方式最多只能存储15个字节的数据
  • 这种方式很像OC中的Tagger Pointer的存储方式

如果存储的数据超过15个字符,String变量又会是什么样呢?

  • 我们改变String变量的值,再进行打印观察
var str1 = "0123456789ABCDEF"
print(Mems.size(ofVal: &str1)) // 16
print(Mems.memStr(ofVal: &str1)) // 0xd000000000000010 0x80000001000079a0
  • 我们发现String变量的内存占用还是16个字节,但是内存布局已经完全不一样了
  • 这时我们就需要借助反汇编来进一步分析了: -w998
  • 看上图能发现最后还是会先后分配8个字节String变量,但不同的是在这之前会调用了函数,并将返回值给了String变量前8个字节
  • 而且分别将字符串的值还有长度作为参数传递了进去,下面我们就看看调用的函数里具体做了什么 -w995 -w1058
  • 我们可以看到函数内部会将一个掩码的值和String变量的地址值相加,然后存储到String变量的后8个字节中
  • 所以我们可以反向计算出所存储的数据真实地址值
0x80000001000079a0 - 0x7fffffffffffffe0 = 0x1000079C0
  • 其实也就是一开始存储到rdi中的值 -w640
    • 通过打印真实地址值可以看到16个字节确实都是存储着对应的ASCII码值

那么真实数据是存储在什么地方呢?

  • 通过观察它的地址我们可以大概推测是在数据段
  • 为了更确切的认证我们的推测,使用MachOView来直接查看在可执行文件中这句代码的真正存储位置
  • 我们找到项目中的可执行文件,然后右键Show in Finder -w357
  • 然后右键通过MachOView的方式来打开 -w498
  • 最终我们发现在代码段中的字符串常量区-w1055

对比两个字符串的存储位置

  • 我们现在分别查看下这两个字符串的存储位置是否相同
var str1 = "0123456789"
var str2 = "0123456789ABCDEF" 
  • 我们还是用MachOView来打开可执行文件,发现两个字符串的真实地址都是放在代码段中的字符串常量区,并且相差16个字节 -w1165
  • 然后我们再看打印的地址的前8个字节
0xd000000000000010 0x80000001000079a0
  • 按照推测10应该也是表示长度的十六进制,而前面的d就代表着这种类型
  • 我们更改下字符串的值,发现果然表示长度的值也随之变化了
var str2 = "0123456789ABCDEFGH"
print(Mems.size(ofVal: &str2)) // 16
print(Mems.memStr(ofVal: &str2)) // 0xd000000000000012 0x80000001000079a0

如果分别给两个String变量进行拼接会怎样呢?

var str1 = "0123456789"
str1.append("G")

print(Mems.size(ofVal: &str1)) // 16
print(Mems.memStr(ofVal: &str1)) // 0x3736353433323130 0xeb00000000473938

var str2 = "0123456789ABCDEF"
str2.append("G")

print(Mems.size(ofVal: &str2)) // 16
print(Mems.memStr(ofVal: &str2)) // 0xf000000000000011 0x0000000100776ed0
  • 我们发现str1的后8个字节还有位置可以存放新的字符串,所以还是继续存储在内存变量里
  • 而str2的内存布局不一样了,前8个字节可以看出来类型变成f,字符串长度也变为十六进制的11
  • 而后8个字节的地址很像堆空间的地址值

验证String变量的存储位置是否在堆空间

  • 为了验证我们的推测,下面用反汇编来进行观察
  • 我们在验证之前先创建一个类的实例变量,然后跟进去在内部调用malloc的指令位置打上断点
class Person { }

var p = Person() 

-w979

  • 然后我们先将断点置灰,重新反汇编之前的Sting变量 -w979
  • 然后将置灰的malloc的断点点亮,然后进入 -w978
  • 发现确实会进入到我们之前在调用malloc的断点处,所以这就验证了确实会分配堆空间内存来存储String变量的值了
  • 我们还可以用LLDB的指令bt来打印调用栈详细信息来查看 -w979
  • 发现也是在调用完append方法之后就会进行malloc的调用了,从这一层面也验证了我们的推测

那堆空间里存储的str2的值是怎样的呢?

  • 然后我们过掉了append函数后,打印str2的地址值,然后再打印后8个字节存放的堆空间地址值 -w981
  • 其内部偏移了32个字节后,正是我们String变量ASCII码值

1.2 总结

    1. 如果字符串长度小于等于0xF(十进制为15),字符串内容直接存储到字符串变量的内存中,并以ASCII码值的小端模式来进行存储
  • 第9个字节会存储字符串变量的类型字符长度
var str1 = "0123456789"
print(Mems.size(ofVal: &str1)) // 16
print(Mems.memStr(ofVal: &str1)) // 0x3736353433323130 0xeb00000000473938

进行字符串拼接操作后

    1. 如果拼接后的字符串长度还是小于等于0xF(十进制为15) ,存储位置同未拼接之前
var str1 = "0123456789"
str1.append("ABCDE")

print(Mems.size(ofVal: &str1)) // 16
print(Mems.memStr(ofVal: &str1)) // 0x3736353433323130 0xef45444342413938
    1. 如果拼接后的字符串长度大于0xF(十进制为15),会开辟堆空间来存储字符串内容
  • 字符串的地址值中:
  • 前8个字节存储字符串变量的类型字符长度
  • 后8个字节存储着堆空间的地址值堆空间地址 + 0x20可以得到真正的字符串内容
  • 堆空间地址的前32个字节是用来存储描述信息
  • 由于常量区是程序运行之前就已经确定位置了的,所以拼接字符串是运行时操作,不可能再回存放到常量区,所以直接分配堆空间进行存储
var str1 = "0123456789"
str1.append("ABCDEF")

print(Mems.size(ofVal: &str1)) // 16
print(Mems.memStr(ofVal: &str1)) // 0xf000000000000010 0x000000010051d600
    1. 如果字符串长度大于0xF(十进制为15) , 字符串内容会存储在__TEXT.cstring中(常量区)
  • 字符串的地址值中,前8个字节存储字符串变量的类型和字符长度,后8个字节存储着一个地址值,地址值 & mask可以得到字符串内容在常量区真正的地址值
var str2 = "0123456789ABCDEF"
print(Mems.size(ofVal: &str2)) // 16
print(Mems.memStr(ofVal: &str2)) // 0xd000000000000010 0x80000001000079a0
    1. 进行字符串拼接操作后,同上面开辟堆空间存储的方式
var str2 = "0123456789ABCDEF"
str2.append("G")

print(Mems.size(ofVal: &str2)) // 16
print(Mems.memStr(ofVal: &str2)) // 0xf000000000000011 0x0000000106232230

1.3 dyld_stub_binder

    1. 我们反汇编看到底层调用的String.init方法其实是动态库里的方法,而动态库在内存中的位置是在Mach-O文件的更高地址的位置,如下图所示 -w939
    1. 所以我们这里看到的地址值其实是一个假的地址值只是用来占位的 -w999
  • 我们再跟进发现其内部会跳转到另一个地址,取出其存储的真正需要调用的地址值去调用
  • 下一个调用的地址值一般都是相差6个字节
0x10000774e + 0x6 = 0x100007754
0x100007754 + 0x48bc(%rip) = 0x10000C010
最后就是去0x10000C010地址中找到需要调用的地址值0x100007858

-w998 -w997

    1. 然后一直跟进,最后会进入到动态库的dyld_stub_binder中进行绑定 -w996
    1. 最后才会真正进入到动态库中的String.init执行指令,而且可以发现其真正的地址值非常大,这也能侧面证明动态库是在可执行文件更高地址的位置 -w1000
    1. 然后我们在执行到下一个String.init的调用 -w997
    1. 跟进去发现这是要跳转的地址值就已经是动态库中的String.init真实地址值了 -w997 -w999
    1. 这也说明了dyld_stub_binder只会执行一次,而且是用到的时候在进行调用,也就是延迟绑定
    1. dyld_stub_binder的主要作用就是在程序运行时,将真正需要调用的函数地址替换掉之前的占位地址

2. 关于Array的思考

我们来思考Array变量占用多少内存?

var array = [1, 2, 3, 4]

print(Mems.size(ofVal: &array)) // 8
print(Mems.ptr(ofVal: &array)) // 0x000000010000c1c8
print(Mems.ptr(ofRef: array)) // 0x0000000105862270
    1. 我们通过打印可以看到Array变量占用了8个字节,其内存地址就是存储在全局区的地址
    1. 然而我们发现其内存地址的存储空间存储的地址值更像一个堆空间的地址

Array变量存储在什么地方呢?

  • 3.带着疑问我们还是进行反汇编来观察下,并且在malloc的调用指令处打上断点 -w988
  • 发现确实调用了malloc,那么就证明了Array变量内部会分配堆空间 -w1000
    1. 等执行完返回值给到Array变量之后,我们打印Array变量存储的地址值内存布局,发现其内部偏移32个字节的位置存储着元素1、2、3、4
  • 我们还可以直接通过打印内存结构来观察
    var array = [1, 2, 3, 4]
    print(Mems.memStr(ofRef: array))
    
    //0x00007fff88a8dd18
    //0x0000000200000003
    //0x0000000000000004
    //0x0000000000000008
    //0x0000000000000001
    //0x0000000000000002
    //0x0000000000000003
    //0x0000000000000004 
    
  • 我们调整一下元素数量,再打印观察
    var array = [Int]()
    
    for i in 1...8 {
        array.append(i)
    }
    
    print(Mems.memStr(ofRef: array))
    
    //0x00007fff88a8e460
    //0x0000000200000003
    //0x0000000000000008
    //0x0000000000000010
    //0x0000000000000001
    //0x0000000000000002
    //0x0000000000000003
    //0x0000000000000004
    //0x0000000000000005
    //0x0000000000000006
    //0x0000000000000007
    //0x0000000000000008
    

八、高级运算符

1. 溢出运算符(Overflow Operator)

    1. Swift的算数运算符出现溢出时会抛出运行时错误
    1. Swift有溢出运算符&+、&-、&*,用来支持溢出运算 image.png
var min = UInt8.min
print(min &- 1) // 255, Int8.max

var max = UInt8.max
print(max &+ 1) // 0, Int8.min
print(max &* 2) // 254, 等价于 max &+ max、

image.png

计算方式

    1. 类似于一个循环,最大值255再+1,就会回到0;最小值0再-1,就会回到255
    1. max &* 2就等于max &+ max,也就是255 + 1 + 254,255 + 1会变为0,那么最后的值就是254 -w596

2. 运算符重载(Operator Overload)

    1. 结构体枚举可以为现有的运算符提供自定义的实现,这个操作叫做运算符重载
struct Point {
    var x: Int, y: Int
}

func + (p1: Point, p2: Point) -> Point {
    Point(x: p1.x + p2.x, y: p1.y + p2.y)
}

let p = Point(x: 10, y: 20) + Point(x: 11, y: 22)
print(p) // Point(x: 21, y: 42) Point(x: 11, y: 22)
    1. 一般将运算符重载写在相关的结构体枚举的内部
struct Point {
    var x: Int, y: Int

    // 默认就是中缀运算符
    static func + (p1: Point, p2: Point) -> Point {
        Point(x: p1.x + p2.x, y: p1.y + p2.y)
    }

    static func - (p1: Point, p2: Point) -> Point {
        Point(x: p1.x - p2.x, y: p1.y - p2.y)
    }

    // 前缀运算符
    static prefix func - (p: Point) -> Point {
        Point(x: -p.x, y: -p.y)
    }

    static func += (p1: inout Point, p2: Point) {
        p1 = p1 + p2
    }

    static prefix func ++ (p: inout Point) -> Point {
        p += Point(x: 1, y: 1)
        return p
    }

    // 后缀运算符
    static postfix func ++ (p: inout Point) -> Point {
        let tmp = p
        p += Point(x: 1, y: 1)
        return tmp
    }

    static func == (p1: Point, p2: Point) -> Bool {
        (p1.x == p2.x) && (p1.y == p2.y)
    }
}

var p1 = Point(x: 10, y: 20)
var p2 = Point(x: 11, y: 22)
print(p1 + p2) // Point(x: 21, y: 42)
print(p2 - p1) // Point(x: 1, y: 2)
print(-p1) // Point(x: -10, y: -20)

p1 += p2
print(p1, p2) // Point(x: 21, y: 42) Point(x: 11, y: 22)

p1 = ++p2
print(p1, p2) // Point(x: 12, y: 23) Point(x: 12, y: 23)

p1 = p2++
print(p1, p2) // Point(x: 12, y: 23) Point(x: 13, y: 24)
print(p1 == p2) // false

3. Equatable

    1. 要想得知两个实例是否等价,一般做法是遵守Equatable协议,重载==运算符 于此同时,等价于!=运算符
class Person: Equatable {
    var age: Int

    init(age: Int) {
        self.age = age
    }

    static func == (lhs: Person, rhs: Person) -> Bool {
        lhs.age == rhs.age
    }
}

var p1 = Person(age: 10)
var p2 = Person(age: 20)
print(p1 == p2) // false
print(p1 != p2) // true
    1. 如果没有遵守Equatable协议,使用!=就会报错 -w640
    1. 如果没有遵守Equatable协议,只重载==运算符,也可以使用p1 == p2的判断,但是遵守Equatable协议是为了能够在有限制的泛型函数中作为参数使用
func equal<T: Equatable>(_ t1: T, _ t2: T) -> Bool {
    t1 == t2
}

print(equal(p1, p2)) // false

Swift为以下类型提供默认的Equatable实现

    1. 没有关联类型的枚举
enum Answer {
    case right, wrong
}

var s1 = Answer.right
var s2 = Answer.wrong
print(s1 == s2) 
    1. 只拥有遵守Equatable协议关联类型的枚举
    1. 系统很多自带类型都已经遵守了Equatable协议,类似Int、Double
enum Answer: Equatable {
    case right, wrong(Int)
}

var s1 = Answer.wrong(20)
var s2 = Answer.wrong(10)
print(s1 == s2)
    1. 关联类型没有遵守Equatable协议的就会报错 -w640
    1. 只拥有遵守Equatable协议存储属性的结构体
struct Point: Equatable {
    var x: Int, y: Int
}

var p1 = Point(x: 10, y: 20)
var p2 = Point(x: 11, y: 22)
print(p1 == p2) // false
print(p1 != p2) // true
    1. 引用类型比较存储的地址值是否相等(是否引用着同一个对象),使用恒等运算符===、!==
class Person {

}

var p1 = Person()
var p2 = Person()
print(p1 === p2) // false
print(p1 !== p2) // true 

4. Comparable

    1. 要想比较两个实例的大小,一般做法是遵守Comparable协议,重载相应的运算符
struct Student: Comparable {
    var age: Int
    var score: Int
    init(score: Int, age: Int) {
        self.score = score
        self.age = age
    }

    static func < (lhs: Student, rhs: Student) -> Bool {
        (lhs.score < rhs.score) || (lhs.score == rhs.score && lhs.age > rhs.age)
    }

    static func > (lhs: Student, rhs: Student) -> Bool {
        (lhs.score > rhs.score) || (lhs.score == rhs.score && lhs.age < rhs.age)
    }

    static func <= (lhs: Student, rhs: Student) -> Bool {
        !(lhs > rhs)
    }

    static func >= (lhs: Student, rhs: Student) -> Bool {
        !(lhs < rhs)
    }
}

var stu1 = Student(score: 100, age: 20)
var stu2 = Student(score: 98, age: 18)
var stu3 = Student(score: 100, age: 20)

print(stu1 > stu2) // true
print(stu1 >= stu2) // true
print(stu1 >= stu3) // true
print(stu1 <= stu3) // true
print(stu2 < stu1) // true
print(stu2 <= stu1) // true 

5. 自定义运算符(Custom Operator)

    1. 可以自定义新的运算符: 在全局作用域使用operator进行声明
prefix operator 前缀运算符
postfix operator 后缀运算符
infix operator 中缀运算符:优先级组

precedencegroup 优先级组 {
    associativity: 结合性(left\right\none)
    higherThan: 比谁的优先级更高
    lowerThan: 比谁的优先级低
    assignment: true代表在可选链操作中拥有跟赋值运算符一样的优先级
} 
    1. 自定义运算符的使用示例如下
prefix operator +++

prefix func +++ (_ i: inout Int) {
    i += 2
}

var age = 10
+++age
print(age) // 12 
infix operator +-: PlusMinusPrecedence
precedencegroup PlusMinusPrecedence {
    associativity: none
    higherThan: AdditionPrecedence
    lowerThan: MultiplicationPrecedence
    assignment: true
}

struct Point {
    var x = 0, y = 0
    static func +- (p1: Point, p2: Point) -> Point {
        Point(x: p1.x + p2.x, y: p1.y - p2.y)
    }
}

var p1 = Point(x: 10, y: 20)
var p2 = Point(x: 5, y: 10)

print(p1 +- p2) // Point(x: 15, y: 10)

优先级组中的associativity的设置影响

associativity对应的三个选项

left: 从左往右执行,可以多个进行结合
right: 从右往左执行,可以多个进行结合
none: 不支持多个结合 
    1. 如果再增加一个计算就会报错,因为我们设置的associativitynone -w643
    1. 我们把associativity改为left或者right,再运行就可以了
infix operator +-: PlusMinusPrecedence
precedencegroup PlusMinusPrecedence {
    associativity: left
    higherThan: AdditionPrecedence
    lowerThan: MultiplicationPrecedence
    assignment: true
}

var p3 = Point(x: 5, y: 10)
print(p1 +- p2 +- p3) // Point(x: 20, y: 0)

优先级组中的assignment的设置影响

我们先看下面的示例代码

class Person {
var age = 0
var point: Point = Point()
}

var p: Person? = nil

print(p?.point +- Point(x: 10, y: 20))

设置assignmenttrue的意思就是如果在运算中,前面的可选项为nil,那么运算符后面的代码就不会执行了

Apple文档参考链接: developer.apple.com/documentati…

另一个: docs.swift.org/swift-book/…

九、扩展(Extension)

1. 基本概念

    1. Swift中的扩展,类似于OC中的Category
    1. 扩展可以为枚举结构体协议添加新功能;可以添加方法、便捷初始化器计算属性下标嵌套类型协议
    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 Array {
    subscript(nullable idx: Int) -> Element? {
        if (startIndex..<endIndex).contains(idx) {
            return self[idx]
        }
        return nil
    }
} 
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
    }
} 

3. 初始化器

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: 10)
    var p4 = Point(x: 10, y: 20)
    var p5 = Point(p4) 
    
  • required的初始化器也不能写在扩展中 -w634

4. 协议

    1. 如果一个类型已经实现了协议的所有要求,但是还没有声明它遵守了这个协议,可以通过扩展来让他遵守这个协议
    protocol TestProtocol {
        func test1()
    }
    
    class TestClass {
        func test1() {
            print("TestClass test1")
        }
    }
    
    extension TestClass: TestProtocol { } 
    
    extension BinaryInteger {
        func isOdd() -> Bool {self % 2 != 0 }
    }
    
    print(10.isOdd()) 
    
    1. 扩展可以给协议提供默认实现,也间接实现可选协议的结果
      扩展可以给协议扩充协议中从未声明过的方法
    protocol TestProtocol {
        func test1()
    }
    
    extension TestProtocol {
        func test1() {
            print("TestProtocol test1")
        }
    
        func test2() {
            print("TestProtocol test2")
        }
    }
    
    class TestClass: TestProtocol { }
    var cls = TestClass()
    cls.test1() // TestProtocol test1
    cls.test2() // TestProtocol test2
    
    var cls2: TestProtocol = TestClass()
    cls2.test1() // TestProtocol test1
    cls2.test2() // TestProtocol test2 
    
    class TestClass: TestProtocol {
        func test1() {
            print("TestClass test1")
        }
    
        func test2() {
            print("TestClass test2")
        }
    }
    
    var cls = TestClass()
    cls.test1() // TestClass test1
    cls.test2() // TestClass test2
    
    var cls2: TestProtocol = TestClass()
    cls2.test1() // TestClass test1
    cls2.test2() // TestProtocol test2 
    

5. 泛型

class Stack<E> {
    var elements = [E]()
    func push(_ element: E) {
        elements.append(element)
    }
    
    func pop() -> E {
        elements.removeLast()
    }
    
    func size() -> Int {
        elements.count
    }
}  
    1. 扩展中依然可以使用原类型中的泛型类型
    extension Stack {
        func top() -> E {
            elements.last!
        }
    } 
    
    1. 符合条件才扩展
    extension Stack: Equatable where E : Equatable {
        static func == (left: Stack, right: Stack) -> Bool {
            left.elements == right.elements
        }
    } 
    

十、访问控制(Access Control)

1. 基本概念

在访问权限控制这块,Swift提供了5个不同的访问级别(以下是从高到低排列,实体指被访问级别修饰的内容)

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

绝大部分实体默认都是internal级别

2. 访问级别的使用准则

    1. 一个实体不可以被更低访问级别的实体定义
    1. 变量\常量类型 ≥ 变量\常量
internal class Person {} // 变量类型
fileprivate var person: Person // 变量 

-w635

  • 3. 参数类型、返回值类型 ≥ 函数
// 参数类型:Int、Double
// 函数:func test
internal func test(_ num: Int) -> Double {
    return Double(num)
} 
    1. 父类 ≥ 子类
class Person {}
class Student: Person {} 
public class Person {}
class Student: Person {} 

-w645

  • 5. 父协议 ≥ 子协议
public protocol Sportable {}
internal protocol Runnalbe: Sportable {} 

-w646

  • 6. 原类型 ≥ typealias
class Person {} // 原类型
private typealias MyPerson = Person 
    1. 原始值类型\关联值类型 ≥ 枚举类型
typealias MyInt = Int
typealias MyString = String

enum Score {
    case point(MyInt)
    case grade(MyString)
} 

-w640

  • 8. 定义类型A时用到的其他类型 ≥ 类型A
typealias MyString = String

struct Dog {}

class Person {
    var age: MyString = ""
    var dog: Dog?
} 

-w645

3. 元组类型

  • 元组类型的访问级别是所有成员类型最低的那个
internal struct Dog { }
fileprivate class Person { }

// (Dog, Person)中更低的访问级别是fileprivate,所以元组的访问级别就是fileprivate
fileprivate var datal: (Dog, Person)
private var data2: (Dog, Person) 

4. 泛型类型

  • 泛型类型的访问级别是类型的访问级别以及所有泛型类型参数的访问级别中最低的那个
internal class Car {}
fileprivate class Dog {}
public class Person<T1, T2> {}

// Person<Car, Dog>中比较的是Person、Car、Dog三个的访问级别最低的那个,也就是fileprivate,fileprivate就是泛型类型的访问级别
fileprivate var p = Person<Car, Dog>() 

5. 成员、嵌套类型

    1. 类型的访问级别会影响成员(属性方法初始化器下标),嵌套类型的默认访问级别
    1. 一般情况下,类型为privatefileprivate,那么成员\嵌套类型默认也是privatefileprivate
fileprivate class FilePrivateClass { // fileprivate
    func f1() {} // fileprivate
    private func f2() {} // private
}

private class PrivateClass { // private
    func f() {} // private
} 
    1. 一般情况下,类型为internalpublic,那么成员/嵌套类型默认是internal
public class PublicClass { // public
    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
} 

看下面几个示例,编译能否通过?

示例1

private class Person {}
fileprivate class Student: Person {} 
class Test {
    private class Person {}
    fileprivate class Student: Person {}
} 

结果是第一段代码编译通过,第二段代码编译报错

-w642

第一段代码编译通过,是因为两个全局变量不管是private还是fileprivate,作用域都是当前文件,所以访问级别就相同了

第二段代码的两个属性的作用域局限到类里面了,那访问级别就有差异了

示例2

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 {
    private var age: Int = 0
    private func run() {}
}

fileprivate struct Person {
    var dog: Dog = Dog()
    mutating func walk() {
        dog.run()
        dog.age = 1
    }
} 

结果是第一段代码编译通过,第二段代码编译报错 -w646

第一段代码编译通过,是因为两个结构体的访问级别都是该文件内,所以访问级别相同

第二段代码报错是因为Dog里的属性和方法的访问级别是更低的了,虽然两个结构体的访问级别相同,但从Person里调用Dog中的属性和方法是访问不到的

结论:直接在全局作用域下定义的private等于fileprivate

6. 成员的重写

子类重写成员的访问级别必须 ≥ 子类的访问级别,或者 ≥ 父类被重写成员的访问级别

class Person {
    internal func run() {}
}

fileprivate class Student: Person {
    fileprivate override func run() {}
} 

父类的成员不能被成员作用域外定义的子类重写 -w641

放到同一个作用域下

public class Person {
    private var age: Int = 0
    
    public class Student: Person {
        override var age: Int {
            set {}
            get { 10 }
        }
    }
} 

7. getter、setter

    1. getter、setter默认自动接收它们所属环境的访问级别
    1. 可以给setter单独设置一个比getter更低的访问级别,用以限制写的权限
fileprivate(set) public var num = 10
num = 10
print(num) 

-w645

8. 初始化器

    1. 如果一个public类想在另一个模块调用编译生成的默认无参初始化器,必须显式提供public的无参初始化器,因为public类的默认初始化器是internal级别
    public class Person {
        // 默认生成的,因为是internal,所以外部无法调用到该初始化器
    //    internal init() {
    //
    //    }
    }
    
    变成这样
    
    public class Person {
        // 自己手动添加指定初始化器,并用public修饰,外部才能访问的到
        public init() {
    
        }
    } 
    
    1. required初始化器 ≥ 它的默认访问级别
    fileprivate class Person {
        internal required init() {}
    } 
    
    1. 当类是public的时候,它的默认初始化器就是internal级别的,所以不会报错
    public class Person {
        internal required init() {}
    } 
    

    -w639

    1. 如果结构体有private\fileprivate的存储实例属性,那么它的成员初始化器也是private\fileprivate,否则默认就是internal -w641
    1. 结构体里有一个属性设置为private,带有其他属性的初始化器也没有了 -w642

9. 枚举类型的case

    1. 不能给enum的每个case单独设置访问级别 -w641
    1. 每个case自动接收enum的访问级别
    fileprivate enum Season {
        case spring // fileprivate
        case summer // fileprivate
        case autumn // fileprivate
        case winter // fileprivate
    } 
    
    1. public enum定义的case也是public
    public enum Season {
        case spring // public
        case summer // public
        case autumn // public
        case winter // public
    } 
    

10. 协议

    1. 协议中定义的要求自动接收协议的访问级别,不能单独设置访问级别 -w637
    1. public协议定义的要求也是public
    public protocol Runnable {
        func run()
    } 
    
    1. 协议实现的访问级别必须 ≥ 类型的访问级别,或者 ≥ 协议的访问级别 -w641 -w640

11. 扩展

    1. 如果有显式设置扩展的访问级别,扩展添加的成员自动接收扩展的访问级别
    class Person {
    
    }
    
    private extension Person {
        func run() {} // private
    } 
    
    1. 如果没有显式设置扩展的访问级别,扩展添加的成员的默认访问级别,跟直接在类型中定义的成员一样
    private class Person {
    
    }
    
    extension Person {
        func run() {} // private
    } 
    
    1. 可以单独给扩展添加的成员设置访问级别
    class Person {
    
    }
    
    extension Person {
        private func run() {} 
    } 
    
    1. 不能给用于遵守协议的扩展显式设置扩展的访问级别 -w645
    1. 在同一文件中的扩展,可以写成类似多个部分的类型声明
    1. 在原本的声明中声明一个私有成员,可以在同一个文件的扩展中访问它
    1. 在扩展中声明一个私有成员,可以在同一文件的其他扩展中、原本声明中访问它
    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()
        }
    } 
    

12. 将方法赋值给var\let

    1. 方法也可以像函数那样,赋值给一个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
    

十一、内存管理

1. 基本概念

  • OC一样,Swift也是采取基于引用计数的ARC内存管理方案(针对堆空间
  • Swift的ARC中有三种引用:
  • a. 强引用(strong reference) : 默认情况下,引用都是强引用
    class Person { }
    var po: Person? 
    
  • b. 弱引用(weak reference) :通过weak定义弱引用
    class Person { }
    weak var po: Person? 
    
    • 必须是可选类型的var,因为实例销毁后,ARC会自动将弱引用设置为nil -w634

    • ARC自动给弱引用设置nil时,不会触发属性观察器

  • c. 无主引用(unowned reference) : 通过unowned定义无主引用
    不会产生强引用,实例销毁后仍然存储着实例的内存地址(类似于OC中的unsafe_unretained
    class Person { }
    unowned var po: Person?
    
    • 试图在实例销毁后访问无主引用,会产生运行时错误(野指针)

2. weak、unowned的使用限制

    1. weak、unowned只能用在类实例上面
    1. 只有是存放在堆空间的,堆空间的内存是需要我们手动管理的
protocol Liveable: AnyObject { }
class Person { }

weak var po: Person?
weak var p1: AnyObject?
weak var p2: Liveable?

unowned var p10: Person?
unowned var p11: AnyObject?
unowned var p12: Liveable? 

3. Autoreleasepool

-w628

class Person {
var age: Int
var name: String

init(age: Int, name: String) {
    self.age = age
    self.name = name
}

func run() {}
}

autoreleasepool {
let p = Person(age: 20, name: "Jack")
p.run()
} 

4. 循环引用(Reference Cycle)

    1. weak、unowned都能解决循环引用的问题,unowned要比weak少一些性能消耗
    1. 在生命周期中可能会变为nil的使用weak -w649
    1. 初始化赋值后再也不会变为nil的使用unowned -w644

5. 闭包的循环引用

    1. 闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行了retain操作)
    1. 下面代码会产生循环引用,导致Person对象无法释放(看不到Person的deinit被调用)
class Person {
    var fn: (() -> ())?
    func run() { print("run") }
    deinit { print("deinit") }
}

func test() {
    let p = Person()
    p.fn = { p.run() }
}

test() 
    1. 在闭包表达式的捕获列表声明weakunowned引用,解决循环引用问题
func test() {
    let p = Person()
    p.fn = {
        [weak p] in
        p?.run()
    }
} 
func test() {
    let p = Person()
    p.fn = {
        [unowned p] in
        p.run()
    }
} 
    1. 如果想在定义闭包属性的同时引用self,这个闭包必须是lazy的(因为在实例初始化完毕之后才能引用self-w645
class Person {
    lazy var fn: (() -> ()) = {
        [weak self] in
        self?.run()
    }

    func run() { print("run") }
    deinit { print("deinit") }
} 
    1. 闭包fn内部如果用到了实例成员(属性、方法),编译器会强制要求明确写出self -w642
    1. 如果lazy属性是闭包调用的结果,那么不用考虑循环引用的问题(因为闭包调用后,闭包的生命周期就结束了)
class Person {
    var age: Int = 0
    lazy var getAge: Int = {
        self.age
    }()

    deinit { print("deinit") }
} 

6. @escaping

    1. 非逃逸闭包、逃逸闭包,一般都是当做参数传递给函数
    1. 非逃逸闭包:闭包调用发生在函数结束前,闭包调用在函数作用域内
typealias Fn = () -> ()

func test1(_ fn: Fn) { fn() }
    1. 逃逸闭包:闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过@escaping声明
typealias Fn = () -> ()

var gFn: Fn?
func test2(_ fn: @escaping Fn) { gFn = fn }
    1. DispatchQueue.global().async也是一个逃逸闭包 -w605 使用示例如下
import Dispatch
typealias Fn = () -> ()

func test3(_ fn: @escaping Fn) {
DispatchQueue.global().async {
    fn()
}
} 
class Person {
var fn: Fn

// fn是逃逸闭包
init(fn: @escaping Fn) {
    self.fn = fn
}

func run() {
    // DispatchQueue.global().async也是一个逃逸闭包
    // 它用到了实例成员(属性、方法),编译器会强制要求明确写出self
    DispatchQueue.global().async {
        self.fn()
    }
}
} 
    1. 逃逸闭包不可以捕获inout参数
      看下面的示例 -w646 -w644
  • 如果逃逸闭包里捕获的是外面的局部变量的地址值,就会有局部变量已经不存在了之后才会执行逃逸闭包的情况,那么捕获的值就是不合理的
  • 而非逃逸闭包是可以保证在局部变量的生命周期没有结束的时候就能够执行闭包的

7. 内存访问冲突(Conflicting Access to Memory)

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

  • 至少一个是写入操作
  • 它们访问的是同一块内存
  • 它们的访问时间重叠(比如在同一个函数内)
  1. 看下面示例,哪个会造成内存访问冲突
func plus(_ num: inout Int) -> Int { num + 1 }

var number = 1
number = plus(&number)
var step = 1
func increment(_ num: inout Int) { num += step }
increment(&step) 
  • 第一个不会造成内存访问冲突,第二个会造成内存访问冲突,并报错
  • 因为在num += step中既访问了step的值,同时又进行了写入操作 -w716 解决方案如下
var step = 1
func increment(_ num: inout Int) { num += step }

var copyOfStep = step
increment(&copyOfStep)
step = copyOfStep

2.看下面示例,哪个会造成内存访问冲突

func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}

var num1 = 42
var num2 = 30
balance(&num1, &num2) // ok
balance(&num1, &num1) // Error
  • 第一句执行不会报错,因为传进去的是两个变量的地址值,不会冲突
  • 第二句会报错,传进去的都是同一个变量的地址值,而内部又同时进行了对num1的读写操作,所以会造成内存访问冲突
  • 而且都不用运行,编译器直接就报错

-w635

3.看下面示例,哪个会造成内存访问冲突

struct Player {
var name: String
var health: Int
var energy: Int

mutating func shareHealth(with teammate: inout Player) {
    balance(&teammate.health, &health)
}
}

var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)
oscar.shareHealth(with: &maria)
oscar.shareHealth(with: &oscar)
  • 第一句执行不会报错,第二句执行会报错
  • 因为传入的地址都是同一个,会造成内存访问冲突,而且也是在编译阶段就直接报错了 -w647

4.看下面示例,哪个会造成内存访问冲突

var tuple = (health: 10, energy: 20)

balance(&tuple.health, &tuple.energy)

var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy)
  • 这两个都会报错,都是操作了同一个存储空间,同时进行了读写操作 -w712

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

  • 你只访问实例存储属性,不是计算属性或者类属性
  • 结构体是局部变量而非全局变量
  • 结构体要么没有被闭包捕获,要么只被非逃逸闭包捕获
    func test() {
        var tuple = (health: 10, energy: 20)
        balance(&tuple.health, &tuple.energy)

        var holly = Player(name: "Holly", health: 10, energy: 10)
        balance(&holly.health, &holly.energy)
    }

    test() 

十二、指针

1. 指针简介

Swift中也有专门的指针类型,这些都被定性为Unsafe(不安全的),常见的有以下四种类型:

  • UnsafePointer<Pointee>: 类似于
const Pointee *
  • UnsafeMutablePointer<Pointee>: 类似于
Pointee *
  • UnsafeRawPointer: 类似于
const void *
  • UnsafeMutableRawPointer: 类似于
void *
  • UnsafePointerUnsafeMutablePointer
var age = 10

func test1(_ ptr: UnsafeMutablePointer<Int>) {
ptr.pointee += 10
}

func test2(_ ptr: UnsafePointer<Int>) {
print(ptr.pointee)
}

test1(&age)
test2(&age) // 20
print(age) // 20  
  • UnsafeRawPointerUnsafeMutableRawPointer
var age = 10 

func test3(_ ptr: UnsafeMutableRawPointer) {
ptr.storeBytes(of: 30, as: Int.self)
}

func test4(_ ptr: UnsafeRawPointer) {
print(ptr.load(as: Int.self))
}

test3(&age)
test4(&age) // 30
print(age) // 30 

2. 指针应用示例

NSArray的遍历方法中也使用了指针类型

-w545

var arr = NSArray(objects: 11, 22, 33, 44)
arr.enumerateObjects { (obj, idx, stop) in
print(idx, obj)

if idx == 2 { // 下标为2就停止遍历
    stop.pointee = true
}

print("----")
}

//0 11
//----
//1 22
//----
//2 33
//---- 
  • arr.enumerateObjects中的stop并不等同于break的作用,设置完stop也会继续执行完作用域中的代码,然后才会判断是否需要下一次循环
  • 在Swift中遍历元素更适用于enumerated的方式
var arr = NSArray(objects: 11, 22, 33, 44)
for (idx, obj) in arr.enumerated() {
    print(idx, obj)
    if idx == 2 { break }
} 

3. 获得指向某个变量的指针

  • 我们可以调用withUnsafeMutablePointer、withUnsafePointer来获得指向变量的指针
var age = 11
var ptr1 = withUnsafeMutablePointer(to: &age) { $0 }
var ptr2 = withUnsafePointer(to: &age) { $0 }
ptr1.pointee = 22

print(ptr2.pointee) // 22
print(age) // 22

var ptr3 = withUnsafeMutablePointer(to: &age) { UnsafeMutableRawPointer($0)}
var ptr4 = withUnsafePointer(to: &age) { UnsafeRawPointer($0) }
ptr3.storeBytes(of: 33, as: Int.self)

print(ptr4.load(as: Int.self)) // 33
print(age) // 33 
  • withUnsafeMutablePointer的实现本质就是将传入的变量地址值放到闭包表达式中作为返回值
func withUnsafeMutablePointer<Result, T>(to value: inout T, _ body: (UnsafeMutablePointer<T>) throws -> Result) rethrows -> Result {
    try body(&value)
} 

4. 获得指向堆空间实例的指针

class Person {}
var person = Person()

// ptr中存储的还是person指针变量的地址值
var ptr = withUnsafePointer(to: &person) { UnsafeRawPointer($0) }
// 从指针变量里取8个字节,也就是取出存储的堆空间地址值
var heapPtr = UnsafeRawPointer(bitPattern: ptr.load(as: UInt.self))
print(heapPtr!) 

5. 创建指针

第一种方式

var ptr = UnsafeRawPointer(bitPattern: 0x100001234) 

第二种方式

// 创建
var ptr = malloc(16)

// 存
ptr?.storeBytes(of: 11, as: Int.self)
ptr?.storeBytes(of: 22, toByteOffset: 8, as: Int.self)

// 取
print(ptr?.load(as: Int.self)) // 11
print(ptr?.load(fromByteOffset: 8, as: Int.self)) // 22

// 销毁
free(ptr) 

第三种方式

var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)

// 从前8个字节开始存储11
ptr.storeBytes(of: 11, as: Int.self)
// 指向后8个字节开始存储22
ptr.advanced(by: 8).storeBytes(of: 22, as: Int.self)

print(ptr.load(as: Int.self)) // 11
print(ptr.advanced(by: 8).load(as: Int.self)) // 22
ptr.deallocate() 

第四种方式

var ptr = UnsafeMutablePointer<Int>.allocate(capacity: 3)

// 先初始化内存
ptr.initialize(to: 11)
// ptr.successor表示下一个Int,也就是跳一个类型字节大小
ptr.successor().initialize(to: 22)
ptr.successor().successor().initialize(to: 33)

print(ptr.pointee) // 11

// ptr + 1,意味着跳过一个Int类型大小的字节数
print((ptr + 1).pointee) // 22
print((ptr + 2).pointee) // 33

print(ptr[0]) // 11
print(ptr[1]) // 22
print(ptr[2]) // 33

// 释放要调用反初始化,调用了几个就释放几个
ptr.deinitialize(count: 3)
ptr.deallocate() 
class Person {
var age: Int
var name: String
init(age: Int, name: String) {
    self.age = age
    self.name = name
}

deinit {
    print(name, "deinit")
}
}

var ptr = UnsafeMutablePointer<Person>.allocate(capacity: 3)
ptr.initialize(to: Person(age: 10, name: "Jack"))
(ptr + 1).initialize(to: Person(age: 11, name: "Rose"))
(ptr + 2).initialize(to: Person(age: 12, name: "Kate"))

ptr.deinitialize(count: 3)
ptr.deallocate() 

6. 指针之间的转换

var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
// 假想一个类型
ptr.assumingMemoryBound(to: Int.self)
// 不确定类型的pointer+8是真的加8个字节,不同于有类型的pointer
(ptr + 8).assumingMemoryBound(to: Double.self).pointee = 22.0

// 强制转换类型为Int
print(unsafeBitCast(ptr, to: UnsafePointer<Int>.self).pointee) // 11
print(unsafeBitCast((ptr + 8), to: UnsafePointer<Double>.self).pointee) // 22.0

ptr.deallocate() 
  • unsafeBitCast是忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据,所以这种转换也是不安全的
  • 类似于C++中的reinterpret_cast
  • 我们可以用unsafeBitCast的强制转换指针类型,直接将person变量里存储的堆空间地址值拷贝到ptr指针变量中,由于ptr是指针类型,那么它所指向的地址值就是堆空间地址
class Person {}
var person = Person()
var ptr = unsafeBitCast(person, to: UnsafeRawPointer.self)
print(ptr) 
  • 另一个转换方式,可以先转成UInt类型的变量,然后再从变量中取出存储的地址值
class Person {}
var person = Person()
var address = unsafeBitCast(person, to: UInt.self)
var ptr = UnsafeRawPointer(bitPattern: address) 

看下面的示例 -w944

  • IntDouble的内存结构应该是有差异的,但通过unsafeBitCast转换的age3的内存结构和age1是一样的,所以说unsafeBitCast只会转换数据类型,不会改变内存数据

十三、字面量(Literal)

1. 基本概念

  • 下面代码中的10、false、"Jack"就是字面量
var age = 10
var isRed = false
var name = "Jack" 
  • 常见字面量的默认类型 -w507
  • 可以通过typealias修改字面量的默认类型
typealias FloatLiteralType = Float
typealias IntegerLiteralType = UInt8
var age = 10 // UInt8
var height = 1.68 // Float

Swift自带的绝大部分类型、都支持直接通过字面量进行初始化

BoolIntFloatDoubleStringArrayDictionarySetOptional等

2. 字面量协议

Swift自带类型之所以能够通过字面量初始化,是因为它们遵守了对应的协议

  • Bool: ExpressibleByBooleanLiteral
  • Int: ExpressibleByIntegerLiteral
  • Float、Double: ExpressibleByIntegerLiteral、ExpressibleByFloatLiteral
  • String: ExpressibleByStringLiteral
  • Array、Set: ExpressibleByArrayLiteral
  • Dictionary: ExpressibleByDictionaryLiteral
  • Optional: ExpressibleByNilLiteral
var b: Bool = false // ExpressibleByBooleanLiteral
var i: Int = 10 // ExpressibleByIntegerLiteral
var f0: Float = 10 // ExpressibleByIntegerLiteral
var f1: Float = 10.0 // ExpressibleByFloatLiteral
var d0: Double = 10 // ExpressibleByIntegerLiteral
var d1: Double = 10.0 // ExpressibleByFloatLiteral
var s: String = "jack" // ExpressibleByStringLiteral
var arr: Array = [1, 2, 3] // ExpressibleByArrayLiteral
var set: Set = [1, 2, 3] // ExpressibleByArrayLiteral
var dict: Dictionary = ["jack" : 60] // ExpressibleByDictionaryLiteral
var o: Optional<Int> = nil // ExpressibleByNilLiteral

3.字面量协议应用

有点类似于C++中的转换构造函数

extension Int: ExpressibleByBooleanLiteral {
public init(booleanLiteral value: Bool) {
    self = value ? 1 : 0
}
}

var num: Int = true
print(num) // 1 
class Student: ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByStringLiteral, CustomDebugStringConvertible {
var name: String = ""
var score: Double = 0

required init(floatLiteral value: Double) {
    self.score = value
}

required init(integerLiteral value: Int) {
    self.score = Double(value)
}

required init(stringLiteral value: String) {
    self.name = value
}

required init(unicodeScalarLiteral value: String) {
    self.name = value
}

required init(extendedGraphemeClusterLiteral value: String) {
    self.name = value
}

var debugDescription: String {
    "name=(name), score=(score)"
}
}

var stu: Student = 90
print(stu) // name=, score=90.0

stu = 98.5
print(stu) // name=, score=98.5

stu = "Jack"
print(stu) // name=Jack, score=0.0 
struct Point {
var x = 0.0, y = 0.0
}

extension Point: ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral {
init(arrayLiteral elements: Double...) {
    guard elements.count > 0 else { return }
    self.x = elements[0]

    guard elements.count > 1 else { return }
    self.y = elements[1]
}

init(dictionaryLiteral elements: (String, Double)...) {
    for (k, v) in elements {
        if k == "x" { self.x = v }
        else if k == "y" { self.y = v }
    }
}
}

var p: Point = [10.5, 20.5]
print(p) // Point(x: 10.5, y: 20.5)

p = ["x" : 11, "y" : 22]
print(p) // Point(x: 11.0, y: 22.0) 

十四、模式匹配(Pattern)

1. 基本概念

什么是模式?

  • 模式是用于匹配的规则,比如switch的case、捕捉错误的catch、if\guard\while\for语句的条件

Swift中的模式有

  • 通配符模式(Wildcard Pattern)
  • 标识符模式(Identifier Pattern)
  • 值绑定模式(Value-Binding Pattern)
  • 元组模式(Tuple Pattern)
  • 枚举Case模式(Enumeration Case Pattern)
  • 可选模式(Optional Pattern)
  • 类型转换模式(Type-Casting Pattern)
  • 表达式模式(Expression Pattern)

2. 通配符模式(Wildcard Pattern)

  • _匹配任何值
  • _?匹配非nil
enum Life {
case human(name: String, age: Int?)
case animal(name: String, age: Int?)
}

func check(_ life: Life) {
switch life {
case .human(let name, _):
    print("human", name)
case .animal(let name, _?):
    print("animal", name)
default:
    print("other")
}
}

check(.human(name: "Rose", age: 20)) // human Rose
check(.human(name: "Jack", age: nil)) // human Jack
check(.animal(name: "Dog", age: 5)) // animal Dog
check(.animal(name: "Cat", age: nil)) // other

2.标识符模式(Identifier Pattern)

给对应的变量、常量名赋值

var age = 10
let name = "jack"

3.值绑定模式(Value-Binding Pattern)

let point = (3, 2)
switch point {
case let (x, y):
print("The point is at ((x), (y).")
} 

4.元组模式(Tuple Pattern)

let points = [(0, 0), (1, 0), (2, 0)]
for (x, _) in points {
print(x)
} 
let name: String? = "jack"
let age = 18
let info: Any = [1, 2]
switch (name, age, info) {
case (_?, _, _ as String):
print("case")
default:
print("default")
} // default 
var scores = ["jack" : 98, "rose" : 100, "kate" : 86]
for (name, score) in scores {
print(name, score)
} 

5. 枚举Case模式(Enumeration Case Pattern)

  • if case语句等价于只有1个caseswitch语句
let age = 2

// 原来的写法
if age >= 0 && age <= 9 {
print("[0, 9]")
}

// 枚举Case模式
if case 0...9 = age {
print("[0, 9]")
}

guard case 0...9 = age else { return }
print("[0, 9]")

// 等同于switch case
switch age {
case 0...9: print("[0, 9]")
default: break
} 
let ages: [Int?] = [2, 3, nil, 5]
for case nil in ages {
print("有nil值")
break
} // 有nil值 
let points = [(1, 0), (2, 1), (3, 0)]
for case let (x, 0) in points {
print(x)
} // 1 3 

6. 可选模式(Optional Pattern)

let age: Int? = 42
if case .some(let x) = age { print(x) }
if case let x? = age { print(x) }
let ages: [Int?] = [nil, 2, 3, nil, 5]
for case let age? in ages {
print(age)
} // 2 3 5

// 同上面效果等价
let ages: [Int?] = [nil, 2, 3, nil, 5]
for item in ages {
if let age = item {
    print(age)
}
}  
func check(_ num: Int?) {
switch num {
case 2?: print("2")
case 4?: print("4")
case 6?: print("6")
case _?: print("other")
case _: print("nil")
}
}

check(4) // 4
check(8) // other
check(nil) // nil 

7.类型转换模式(Type-Casting Pattern)

let num: Any = 6
switch num {
case is Int:
// 编译器依然认为num是Any类型
print("is Int", num)
//case let n as Int:
//    print("as Int", n + 1)
default:
break
} 
class Animal {
func eat() {
    print(type(of: self), "eat")
}
}

class Dog: Animal {
func run() {
    print(type(of: self), "run")
}
}

class Cat: Animal {
func jump() {
    print(type(of: self), "jump")
}
}

func check(_ animal: Animal) {
switch animal {
case let dog as Dog:
    dog.eat()
    dog.run()
case is Cat:
    animal.eat()
default: break
}
}

check(Dog()) // Dog eat, Dog run
check(Cat()) // Cat eat 

8.表达式模式(Expression Pattern)

表达式模式用在case

let point = (1, 2)
switch point {
case (0, 0):
print("(0, 0) is at the origin.")
case (-2...2, -2...2):
print("((point.0), (point.1) is near the origin.")
default:
print("The point is at ((point.0), (point.1).")
} // (1, 2) is near the origin. 

通过反汇编,我们可以看到其内部会调用~=运算符来计算(-2...2, -2...2)这个区间

-w714

9. 自定义表达式模式

可以通过重载运算符,自定义表达式模式的匹配规则

struct Student {
var score = 0, name = ""

// pattern:放的是case后面的值
// value:放的是switch后面的值
static func ~= (pattern: Int, value: Student) -> Bool {
    value.score >= pattern
}

static func ~= (pattern: ClosedRange<Int>, value: Student) -> Bool {
    pattern.contains(value.score)
}

static func ~= (pattern: Range<Int>, value: Student) -> Bool {
    pattern.contains(value.score)
}
}

var stu = Student(score: 75, name: "Jack")

switch stu {
case 100: print(">= 100")
case 90: print(">= 90")
case 80..<90: print("[80, 90]")
case 60...79: print("[60, 79]")
case 0: print(">= 0")
default: break
} // [60, 79]

if case 60 = stu {
print(">= 60")
} // >= 60

var info = (Student(score: 70, name: "Jack"), "及格")
switch info {
case let (60, text): print(text)
default: break
} // 及格 
extension String {
static func ~= (pattern: (String) -> Bool, value: String) -> Bool {
    pattern(value)
}
}

func hasPrefix(_ prefix: String) -> ((String) -> Bool) {
{ $0.hasPrefix(prefix) }
}

func hasSuffix(_ suffix: String) -> ((String) -> Bool) {
{ $0.hasSuffix(suffix) }
}

var str = "jack"
switch str {
case hasPrefix("j"), hasSuffix("k"):
print("以j开头,以k结尾")
default: break
} // 以j开头,以k结尾 
func isEven(_ i: Int) -> Bool { i % 2 == 0 }
func isOdd(_ i: Int) -> Bool { i % 2 != 0 }

extension Int {
static func ~= (pattern: (Int) -> Bool, value: Int) -> Bool {
    pattern(value)
}
}

var age = 9
switch age {
case isEven: print("偶数")
case isOdd: print("奇数")
default: print("其他")
} 
extension Int {
static func ~= (pattern: (Int) -> Bool, value: Int) -> Bool {
    pattern(value)
}
}

prefix operator ~>
prefix operator ~>=
prefix operator ~<
prefix operator ~<=

prefix func ~> (_ i: Int) -> ((Int) -> Bool) {{ $0 > i }}
prefix func ~>= (_ i: Int) -> ((Int) -> Bool) {{ $0 >= i }}
prefix func ~< (_ i: Int) -> ((Int) -> Bool) {{ $0 < i }}
prefix func ~<= (_ i: Int) -> ((Int) -> Bool) {{ $0 <= i }}

var age = 9
switch age {
case ~>=0: print("1")
case ~>10: print("2")
default: break
} // 1 

10. where

可以使用where为模式匹配增加匹配条件

var data = (10, "Jack")
switch data {
case let (age, _) where age > 10:
print(data.1, "age>10")
case let (age, _) where age > 0:
print(data.1, "age>0")
default:
break
} 
var ages = [10, 20, 44, 23, 55]
for age in ages where age > 30 {
print(age)
} // 44 55 
protocol Stackable {
associatedtype Element
}

protocol Container {
associatedtype Stack: Stackable where Stack.Element: Equatable
}

func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element : Hashable { false } 
extension Container where Self.Stack.Element: Hashable { }

专题系列文章

1. 前知识

2. 基于OC语言探索iOS底层原理

3. 基于Swift语言探索iOS底层原理

关于函数枚举可选项结构体闭包属性方法swift多态原理StringArrayDictionary引用计数MetaData等Swift基本语法和相关的底层原理文章有如下几篇:

其它底层原理专题

1. 底层原理相关专题

2. iOS相关专题

3. webApp相关专题

4. 跨平台开发方案相关专题

5. 阶段性总结:Native、WebApp、跨平台开发三种方案性能比较

6. Android、HarmonyOS页面渲染专题

7. 小程序页面渲染专题