美文网首页
Swift5 _09_Error处理_泛型

Swift5 _09_Error处理_泛型

作者: MR_詹 | 来源:发表于2021-02-08 06:51 被阅读0次

自定义错误

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

enum RWError : 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 抛出错误的必须是遵守Error协议
        throw RWError.illegalArg("0不能作为除数")
    }
    return num1/num2
}

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

func test() {
    print("1")
    do {
        print("2")
        /// 如果抛出异常,则在此作用域内的,后面代码不会执行,则不会打印3
        print(try divide(200, 0))
        print("3")
    } catch RWError.illegalArg(let msg) {
        print("参数异常:",msg)
    } catch RWError.outOfBounds(let size, let index) {
        print("下标越界:","size:\(size)","index=\(index)")
    } catch RWError.outOfMemory {
        print("内存溢出")
    } catch {
        print("其他错误")
    }
    print("4")
}

处理Error的2种方式
(1)通过do-catch捕捉Error
(2)不捕捉Error,在当前函数增加throws声明,Error将会自动抛给上层函数
如果顶层函数(main函数)依然没有捕捉Error,那么程序将终止

/// 添加throws 往上抛Error
func test() throws {
       print("1")
       print(try divide(20, 0))
       print("2")
}
/// 如果Error是RWError的,则捕获并处理
/// 如果Error不是RWError的,则继续往上抛出异常,使用test函数还需要加上throws
func test() throws {
 print("1")
 do {
     print("2")
     print(try divide(20, 0))
      print("3")
  }catch let error as RWError {
     print(error)
   }
   print("4")
 }
/// 也是判断Error是否为RWError,如果是,则捕获并处理
/// 如果不是则继续往上层抛出异常
func test() throws { 
   do {
      print(try divide(20, 0))
   }catch is RWError {
     print("RWError")
  }
} 
/// 或者可以这样处理,添加一个catch处理其他的错误,这样就不需要继续往上抛异常
/// 只要能将所有的Error情况都处理了,那么就不需要往上抛异常
func test() {
   do {
       print(try divide(20, 0))
   }catch is RWError {
       print("RWError")
   }catch {
       print("其他错误")
   }
}

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

/// try? : 如果没有抛出异常,那么就会将结果包成可选项
/// 如果抛出异常了,那么就返回nil
func test() {
   print("1")
   var result1 = try? divide(20, 10)
   print(result1)      /// Optional(2), Int?
   var result2 = try? divide(20, 0)
   print(result2)      /// nil
   var result3 = try! divide(20, 10)
   print(result3)      /// 2, Int
   print("2")
}

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

/// exec函数本身并不会抛出错误
/// 此处的错误,是由于闭包参数fn导致的
func exec(_ fn: (Int,Int) throws -> Int, _ num1:Int, _ num2:Int) rethrows {
      print(try fn(num1,num2))
}
    
try exec(divide,20,0)

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

/// 打开文件
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定义的函数
  defer {
     close(file)
  }
   try divide(20, 0)
}
    
 try processFile("test.txt")
/// open
/// close
/// Fatal error: 

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

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

泛型

泛型可以将类型参数化,提供代码复用率,减少代码量
结构体、枚举、类都是可以使用泛型
协议不能使用泛型,但是可以通过关联类型达到相同的效果

更抽象,扩展性更好

泛型: 方法

以下实例是一个交互两个整形变量值的方法,是没有什么问题的,但如果还想实现两个浮点型变量、字符串变量再或者其他类型变量的交互了? 那要怎么做?

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        var n1 = 10
        var n2 = 20
        swapValue(&n1, &n2)
        print(n1)
        print(n2)
    }
    
    /// 交互两个整形变量的值
    func swapValue(_ a: inout Int , _ b: inout Int) {
        (a,b) = (b,a)
    }
}

当然可以通过重载的方式完成,但是不够优雅,代码量多

# 重载方法
/// 交互两个浮点变量的值
func swapValue(_ a: inout Float , _ b: inout Float) {
    (a,b) = (b,a)
}
/// 交互两个字符串变量的值
func swapValue(_ a: inout String , _ b: inout String) {
   (a,b) = (b,a) 
}

最佳实现方式: 泛型

