Swift5.0 - day6-错误处理、泛型、高级运算符

简介: Swift5.0 - day6-错误处理、泛型、高级运算符

一、错误处理


  • 1.1、错误类型
  • 语法错误(编译报错)
  • 逻辑错误
  • 运行时错误(可能会导致闪退,一般也叫做异常)
  • 1.2、自定义错误
  • Swift中可以通过 Error 协议自定义运行时的错误信息


enum SomeError : Error {
    case illegalArg(String) 
    case outOfBounds(Int, Int) 
    case outOfMemory
}
  • 函数内部通过 throw 抛出自定义 Error,可能会抛出 Error 的函数必须加上throws声明


func divide(_ num1: Int, _ num2: Int) throws -> Int { 
    if num2 == 0 {
         throw SomeError.illegalArg("0不能作为除数")
    }
    return num1 / num2
}
  • 需要使用 try 调用可能会抛出 Error 的函数


do {
   let result = try divide(10, 0)
   print("result=\(result)")
} catch {
   print(error)
}
  • 1.3、do-catch 捕获错误可以使用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")
}
  • 抛出Error后,try下一句直到作用域结束的代码都将停止运行


test()
// 1
// 2
// 参数异常: 0不能作为除数
// 4
do {
    try divide(20, 0)
} catch let error {
    switch error {
    case let SomeError.illegalArg(msg): 
         print("参数错误:", msg)
    default: 
         print("其他错误")
    } 
}
  • 1.4、处理Error处理Error的2种方式
  • 通过do-catch捕捉Error


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
  • 不捕捉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
do {
    print(try divide(20, 0))
} catch is SomeError {
    print("SomeError")
}

提示:try 仅仅代表尝试去调用一个函数


  • 1.5、try?、try!
  • 可以使用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()
  • 下面 a、b 是等价的


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

提示:b = try divide(20, 0)  在  try 后面抛出错区后,就不会再给 b 赋值,直接走 catch


  • 1.6、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 是一个声明,上述的 rethrows 代表的是上面抛出的 错误不是 exec 函数 本身,而是 函数内部的 fn 函数 抛出的错误

  • 总之:rethrows 还是 throws 区别 如下
  • 共同点:都代表往外抛出错误
  • 不同点:throws 代表不是通过传递进来的参数调用导致的异常,是函数内部写其他代码导致的异常;rethrows:是因为传进来的参数调用导致的异常


  • 1.7、defer
  • defer 语句:用来定义以任何方式(抛错误、return等)离开代码前必须要执行的代码
  • 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
  • defer 语句的 执行顺序定义顺序 相反


func fn1() { 
    print("fn1")
}
func fn2() { 
    print("fn2") 
}
func test() {
     defer { fn1() }
     defer { fn2() }
}
test()
// 打印结果如下
// fn2
// fn1

总结:defer

  • 语句的 执行顺序定义顺序 相反
  • 当前作用域结束之前执行


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


func divide(_ v1: Int, _ v2: Int) -> Int { 
     assert(v2 != 0, "除数不能为0")
     return v1 / v2
}
print(divide(20, 0))


  • 增加Swift Flags修改断言的默认行为
    -assert-config Release:强制关闭断言
    -assert-config Debug:强制开启断言


image.png



  • 1.9、fatalError
  • 如果遇到严重问题,希望结束程序运行时,可以直接使用fatalError函数抛出错误(这是无法通过do-catch捕捉的错误)
  • 使用了fatalError函数,就不需要再写return


func test(_ num: Int) -> Int {
    if num >= 0 {
        return 1
    }
    fatalError("num不能小于0")
}
  • 在某些不得不实现、但不希望别人调用的方法,可以考虑内部使用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.10、局部作用:可以使用 do 实现局部作用域


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


二、泛型 (Generics)



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


func swapValues<T>(_ a: inout T, _ b: inout T) { 
     (a, b) = (b, a)
}
var x1 = 10
var x2 = 20
swapValues(&x1, &x2)
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)

提示 :T 仅仅是参数的泛型名字,泛型名字定义要有意义,可以根据自己的需要定义为其他的名字


  • 2.2、泛型函数赋值给变量, T1 和  T2 仅仅代表函数 的两个参数的类型不同


