Swift第七章 协议、错误处理

协议

  1. 协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)

     protocol Drawable {
         func draw()
         var x: Int { get set }
         var y: Int { get }
         subscript(index: Int) -> Int { get set }
     }
        
     protocol Test1 {}
     protocol Test2 {}
     protocol Test3 {}
     class TestClass : Test1, Test2, Test3 {}
    
  2. 协议中定义方法时不能有默认参数值
  3. 默认情况下,协议中定义的内容必须全部都实现

协议中的属性

  1. 协议中定义属性时必须用var关键字
  2. 实现协议时的属性权限要不小于协议中定义的属性权限
    1. 协议定义get、set,代表该属性可读写,用var存储属性或get、set计算属性去实现
    2. 协议定义get,代表该属性可读,用任何属性都可以实现。
protocol Drawable {
    func draw()
    //代表这个属性是可读可写
    var x: Int { get set }
    //这个属性是可读的
    var y: Int { get }
    //可读可写
    subscript(index: Int) -> Int { get set }
}

//方式一:
Person : Drawable {
    //用var存储属性实现
    var x: Int = 0
    //let代表只读,存储属性实现
    let y: Int = 0
    func draw() {
        print("Person draw")
    }
    //get、set计算属性去实现
    subscript(index: Int) -> Int {
        set {}
        get { index }
    }
}

//方式二:
class Person : Drawable {
    //get、set计算属性去实现
    var x: Int {
        get { 0 }
        set {}
    }
    //计算属性.get方法简写
    var y: Int { 0 }
    
    func draw() { print("Person draw") }
    
    subscript(index: Int) -> Int {
        set {}
        get { index }
    }
}

static、class

  1. 为了保证通用,协议中必须用static定义类型方法、类型属性、类型下标
protocol Drawable {
    //协议必须用static修饰
    static func draw()
}

class Person1 : Drawable {
    //实现可以用class,允许子类重写
    class func draw() {
        print("Person1 draw")
    }
}
class Person2 : Drawable {
    //也可以用static,不允许子类重写
    static func draw() {
        print("Person2 draw")
    }
}

mutating

  1. 只有将协议中的实例方法标记为mutating,才允许结构体、枚举的具体实现修改自身内存
  2. 类在实现方法时不用加mutating,枚举、结构体才需要加mutating
protocol Drawable {
    mutating func draw()
}
class Size : Drawable {
    var width: Int = 0
    //类不加mutating
    func draw() {
        width = 10
    }
}
struct Point : Drawable {
    var x: Int = 0
    //结构体加mutating
    mutating func draw() {
        x = 10
    }
}

init

  1. 协议中还可以定义初始化器init
  2. 非final类实现时必须加上required

     protocol Drawable {
         init(x: Int, y: Int)
     }
     class Point : Drawable {
         //非final类实现时必须加上required
         required init(x: Int, y: Int) {}
     }
     //final类实现时不用加required
     final class Size : Drawable {
         init(x: Int, y: Int) {}
     }
    
  3. 如果从协议实现的初始化器,刚好是重写了父类的指定初始化器,那么这个初始化必须同时加required、override

     protocol Livable {
         init(age: Int)
     }
     class Person {
         init(age: Int) {}
     }
     //协议和父类的init相同
     class Student : Person, Livable {
         required override init(age: Int) {
             super.init(age: age)
         }
     }
    

init、init?、init!

  1. 协议中定义的init?、init!,可以用init、init?、init!去实现
  2. 协议中定义的init,可以用init、init!去实现
protocol Livable {
    init()
    init?(age: Int)
    init!(no: Int)
}
class Person : Livable {
    //非可失败初始化器,实现有2种方案
    required init() {}
    // required init!() {}
    
    //有三种方案
    required init?(age: Int) {}
    // required init!(age: Int) {}
    // required init(age: Int) {}
    
    
    //有三种方案
    required init!(no: Int) {}
    // required init?(no: Int) {}
    // required init(no: Int) {}
}

协议的继承

  1. 一个协议可以继承其他协议
protocol Runnable {
    func run()
}
protocol Livable : Runnable {
    func breath()
}
class Person : Livable {
    func breath() {}
    func run() {}
}

协议组合

  1. 协议组合,可以包含1个类类型(最多1个) protocol Livable {}
protocol Livable {}
protocol Runnable {}
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
// 接收同时遵守Livable、Runnable协议、并且是Person或者其子类的实例
func fn4(obj: RealPerson) {}

CaseIterable

  1. 让枚举遵守CaseIterable协议,可以实现遍历枚举值
enum Season : CaseIterable {
    case spring, summer, autumn, winter
}


