经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
Swift 进阶(十)错误处理、泛型
来源:cnblogs  作者:FunkyRay  时间:2021/4/12 9:50:48  对本文有异议

错误处理

错误类型

开发过程中常见的错误有

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

自定义错误

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

  1. enum SomeError: Error {
  2. case illegalArg(String)
  3. case outOffBounds(Int, Int)
  4. case outOfMemory
  5. }

函数内部通过throw抛出自定义Error,可能会抛出Error的函数必须加上throws声明

  1. func divide(_ num1: Int, _ num2: Int) throws -> Int {
  2. if num2 == 0 {
  3. throw SomeError.illegalArg("0不能作为除数")
  4. }
  5. return num1 / num2
  6. }

需要使用try调用可能会抛出Error的函数

  1. var result = try divide(20, 10)

抛出错误信息的情况

-w715

do—catch

可以使用do—catch捕捉Error

  1. do {
  2. try divide(20, 0)
  3. } catch let error {
  4. switch error {
  5. case let SomeError.illegalArg(msg):
  6. print("参数错误", msg)
  7. default:
  8. print("其他错误")
  9. }
  10. }

抛出Error后,try下一句直到作用域结束的代码都将停止运行

  1. func test() {
  2. print("1")
  3. do {
  4. print("2")
  5. print(try divide(20, 0)) // 这句抛出异常后面的代码不会执行了
  6. print("3")
  7. } catch let SomeError.illegalArg(msg) {
  8. print("参数异常:", msg)
  9. } catch let SomeError.outOffBounds(size, index) {
  10. print("下标越界:", "size=\(size)", "index=\(index)")
  11. } catch SomeError.outOfMemory {
  12. print("内存溢出")
  13. } catch {
  14. print("其他错误")
  15. }
  16. print("4")
  17. }
  18. test()
  19. //1
  20. //2
  21. //参数异常: 0不能作为除数
  22. //4

catch作用域内默认就有error的变量可以捕获

  1. do {
  2. try divide(20, 0)
  3. } catch {
  4. print(error)
  5. }

处理Error

处理Error的两种方式

通过do—catch捕捉Error

  1. do {
  2. print(try divide(20, 0))
  3. } catch is SomeError {
  4. print("SomeError")
  5. }

不捕捉Error,在当前函数增加throws声明,Error将自动抛给上层函数

如果最顶层函数main函数依然没有捕捉Error,那么程序将终止

  1. func test() throws {
  2. print("1")
  3. print(try divide(20, 0))
  4. print("2")
  5. }
  6. try test()
  7. // 1
  8. // Fatal error: Error raised at top level

调用函数如果是写在函数里面,没有进行捕捉Error就会报错,而写在外面就不会

-w648

然后我们加上do-catch发现还是会报错,因为捕捉Error的处理不够详细,要捕捉所有Error信息才可以

-w639

这时我们加上throws就可以了

  1. func test() throws {
  2. print("1")
  3. do {
  4. print("2")
  5. print(try divide(20, 0))
  6. print("3")
  7. } catch let error as SomeError {
  8. print(error)
  9. }
  10. print("4")
  11. }
  12. try test()
  13. // 1
  14. // 2
  15. // illegalArg("0不能作为除数")
  16. // 4

或者再加上一个catch捕获其他所有Error情况

  1. func test() {
  2. print("1")
  3. do {
  4. print("2")
  5. print(try divide(20, 0))
  6. print("3")
  7. } catch let error as SomeError {
  8. print(error)
  9. } catch {
  10. print("其他错误情况")
  11. }
  12. print("4")
  13. }
  14. test()

看下面示例代码,执行后会输出什么

  1. func test0() throws {
  2. print("1")
  3. try test1()
  4. print("2")
  5. }
  6. func test1() throws {
  7. print("3")
  8. try test2()
  9. print("4")
  10. }
  11. func test2() throws {
  12. print("5")
  13. try test3()
  14. print("6")
  15. }
  16. func test3() throws {
  17. print("7")
  18. try divide(20, 0)
  19. print("8")
  20. }
  21. try test0()

