自定义错误
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()
}
}
网友评论