/// T :代表一种不确定的类型(当然除了可以用T,也可以用其他来表示,一般用T表示,T相当于是Type的缩写)
/// 一个方法就可以实现多种数据类型的兼容,只要传入的参数a和b两种的类型T都是同一种就没问题
func swapValue<T>(_ a: inout T , _ b: inout T) {
   (a,b) = (b,a)
}

提醒:
方法添加泛型,调用有个注意的地方,请看如下示例

var n1 = 10
var n2 = 20
/// 正确的方式
swapValue(&n1, &n2)
/// 错误的方式
swapValue<Int>(&n1, &n2)

#解析:
错误的方式,为什么会报错了?
这是因为泛型T的类型在调用方法传参的时候就已经确定下来的,不需要再添加声明,
这与类、结构体、枚举添加泛型时,是不一样的(详情请继续往下请)

带泛型的函数类型变量

func swapValue<T>(_ a: inout T , _ b: inout T) {
   (a,b) = (b,a)
}
    
func sum(_ a: Int , _ b: Int) -> Int {
    a + b
}


/// 没有添加泛型的方法,可以这样
let fn = sum
fn(10,20)
        
/// 但是添加了泛型的方法,不能这样
/// 因为定义变量sfn的时候,不能确定泛型的类型,所以会报错
/// let sfn = swapValue
/// 正确的方式
let sfn : (inout Int,inout Int)->() = swapValue
sfn(&n1,&n2)

多个泛型

/// 比如要输入两个变量,这个变量的类型可能不一样
/// 这时候就要声明两个泛型T 和 E,分明代表一种类型,当然也可以是相同的类型
/// T 和 E 的类型,取决于方法传入参数a 和 b 是什么类型
func printTwoValue<T,E>(_ a: T, _ b: E) {
   print("参数一:",a)
   print("参数二:",b)
}

泛型:类

class Stack<Int> {
    var elements = [Int]()
    func push(_ element: Int) {
        elements.append(element)
    }
    func pop() -> Int {
        elements.removeLast()
    }
    func top() -> Int {
        elements.last!
    }
    func size() -> Int {
        elements.count as! Int
    }
}


使用泛型将类型Int参数化,进行扩展,最后结果如下

/// 这样Stack类中,存放的类型就可以根据存储的时候决定
/// 可以是Int、String、float等等
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 as! Int
    }
}


/// 使用
/// 因为类在初始化的时候并不能推断出泛型E的类型
/// 所以必须声明"E"的类型
var intStack = Stack<Int>()
/// 一旦确定了E的类型为Int,那么就只能是存放Int类型,不能添加其他的类型
intStack.push(1)

var stringStack = Stack<String>()
stringStack.push("1")

/// 当然如果想要放任意类型的话,可以使用Any
var anyStack = Stack<Any>()

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
    }
}


/// 如果在初始化的时候,可以推断出泛型E的类型
/// 那么就可以省略<E>,当然写也是没问题的
var stack = Stack(firstElement: 10)
 

带有泛型的类:继承

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> {

}

泛型: 结构体

struct Stack<E> {
    var elements = [E]()
    /// 因为这两个方法,都会改变结构体struct的内存结构,所以需要使用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 Score<T> {
    case point(T)
    case grade(String)
}

let score0 = Score<Int>.point(100)
/// T 为 整形
let score1 = Score.point(100)
/// T 为 浮点类型
let score2 = Score.point(100.0)
/// 如果是通过.grade定义一个枚举变量,那么一定要加上<Int>
/// 这样才能确定T的类型
let score3 = Score<Int>.grade("A")

关联类型 Associated Type

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

protocol Stackable {
    /// 关联类型
    /// Element 作用跟定义泛型一样
    associatedtype Element
    /// 可以有多个关联类型
    /// associatedtype Element2
    /// 这里添加mutating关键字,是为了结构体也遵守协议的情况
    mutating func push(_ element: Element)
    mutating func pop() -> Element
    func top() -> Element
    func size() -> Int
}
class StringStack: Stackable {
    /// 给关联类型设定真实类型
    /// 这里可以省略,因为在实现协议Stackable方法时已经确定了关联类型的值
    /// 比如:push方法,已经确定关联类型Element 是 String 类型
    /// 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
    }
}
# 泛型与关联类型使用
/// 这样相当于将泛型 E 与 关联类型Element 绑定在一起
/// 只要泛型E确定,那么关联类型Element也是可以确定的,所以不会报错
class StackType<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
    }
}