执行后打印如下,并会抛出错误信息

-w717

try

可以使用try?、try!调用可能会抛出Error的函数,这样就不用去处理Error

  1. func test() {
  2. print("1")
  3. var result1 = try? divide(20, 10) // Optional(2), Int?
  4. var result2 = try? divide(20, 0) // nil
  5. var result3 = try! divide(20, 10) // 2, Int
  6. print("2")
  7. }
  8. test()

a、b是等价的

  1. var a = try? divide(20, 0)
  2. var b: Int?
  3. do {
  4. b = try divide(20, 0)
  5. } catch { b = nil }

rethrows

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

  1. func exec(_ fn: (Int, Int) throws -> Int, _ num1: Int, _ num2: Int) rethrows {
  2. print(try fn(num1, num2))
  3. }
  4. // Fatal error: Error raised at top level
  5. try exec(divide, 20, 0)

空合并运算符就是用了rethrows来进行声明的

-w609

defer

defer语句,用来定义以任何方式(抛错误、return等)离开代码块前必须要执行的代码

defer语句将延迟至当前作用域结束之前执行

  1. func open(_ filename: String) -> Int {
  2. print("open")
  3. return 0
  4. }
  5. func close(_ file: Int) {
  6. print("close")
  7. }
  8. func processFile(_ filename: String) throws {
  9. let file = open(filename)
  10. defer {
  11. close(file)
  12. }
  13. // 使用file
  14. // .....
  15. try divide(20, 0)
  16. // close将会在这里调用
  17. }
  18. try processFile("test.txt")
  19. // open
  20. // close
  21. // Fatal error: Error raised at top level

defer语句的执行顺序与定义顺序相反

  1. func fn1() { print("fn1") }
  2. func fn2() { print("fn2") }
  3. func test() {
  4. defer { fn1() }
  5. defer { fn2() }
  6. }
  7. test()
  8. // fn2
  9. // fn1

assert(断言)

很多编程语言都有断言机制,不符合指定条件就抛出运行时错误,常用于调试Debug阶段的条件判断

-w716

默认情况下,Swift的断言只会在Debug模式下生效,Release模式下会忽略

增加Swift Flags修改断言的默认行为

  • -assert-config Release:强制关闭断言
  • -assert-config Debug:强制开启断言

-w716

fatalError

如果遇到严重问题,希望结束程序运行时,可以直接使用fatalError函数抛出错误

这是无法通过do—catch捕获的错误

使用了fatalError函数,就不需要再写return

  1. func test(_ num: Int) -> Int {
  2. if num >= 0 {
  3. return 1
  4. }
  5. fatalError("num不能小于0")
  6. }

在某些不得不实现,但不希望别人调用的方法,可以考虑内部使用fatalError函数

  1. class Person { required init() {} }
  2. class Student: Person {
  3. required init() {
  4. fatalError("don't call Student.init")
  5. }
  6. init(score: Int) {
  7. }
  8. }
  9. var stu1 = Student(score: 98)
  10. var stu2 = Student()

局部作用域

可以使用do实现局部作用域

  1. do {
  2. let dog1 = Dog()
  3. dog1.age = 10
  4. dog1.run()
  5. }
  6. do {
  7. let dog2 = Dog()
  8. dog2.age = 10
  9. dog2.run()
  10. }

泛型(Generics)

基本概念

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

  1. func swapValues<T>(_ a: inout T, _ b: inout T) {
  2. (a, b) = (b, a)
  3. }
  4. var i1 = 10
  5. var i2 = 20
  6. swap(&i1, &i2)
  7. print(i1, i2) // 20, 10
  8. struct Date {
  9. var year = 0, month = 0, day = 0
  10. }
  11. var d1 = Date(year: 2011, month: 9, day: 10)
  12. var d2 = Date(year: 2012, month: 10, day: 20)
  13. swap(&d1, &d2)
  14. print(d1, d2) // Date(year: 2012, month: 10, day: 20), Date(year: 2011, month: 9, day: 10)