func test<T1, T2>(_ t1: T1, _ t2: T2) {} 
var fn1: (Int, Double) -> () = test
var fn2: (String, Double) -> () = test
  • 2.3、泛型使用例子


// 父类
class Stack<E> {
    var elements = [E]()
    init(firstElement: E){
       elements.append(firstElement)
    }
    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 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
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 
    }
}

提示:在结构体的泛型里面,在函数里面修改变量,要加上 mutating

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")
  • 2.4、关联类型
  • 关联类型的作用:给协议中用到的类型定义一个占位名称
  • 协议中可以拥有多个关联类型


protocol Stackable {
    // 协议中可以拥有多个关联类型
    associatedtype Element // 关联类型 1
    associatedtype Element2 // 关联类型 2
    mutating func push(_ element: Element) 
    mutating func pop() -> Element
    func top() -> Element
    func size() -> Int
}

提示: associatedtype Element  关联类型  这是 swift的规定,而不能像函数、类、结构体 - >   <泛型名字>


  • 明确关联的类型, typealias Element = String,也可以省略,因为 func push(_ element: String) 函数可以识别类型


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")
  • 类设置泛型,可以如下定义,同时 typealias Element = E 可以省略


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 
     }
}
  • 2.5、对泛型进行约束
  • 通过 继承基类 或者 遵守协议 和 通过 where 语句来 进行约束


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>()
// error: requires the types 'Int' and 'String' be equivalent equal(stack1, stack2)

提示:传进来的参数泛型要是 Person 类型并且遵守Runnable协议,还有 where 判断


  • 2.6、协议类型的注意点


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) -> Runnable {
    if type == 0 {
        return Person()
    }
    return Car()
}
var r1 = get(0)
var r2 = get(1)

提示: func get(_ type: Int) -> Runnable 会报错:Protocol 'Runnable8' can only be used as a generic constraint because it has Self or associated type requirements

  • 原因:Runnable 协议里面 associatedtype 关联类型


  • 解决办法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:使用 some 关键字声明一个不透明类型


func get(_ type: Int) -> some Runnable {
     return Car()
}
var r1 = get(0)
var r2 = get(1)
  • 2.7、不透明类型
  • some 限制只能返回一种类型


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

提示:报错 Function declares an opaque return type, but the return statements in its body do not have matching underlying types


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


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


提示:我们可以看到返回的是遵守了 Runnable 协议的对象


image.png

  • 2.8、可选项的本质是 enum 类型


public enum Optional<Wrapped> : ExpressibleByNilLiteral { 
     case none
     case some(Wrapped)
     public init(_ some: Wrapped) 
}
var age: Int? = .none
age = 10
age = .some(20)
age = nil
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
switch age {
     case let v?:
         print("some", v)
     case nil:
         print("none")
}
switch age {
     case let .some(v):
         print("some", v)
     case .none:
         print("none")
}
var age: Int? = nil
var age0 = Optional<Int>.none 
var age1: Optional<Int> = .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


三、高级运算符



  • 3.1、溢出运算符
  • Swift的算术运算符出现溢出时还会抛出运行时错误
  • Swift有溢出运算符(&+、&-、&*),用来支持溢出运算
  • Int8(有符号): -128 ~ 127;UInt8(无符号):0 ~ 255
  • 在使用了溢出运算符,那么范围就变成了一个圈,如下结果:


print(Int8.max &+ 1)  // 结果是   128  &+ 1 = -128
print(Int8.min &- 1)  // 结果是   -128  &- 1 = 127

提示:使用了溢出运算符,那么范围就变成了一个圈 如:UInt8(-128~127~128~127)


  • 3.2、运算符重载
    类、结构体 、枚举 可以为现有的运算符提供自定义的实现,这个操作叫做:运算符重载


class Point {
    var x: Int
    var y: Int
    init(x: Int,y:Int) {
        self.x = x
        self.y = y
    }
    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 func += (p1: inout Point, p2: Point) {
        p1 = p1 + p2
    }
    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)
    }
}
let p1 = Point(x: 1, y: 2)
let p2 = Point(x: 10, y:20)
let p3 = p1 + p2
print(p3.x,p3.y)

提示:prefix代表前置,postfix代表后置


  • 3.3、Equatable
  • 要想得知2个实例是否等价,一般做法是遵守 Equatable 协议,重载  == 运算符;与此同时,等价于重载了 != 运算符