let seasons = Season.allCases
print(seasons.count) // 4
for season in seasons {
    print(season)
} // spring summer autumn winter

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
    
  2. print调用的是CustomStringConvertible协议的description
  3. debugPrint、po调用的是CustomDebugStringConvertible协议的debugDescription

Any、AnyObject

  1. Swift提供了2种特殊的类型:Any、AnyObject
  2. Any:可以代表任意类型(枚举、结构体、类,也包括函数类型)
  3. AnyObject:可以代表任意类型(在协议后面写上: AnyObject代表只有类能遵守这个协议)
  4. 在协议后面写上: class也代表只有类能遵守这个协议

     //这个协议只能被类遵守
     protocol Runnable : AnyObject {
         func run()
     }
     class Person : Runnable {}
     //报错
     //struct Size : Runnable {}
        
     //创建一个任意类型的stu
     var stu: Any = 10
     stu = "Jack"
     stu = Student()
        
     // 创建1个能存放任意类型的数组
     // var data = Array<Any>()
     var data = [Any]()
     data.append(1)
     data.append(3.14)
     data.append(Student())
     data.append("Jack")
     //闭包表达式
     data.append({ 10 })
    

is、as?、as!、as

  1. is用来判断是否为某种类型,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
     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
        
     var stu: Any = 10
     //as强制转换。as?强制转换,但是结果可以是nil
     //转换结果是可选类型,调用方法,因此:()?.方法
     (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
    

X.self、X.Type、AnyClass

  1. X.self是一个元类型(metadata)的指针,metadata存放着类型相关信息
  2. X.self属于X.Type类型
  3. 也是实例对象前8个字节的地址值。

     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
        
     public typealias AnyClass = AnyObject.Type
     var anyType2: AnyClass = Person.self
     anyType2 = Student.self
        
     var per = Person()
     //取出Person实例对象的前8个字节
     var perType = type(of: per) //type函数的返回值为 Person.self
     print(Person.self == type(of: per)) // true
    

元类型的应用

  1. 案例1:

     class Animal { required init() {} }
     class Cat : Animal {}
     class Dog : Animal {}
     class Pig : Animal {}
     //参数是Animal.Type元类型数组,返回值为Animal类型的实例对象
     func create(_ clses: [Animal.Type]) -> [Animal] {
         var arr = [Animal]()
         for cls in clses {
             //元类型.init(),创建实例。为确保每个子类型类型都有init,所以父类必须使用required
             arr.append(cls.init())
         }
         return arr
     }
     print(create([Cat.self, Dog.self, Pig.self]))
    
  2. 案例2

     class Person {
         var age: Int = 0
     }
     class Student : Person {
         var no: Int = 0
     }
     //有些runtime的方法,在swift里面也能用
     print(class_getInstanceSize(Student.self)) // 32
     print(class_getSuperclass(Student.self)!) // Person
     //Swift还有个隐藏的基类:Swift._SwiftObject
     print(class_getSuperclass(Person.self)!) // Swift._SwiftObject
    
    1. 从结果可以看得出来,Swift还有个隐藏的基类:Swift._SwiftObject
    2. 可以参考Swift源码:https://github.com/apple/swift/blob/master/stdlib/public/runtime/SwiftObject.h

Self

  1. Self代表当前类型

     class Person {
         //实例属性
         var age = 1
         //类属性
         static var count = 2
         func run() {
             //小写的self代表当前实例
             print(self.age) // 1
             //大写Self代表当前的类
             print(Self.count) // 2
         }
     }
    
  2. Self一般用作返回值类型,限定返回值跟方法调用者必须是同一类型(也可以作为参数类型)

     protocol Runnable {
         func test() -> Self
     }
     class Person : Runnable {
         required init() {}
         //这个函数的返回值是,方法调用者的类型
         func test() -> Self { type(of: self).init() }
     }
     class Student : Person {}
        
     var p = Person()
     // Person
     print(p.test())
        
     var stu = Student()
     // Student
     print(stu.test())
    

错误处理

自定义错误

  1. Swift中可以通过Error协议自定义运行时的错误信息

     enum SomeError : Error {
         //非法参数
         case illegalArg(String)
         //数组越界
         case outOfBounds(Int, Int)
         case outOfMemory
     }
    
  2. 函数内部通过throw抛出自定义Error,可能会抛出Error的函数必须加上throws声明

     //throws,代表当前函数可能会抛出异常
     func divide(_ num1: Int, _ num2: Int) throws -> Int {
         if num2 == 0 {
             //函数内部通过throw抛出自定义Error
             throw SomeError.illegalArg("0不能作为除数")
         }
         return num1 / num2
     }
    
  3. 需要使用try调用可能会抛出Error的函数

     //要使用try调用
     var result = try divide(20, 10)
    

do-catch

  1. 可以使用do-catch捕捉Error

     func test() {
     print("1")
     do {
         print("2")
         //这抛出异常
         print(try divide(20, 0))
         //永远不会被执行
         print("3")
     } catch let SomeError.illegalArg(msg) {
         print("参数异常:", msg)
     } catch let SomeError.outOfBounds(size, index) {
         print("下标越界:", "size=\(size)", "index=\(index)")
     } catch SomeError.outOfMemory {
         print("内存溢出")
     } catch {
         print("其他错误")
     }
     print("4")
     }
        
     test()
     // 1
     // 2
     // 参数异常: 0不能作为除数
     // 4
        
     //第二种实现方式
     do {
         try divide(20, 0)
     } catch let error {
         switch error {
             case let SomeError.illegalArg(msg):
             print("参数错误:", msg)
             default:
             print("其他错误")
         }
     }
    
  2. 抛出Error后,try下一句直到作用域结束的代码都将不会执行

处理Error的2种方式

  1. 通过do-catch捕捉Error
  2. 不捕捉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
        
        
        
     func test() throws {
         print("1")
         do {
             print("2")
             print(try divide(20, 0))
             print("3")
         } catch let error as SomeError { //将error强制转换为SomeError类型
             print(error)
         }
         print("4")
     }
        
     try test()
     // 1
     // 2
     // illegalArg("0不能作为除数")
     // 4
        
        
     do {
         print(try divide(20, 0))
     } catch is SomeError { //如果错误类型是SomeError就处理
         print("SomeError")
     }
    
  3. 注意点:

     //单独放这一句,不会报错,因为异常会抛出给顶层
     try divide(20, 0)
        
     //如果放到函数里面,就会报错,要求函数必须处理异常,要么do-catch、要么throws
     func test()  {
         //try divide(20, 0)
     }
    

try?、try!

  1. 可以使用try?、try!调用可能会抛出Error的函数,这样就不用去处理Error。
  2. try?调用结果,一旦有异常就为nil,否则返回可选类型。

     //此时test函数不用处理异常
     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()
    
  3. a、b是等价的

     var a = try? divide(20, 0)
     var b: Int?
     do {
         b = try divide(20, 0)
     } catch { 
         b = nil 
     }
    

rethrows

  1. rethrows表明:函数本身不会抛出错误,但调用闭包参数抛出错误,那么它会将错误向上抛

     //fn: (Int, Int) throws -> Int 参数fn是个函数,这个函数可能会抛出异常
     //exec函数不会抛出异常,但是参数是一个函数,调用的时候可能会抛出异常,此时用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)
    

defer

  1. defer语句:用来定义以任何方式(抛错误、return等)离开代码块前必须要执行的代码
  2. defer语句将延迟至当前作用域结束之前执行

     //打开文件
     func open(_ filename: String) -> Int {
         print("open")
         return 0
     }
     //关闭文件
     func close(_ file: Int) {
         print("close")
     }
        
        
     func processFile(_ filename: String) throws {
         //1. 打开文件
         let file = open(filename)
            
         //4. 通过defer,确保异常出现之前,执行关闭
         defer {
             close(file)
         }
            
         // 2. 使用file
         // ....
         try divide(20, 0)
            
         //3. 关闭文件
         // 上面一旦抛出异常,这句就不会执行
         //close(file)
     }
     try processFile("test.txt")
     // open
     // close---抛出异常之前执行
     // Fatal error: Error raised at top level
    
  3. defer语句的执行顺序与定义顺序相反

     func fn1() { print("fn1") }
     func fn2() { print("fn2") }
     func test() {
         defer { fn1() }
         defer { fn2() }
     }
     test()
     // fn2
     // fn1
    

assert(断言)

  1. 很多编程语言都有断言机制:不符合指定条件就抛出运行时错误,常用于调试(Debug)阶段的条件判断
  2. 默认情况下,Swift的断言只会在Debug模式下生效,Release模式下会忽略

     func divide(_ v1: Int, _ v2: Int) -> Int {
         assert(v2 != 0, "除数不能为0")
         return v1 / v2
     }
     print(divide(20, 0))
    
  3. 增加Swift Flags修改断言的默认行为
    1. -assert-config Release:强制关闭断言
    2. -assert-config Debug:强制开启断言 1.png

fatalError

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

局部作用域

  1. swift不支持使用大括号{}实现局部作用域,但是可以使用 do 实现局部作用域

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

泛型(Generics)

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

泛型函数

  1. 举例

     func swapValues<T>(_ a: inout T, _ b: inout T) {
         //两个元组
         (a, b) = (b, a)
     }
     var i1 = 10
     var i2 = 20
     swapValues(&i1, &i2)
        
     var d1 = 10.0
     var d2 = 20.0
     swapValues(&d1, &d2)
        
     struct Date {
         var year = 0, month = 0, day = 0
     }
     var dd1 = Date(year: 2011, month: 9, day: 10)
     var dd2 = Date(year: 2012, month: 10, day: 11)
     swapValues(&dd1, &dd2)
    
  2. 泛型函数赋值给变量

     func test<T1, T2>(_ t1: T1, _ t2: T2) {}
     //必须明确泛型的具体类型,才能赋值
     var fn: (Int, Double) -> () = test
    

泛型类型

  1. 举例

     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 
         }
     }
        
        
     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
        
     //继承
     class SubStack<E> : Stack<E> {}
        
     //struct
     struct Stack<E> {
         var elements = [E]()
         //必须加mutating
         mutating func push(_ element: E) { 
             elements.append(element) 
         }
         mutating func pop() -> E { 
             elements.removeLast() 
         }
         func top() -> E { 
             elements.last! 
         }
         func size() -> Int { 
             elements.count 
         }
     }
        
     //enum
     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")
    