泛型函数赋值给变量

  1. func test<T1, T2>(_ t1: T1, _ t2: T2) {}
  2. var fn: (Int, Double) -> () = test

泛型类型

  1. class Stack<E> {
  2. var elements = [E]()
  3. func push(_ element: E) {
  4. elements.append(element)
  5. }
  6. func pop() -> E {
  7. elements.removeLast()
  8. }
  9. func top() -> E {
  10. elements.last!
  11. }
  12. func size() -> Int {
  13. elements.count
  14. }
  15. }
  16. class SubStack<E>: Stack<E> {
  17. }
  18. var intStack = Stack<Int>()
  19. var stringStack = Stack<String>()
  20. var anyStack = Stack<Any>()
  1. struct Stack<E> {
  2. var elements = [E]()
  3. mutating func push(_ element: E) {
  4. elements.append(element)
  5. }
  6. mutating func pop() -> E {
  7. elements.removeLast()
  8. }
  9. func top() -> E {
  10. elements.last!
  11. }
  12. func size() -> Int {
  13. elements.count
  14. }
  15. }
  16. var stack = Stack<Int>()
  17. stack.push(11)
  18. stack.push(22)
  19. stack.push(33)
  20. print(stack.top()) // 33
  21. print(stack.pop()) // 33
  22. print(stack.pop()) // 22
  23. print(stack.pop()) // 11
  24. print(stack.size()) // 0
  1. enum Score<T> {
  2. case point(T)
  3. case grade(String)
  4. }
  5. let score0 = Score<Int>.point(100)
  6. let score1 = Score.point(99)
  7. let score2 = Score.point(99.5)
  8. let score3 = Score<Int>.grade("A")

关联类型(Associated Type)

关联类型的作用:给协议中用到的类型定义一个占位名称

  1. protocol Stackable {
  2. associatedtype Element
  3. mutating func push(_ element: Element)
  4. mutating func pop() -> Element
  5. func top() -> Element
  6. func size() -> Int
  7. }
  8. struct Stack<E>: Stackable {
  9. var elements = [E]()
  10. mutating func push(_ element: E) {
  11. elements.append(element)
  12. }
  13. mutating func pop() -> E {
  14. elements.removeLast()
  15. }
  16. func top() -> E {
  17. elements.last!
  18. }
  19. func size() -> Int {
  20. elements.count
  21. }
  22. }
  23. class StringStack: Stackable {
  24. var elements = [String]()
  25. func push(_ element: String) {
  26. elements.append(element)
  27. }
  28. func pop() -> String {
  29. elements.removeLast()
  30. }
  31. func top() -> String {
  32. elements.last!
  33. }
  34. func size() -> Int {
  35. elements.count
  36. }
  37. }
  38. var ss = StringStack()
  39. ss.push("Jack")
  40. ss.push("Rose")

协议中可以拥有多个关联类型

  1. protocol Stackable {
  2. associatedtype Element
  3. associatedtype Element2
  4. mutating func push(_ element: Element)
  5. mutating func pop() -> Element
  6. func top() -> Element
  7. func size() -> Int
  8. }

类型约束

  1. protocol Runnable { }
  2. class Person { }
  3. func swapValues<T: Person & Runnable>(_ a: inout T, _ b: inout T) {
  4. (a, b) = (b, a)
  5. }
  1. protocol Stackable {
  2. associatedtype Element: Equatable
  3. }
  4. class Stack<E: Equatable>: Stackable {
  5. typealias Element = E
  6. }
  7. func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element : Hashable {
  8. return false
  9. }
  10. var stack1 = Stack<Int>()
  11. var stack2 = Stack<String>()
  12. equal(stack1, stack2)

协议类型的注意点

看下面的示例代码来分析

  1. protocol Runnable { }
  2. class Person: Runnable { }
  3. class Car: Runnable { }
  4. func get(_ type: Int) -> Runnable {
  5. if type == 0 {
  6. return Person()
  7. }
  8. return Car()
  9. }
  10. var r1 = get(0)
  11. var r2 = get(1)