class Person: Equatable {
    var age: Int
    init(age: Int) {
        self.age = age
    }
    static func == (lhs: Person, rhs: Person) -> Bool {
        lhs.age == rhs.age
    }
}
  • Swift为以下类型提供默认的Equatable 实现
  • 没有关联类型的枚举


enum Car {
   case Name
   case Price
}
let car1 = Car.Name
let car2 = Car.Price
print(car1 == car2)
// false
  • 只拥有遵守 Equatable 协议关联类型的枚举


enum Answer {
    case wrong(Int,String)
    case right
}

提示:Int,String......都是遵守 Equatable 的


  • 只拥有遵守 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
  • 引用类型比较存储的地址值是否相等(是否引用着同一个对象),使用恒等运算符 ===!==


提示:===!== 用于引用类型,比较指向的内存地址是否是同一个


  • 3.4、Comparable
  • score大的比较大,若score相等,age小的比较大


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)
     } 
}
  • 要想比较2个实例的大小,一般做法是:
  • (1)、遵守 Comparable 协议
  • (2)、重载相应的运算符


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
  • 3.5、自定义运算符
  • 可以自定义新的运算符:在全局作用域使用 operator 进行声明
    prefix operator 前缀运算符
    postfix operator 后缀运算符
    infix operator 中缀运算符 : 优先级组


prefix operator +++
prefix func +++ (_ i: inout Int) {
      i +=  2
}
var  age  = 3
print(+++age)
  • 优先级组


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


提示:

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



prefix operator +++
infix operator +- : PlusMinusPrecedence 
precedencegroup PlusMinusPrecedence {
       associativity: none
       higherThan: AdditionPrecedence 
       lowerThan: MultiplicationPrecedence 
       assignment: true
}
struct Point {
    var x: Int, y: Int
    static prefix func +++ (point: inout Point) -> Point {
        point = Point(x: point.x + point.x, y: point.y + point.y)
        return point
    }
    static func +- (left: Point, right: Point) -> Point { 
        return Point(x: left.x + right.x, y: left.y - right.y)
    }
    static func +- (left: Point?, right: Point) -> Point {
        print("+-")
        return Point(x: left?.x ?? 0 + right.x, y: left?.y ?? 0 - right.y) 
    }
}
struct Person {
   var point: Point
}
var person: Person? = nil 
person?.point +- Point(x: 10, y: 20)


提示:person?.point +- Point(x: 10, y: 20)+- 等同于 =

目录
相关文章
|
1天前
|
Swift 开发者
在Swift中,错误处理
在Swift中,错误处理
27 6
|
7月前
|
Go Swift
39 如何在Swift里进行错误处理
在Swift里进行错误处理
35 0
|
1天前
|
编译器 Go Swift
【Swift开发专栏】Swift中的错误处理与异常捕获
【4月更文挑战第30天】Swift的错误处理机制通过遵循`Error`协议的类型定义和传播错误,以声明式方式捕获和处理问题。本文分为三部分:1) 错误定义与传播,包括自定义错误类型和使用`throw/try/catch`处理;2) 错误处理语法,如必须和可选捕获,以及错误传播;3) 实际应用中的最佳实践,如清晰定义错误、使用错误码和避免滥用错误处理。理解并熟练运用这些机制能提升代码的可靠性和可维护性。
|
Go Swift iOS开发
Swift5.1—错误处理
Swift5.1—错误处理
110 0
|
JSON 编译器 Swift
Swift-进阶 06:反射Mirror & 错误处理
Swift-进阶 06:反射Mirror & 错误处理
215 0
Swift-进阶 06:反射Mirror & 错误处理
《从零开始学Swift》学习笔记(Day 53)——do-try-catch错误处理模式
<div style="top: 0px;"></div> <span style="font-family:宋体;font-size:14px;"></span><p style="margin: 0cm 0cm 0pt; mso-outline-level: 1;"><span style="font-size:14px;"><strong style="mso-bidi-font-w
1654 0
|
iOS开发
《从零开始学Swift》学习笔记(Day 52)——Cocoa错误处理模式
<span style="font-family:宋体;font-size: 10.5pt; mso-ascii-font-family: Arial; mso-fareast-font-family: 宋体; mso-fareast-theme-font: minor-fareast; mso-hansi-font-family: Arial; mso-bidi-font-family: A
1886 0