一、错误处理
- 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
:强制开启断言
- 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 协议的对象
- 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代表在可选链操作中拥有跟赋值运算符一样的优先级
- Apple文档:运算符参考
- 自定义 运算符实例
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)
的+-
等同于=