如果协议中有associatedtype

  1. protocol Runnable {
  2. associatedtype Speed
  3. var speed: Speed { get }
  4. }
  5. class Person: Runnable {
  6. var speed: Double { 0.0 }
  7. }
  8. class Car: Runnable {
  9. var speed: Int { 0 }
  10. }

这样写会报错,因为无法在编译阶段知道Speed的真实类型是什么

-w638

可以用泛型来解决

  1. protocol Runnable {
  2. associatedtype Speed
  3. var speed: Speed { get }
  4. }
  5. class Person: Runnable {
  6. var speed: Double { 0.0 }
  7. }
  8. class Car: Runnable {
  9. var speed: Int { 0 }
  10. }
  11. func get<T: Runnable>(_ type: Int) -> T {
  12. if type == 0 {
  13. return Person() as! T
  14. }
  15. return Car() as! T
  16. }
  17. var r1: Person = get(0)
  18. var r2: Car = get(1)

还可以使用some关键字声明一个不透明类型

some限制只能返回一种类型

  1. protocol Runnable {
  2. associatedtype Speed
  3. var speed: Speed { get }
  4. }
  5. class Person: Runnable {
  6. var speed: Double { 0.0 }
  7. }
  8. class Car: Runnable {
  9. var speed: Int { 0 }
  10. }
  11. func get(_ type: Int) -> some Runnable {
  12. return Car()
  13. }
  14. var r1 = get(0)
  15. var r2 = get(1)

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

  1. protocol Runnable {
  2. associatedtype Speed
  3. }
  4. class Dog: Runnable {
  5. typealias Speed = Double
  6. }
  7. class Person {
  8. var pet: some Runnable {
  9. return Dog()
  10. }
  11. }

泛型的本质

我们通过下面的示例代码来分析其内部具体是怎样实现的

  1. func swapValues<T>(_ a: inout T, _ b: inout T) {
  2. (a, b) = (b, a)
  3. }
  4. var i1 = 10
  5. var i2 = 20
  6. swap(&i1, &i2)
  7. print(i1, i2) // 20, 10
  8. var d1 = 10.0
  9. var d2 = 20.0
  10. swap(&d1, &d2)
  11. print(d1, d2) // 20.0, 10.0

反汇编之后

-w1000
-w1002

从调用两个交换方法来看,最终调用的都是同一个函数,因为函数地址是一样的;但不同的是分别会将Int的metadataDouble的metadata作为参数传递进去,所以推测metadata中应该分别指明对应的类型来做处理

可选项的本质

可选项的本质是enum类型

我们可以进到头文件中查看

-w1034

我们平时写的语法糖的真正写法如下

  1. var age: Int? = 10
  2. 本质写法如下:
  3. var ageOpt0: Optional<Int> = Optional<Int>.some(10)
  4. var ageOpt1: Optional = .some(10)
  5. var ageOpt2 = Optional.some(10)
  6. var ageOpt3 = Optional(10)
  1. var age: Int? = nil
  2. 本质写法如下:
  3. var ageOpt0: Optional<Int> = .none
  4. var ageOpt1 = Optional<Int>.none
  1. var age: Int? = .none
  2. age = 10
  3. age = .some(20)
  4. age = nil

switch中可选项的使用

  1. switch age {
  2. case let v?: // 加上?表示如果有值会解包赋值给v
  3. print("some", v)
  4. case nil:
  5. print("none")
  6. }
  7. switch age {
  8. case let .some(v):
  9. print("some", v)
  10. case .none:
  11. print("none")
  12. }

多重可选项

  1. var age_: Int? = 10
  2. var age: Int?? = age_
  3. age = nil
  4. var age0 = Optional.some(Optional.some(10))
  5. age0 = .none
  6. var age1: Optional<Optional> = .some(.some(10))
  7. age1 = .none
  1. var age: Int?? = 10
  2. var age0: Optional<Optional> = 10

原文链接:http://www.cnblogs.com/funkyRay/p/swift-jin-jie-shi-cuo-wu-chu-li-fan-xing.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号