关联类型(Associated Type)

  1. 协议只能使用关联类型实现泛型的效果
  2. 关联类型的作用:给协议中用到的类型定义一个占位名称
  3. 协议中可以拥有多个关联类型

     protocol Stackable {
         associatedtype Element // 关联类型
         mutating func push(_ element: Element)
         mutating func pop() -> Element
         func top() -> Element
         func size() -> Int
     }
     class StringStack : Stackable {
         // 给关联类型设定真实类型
         // typealias Element = String
         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")
        
     //将类的泛型,与协议的关联类型设置一致
     class Stack<E> : Stackable {
         // typealias Element = 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 }
     }
    

类型约束

  1. 对泛型的约束,举例

     protocol Runnable { }
     class Person { }
     //T必须是Person类,且遵守Runnable协议
     func swapValues<T : Person & Runnable>(_ a: inout T, _ b: inout T) {
         (a, b) = (b, a)
     }
     protocol Stackable {
         associatedtype Element: Equatable
     }
     //E必须遵守Equatable协议
     class Stack<E : Equatable> : Stackable { typealias Element = E }
        
     //用where加限制
     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>()
     // error: requires the types 'Int' and 'String' be equivalent
     equal(stack1, stack2)
    

协议类型的注意点

  1. 举例

     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)
    
  2. 如果协议中有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到底是什么类型,Double还是Int?
     func get(_ type: Int) -> Runnable {
         if type == 0 {
             return Person()
         }
         return Car()
     }
     var r1 = get(0)
     var r2 = get(1)
    
  3. 解决方案

    1. 方案1:使用泛型

       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)
      
    2. 方案2: 不透明类型(Opaque Type),不希望外部知道具体是什么类型

       //使用some关键字声明一个不透明类型
       func get(_ type: Int) -> some Runnable { 
           //some限制只能返回一种类型,因此只能返回Car()或者Person()
           //如果只返回一种类型,那编译器就知道speed具体是哪种类型了,因此不报错
           /*
           if type == 0 {
               return Person()
           }
           */
           return Car()
       }
       var r1 = get(0)
       var r2 = get(1)
      

some

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

     protocol Runnable { associatedtype Speed }
     class Dog : Runnable { typealias Speed = Double }
     class Person {
         //不想让外界知道这个人具体有什么样的宠物
         var pet: some Runnable {
             return Dog()
         }
     }
    

可选项的本质

  1. 可选项的本质是enum类型

     public enum Optional<Wrapped> : ExpressibleByNilLiteral {
         case none   //空值
         case some(Wrapped)  //非空值
         public init(_ some: Wrapped)
     }
     var age: Int? = 10
     var age0: Optional<Int> = Optional<Int>.some(10)
     var age1: Optional = .some(10)
     var age2 = Optional.some(10)
     var age3 = Optional(10)
     age = nil
     age3 = .none
        
     var age: Int? = nil
     var age0 = Optional<Int>.none
     var age1: Optional<Int> = .none
        
     var age: Int? = .none
     age = 10
     age = .some(20)
     age = nil
        
     switch age {
         case let v?: //本质将可选项age强制解包,如果有值复制给v。否则进入下一个case
             print("some", v)
         case nil:
             print("none")
     }
     //等价上面
     switch age {
         case let .some(v):
             print("some", v)
         case .none:
             print("none")
     }
        
     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
    
Table of Contents