类型约束

泛型 添加 类型约束

protocol Runnable { }
class Person { }

/// : Person & Runnable 就是类型约束
/// 规定泛型T,必须是继承 Person,并且遵守Runnable协议
func swapValues<T: Person & Runnable>(_ a: inout T, _ b: inout T) {
    (a,b) = (b,a)
}

关联类型 添加 类型约束

protocol Stackable {
    /// 关联类型 必须要遵守 Equatable 协议
    associatedtype Element : Equatable
}

/// 规定:泛型也遵守Equatable协议
/// 这样才能让泛型E 与 关联类型Element 对应上
class StackType<E : Equatable>: Stackable {
    typealias Element = E
}
# 实例(复杂的)
/// 设置两个泛型S1, S2, 并且都遵守协议Stackable
/// where S1.Element == S2.Element, S1.Element:Hashable
/// 1、要求S1的关联类型 与 S2的关联类型要一致
/// 2、要求S1的关联类型 遵守 协议Hashable(系统定义的协议)
func equal<S1:Stackable,S2:Stackable>(_ s1:S1,_ s2:S2) -> Bool
    where S1.Element == S2.Element, S1.Element:Hashable{
    return false
}

不透明类型(Opaque Type)

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 }
}

// 这里编译器会报错
// 因为编译器不能确定返回Runnable协议中关联类型是Double 还是 Int
func get(_ type: Int) -> Runnable {
    if type == 0 {
        return Person()
    }
    return Car()
}



// 第一种解决方案: 泛型
// 但是不太灵活,并且容易出错
// 比如在使用的时候,要声明返回的是什么类型
// var r1: Person  = get(0)
// 这相对就是说get方法的泛型T就是Person,那么不论get方法参数type是0还是其他的,返回的类型都是Person
// 但是在type  != 0 时 , 实际返回的是Car, 这就导致强制转换失败报错
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 有个限制: 只能是返回一种类型
/// 可能会有人觉得这里干嘛不直接返回一个Car类型,搞得这么麻烦
/// 返回Runnable对象,能够屏蔽内部具体的实现类,避免外部对实现类的依赖
/// 即使日后要修改内部的实现类型,也不会对外部造成影响
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()
    }
}

相关文章

  • Swift5 _09_Error处理_泛型

    自定义错误 Swift中可以通过Error协议自定义运行时的错误信息 函数内部通过throw抛出自定义Error,...

  • 2021-12-01

    swift5基本语法-泛型函数和泛型类型 Swift中泛型可以将类型参数化,提高代码复用率,减少代码量。 一、泛型...

  • Java泛型—Java语法糖,只在编译有作用,编译后擦出泛型

    Java泛型—Java语法糖,只在编译有作用,编译后擦出泛型 在代码进入和离开的边界处,会处理泛型 Java泛型作...

  • Java基础---泛型

    泛型概述以及泛型类 泛型就是类型参数化,处理的数据类型不是固定的,而是可以作为参数传入; 泛型的核心: 告诉编译器...

  • Swift5 基础(五)协议、错误处理、泛型

    Swift5 基础教程与进阶合集[https://www.jianshu.com/p/7d0506aeef29] ...

  • Error处理,泛型

    swift系列课程 自定义错误 do-catch 处理Error 处理rethrows 处理defer 泛型 关联...

  • spring 泛型处理

    java 泛型基础 泛型类型:泛型类型是在类型上参数化的泛型类或接口 泛型使用场景编译时前类型检查。定义为 Col...

  • Swift泛型笔记

    泛型可以让代码处理类型更加灵活,在某些场景下可以很大程度的重用代码,泛型是什么,使用泛型的好处,这里不多说,网上有...

  • 泛型

    一、泛型的概念 泛型就是:类型参数化,处理的数据类型不是固定的,而是可以作为参数传入;疯狂讲义定义:泛型就是允许在...

  • Java中的泛型

    一 泛型是什么泛型最精准的定义:参数化类型。具体点说就是处理的数据类型不是固定的,而是可以作为参数传入。定义泛型类...

网友评论

      本文标题:Swift5 _09_Error处理_泛型

      本文链接:https://www.haomeiwen.com/subject/ycomtltx.html