錯(cuò)誤處理(異常處理)
錯(cuò)誤類型
開發(fā)過程中常見的錯(cuò)誤:
- 語法錯(cuò)誤(編譯報(bào)錯(cuò))
- 邏輯錯(cuò)誤 (偏離開發(fā)人員本意)
- 運(yùn)行時(shí)錯(cuò)誤(可能會(huì)閃退妥畏,一般也叫做異常)
...
自定義錯(cuò)誤
- Swift可以通過Error協(xié)議自定義運(yùn)行時(shí)的錯(cuò)誤信息
- 函數(shù)內(nèi)部通過throw拋出自定義Error,可能會(huì)拋出Error的函數(shù)必須加上throws申明
- 需要使用try調(diào)用可能會(huì)拋出Error的函數(shù)
enum MyError :Error {
case illegalArt(String)
}
func divide(_ num1: Int,_ num2: Int) throws -> Int{
if num2 == 0 {
throw MyError.illegalArt("0不能作為除數(shù)")
}
return num1 / num2
}
var result = try divide(20, 0)
print(result)
do-catch捕捉Error
- 使用 do-catch 捕捉Error
- 拋出Error后安吁,try下一句直到作用域結(jié)束的代碼都將停止運(yùn)行
enum MyError :Error {
case illegalArt(String)
case outOfBounds(Int, Int)
case outOfMemory
}
func divide(_ num1: Int,_ num2: Int) throws -> Int{
if num2 == 0 {
throw MyError.illegalArt("0不能作為除數(shù)")
}
return num1 / num2
}
func test() throws {
print("1")
do {
print("2")
print(try divide(20, 0))
print("3")
} catch let MyError.illegalArt(tip) {
print("參數(shù)異常:",tip)
}catch let MyError.outOfBounds(size, index){
print("下標(biāo)越界異常:size = \(size),index = \(index)")
}catch let MyError.outOfMemory{
print("內(nèi)存溢出")
}catch{
print("其他錯(cuò)誤")
}
}
try test()
/**
輸出:
1
2
參數(shù)異常: 0不能作為除數(shù)
*/
處理Error的2種方式
- 通過do-catch捕捉Error
- 不捕捉Error醉蚁,在當(dāng)前函數(shù)增加throws聲明,Error將自動(dòng)上拋給上層函數(shù)
-
如果最頂層函數(shù)(main函數(shù))依然沒有捕捉Error,那么程序?qū)⒔K止
enum MyError :Error {
case illegalArt(String)
case outOfBounds(Int, Int)
case outOfMemory
}
enum NewError :Error{
case unKnownError(String)
}
func divide(_ num1: Int,_ num2: Int) throws -> Int{
if num2 == 0 {
throw MyError.illegalArt("0不能作為除數(shù)")
}
if num2 == 1 {
throw NewError.unKnownError("任何數(shù)除以1結(jié)果都是其本身")
}
return num1 / num2
}
func test() throws {//第3鬼店、4個(gè)do-catch錯(cuò)誤處理沒有窮盡网棍,所以需要throws上拋 如果處理詳盡,可以無需throws
do{
print(try divide(20, 0))
} catch let error as MyError {//強(qiáng)制類型轉(zhuǎn)換成功
print(error)
}catch{
print("其他錯(cuò)誤")
}
do{
print(try divide(20, 1))
} catch let error as MyError {//強(qiáng)制類型轉(zhuǎn)換失敗
print(error)
}catch{
print("其他錯(cuò)誤")
}
do{
print(try divide(20, 0))
} catch is MyError {//判斷錯(cuò)誤類型
print("MyError")
}
do{
print(try divide(20, 1))
} catch is MyError {//此處無法處理Error上拋 程序終止
print("MyError")
}
}
try test()
/*輸出:
illegalArt("0不能作為除數(shù)")
其他錯(cuò)誤
MyError
Fatal error: Error raised at top level: example.NewError.unKnownError("任何數(shù)除以1結(jié)果都是其本身"):
*/
第3妇智、4個(gè)do-catch錯(cuò)誤處理沒有窮盡确沸,所以需要throws上拋 如果處理詳盡,可以無需throws
try?俘陷、 try!
- 可以使用try?、try!調(diào)用可能會(huì)拋出Error的函數(shù)观谦,這樣就不用去處理Error
func test(){
var result1 = try? divide(20, 10)//Optional(2),Int?
var result2 = try? divide(20, 0)//nil
var result3 = try! divide(20, 10)//2, Int
}
test()
//下面a與b完全等價(jià)
var a = try? divide(20, 0)
var b: Int?
do {
b = try divide(20, 0)
}catch{
}
rethrows
- rethrows表明:函數(shù)本身不會(huì)拋出錯(cuò)誤拉盾,但調(diào)用閉包參數(shù)拋出錯(cuò)誤,那么它將錯(cuò)誤向上拋
-
??空合運(yùn)算符就是這么聲明的
var fn = { (a: Int,b: Int) throws -> Int in
a / b
}//閉包表達(dá)式
func exec(_ fn:(Int, Int) throws -> Int, _ num1:Int, _ num2: Int) rethrows {
print(try fn(num1,num2))
}
try exec(fn, 10, 20)
try exec(divide(_:_:), 10, 20)
defer
- defer語句:用來定義以任何方式(拋錯(cuò)誤豁状,return等)離開代碼塊前必須要執(zhí)行的代碼
- defer語句將在延遲至當(dāng)前作用域結(jié)束之前執(zhí)行
- defer語句的執(zhí)行順序捉偏,與定義順序相反
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
//...
do {
try divide(20,0)
} catch let error {
switch error {
case let MyError.illegalArt(tip):
print(tip)
default:
print("其他錯(cuò)誤")
}
}
//close將會(huì)在此處調(diào)用
}
try processFile("LOL.txt")
/**輸出:
open
0不能作為除數(shù)
close
*/
func fn1() {
print("fn1")
}
func fn2() {
print("fn2")
}
func test() {
defer {
fn1()
}
defer {
fn2()
}
}
test()
/*輸出:
fn2
fn1
*/
斷言assert
- 很多編程語言都有斷言機(jī)制:不符合指定條件就拋出運(yùn)行時(shí)錯(cuò)誤倒得,常用于調(diào)試(Debug)階段的條件判斷
- 默認(rèn)條件下,Swift的斷言只會(huì)在Debug模式下生效夭禽,Release模式下忽略
- 增加Swift Flags修改斷言的默認(rèn)行為
- -assert-config Release 強(qiáng)制關(guān)閉斷言
- -assert-config Debug 強(qiáng)制開啟斷言
fatalError
- 如果遇到嚴(yán)重問題霞掺,希望結(jié)束程序運(yùn)行時(shí),可以直接使用fatalError函數(shù)拋出錯(cuò)誤(該錯(cuò)誤無法通過do-catch捕捉)
- 使用fatalError函數(shù)讹躯,就不需要再寫return
- 在某些不得不實(shí)現(xiàn)菩彬,但不希望別人調(diào)用的方法,可以考慮內(nèi)部使用fatalError函數(shù)
局部作用域
- 可以使用do 實(shí)現(xiàn)局部作用域
泛型(Generics)
泛型
泛型可以將類型參數(shù)化潮梯,提高代碼復(fù)用率骗灶,減少代碼量
//將類型參數(shù)化
func swapValues<T>(_ a: inout T , _ b: inout T){
(a,b) = (b,a)
}
var v1 = 10
var v2 = 20
swap(&v1, &v2)//v1:20 v2: 10
var a1 = 10.5
var a2 = 20.6
swap(&a1, &a2)//v1:20.6 v2: 10.5
struct Date {
var year = 0
var month = 0
var day = 0
}
var date1 = Date(year: 2019, month: 12, day: 31)
var date2 = Date(year: 2020, month: 01, day: 01)
swap(&date1, &date2)
//date1:Date(year: 2020, month: 1, day: 1)
//date2:Date(year: 2019, month: 12, day: 31)
//泛型函數(shù)賦值給變量
func test<T1,T2>(_ a: T1, _ b: T2) {
}
var fn :(Int, Double)->() = test(_:_:)//此時(shí)確定參數(shù)類型
//類
class Stack <Element>{
var elements = [Element]()
func push(_ element:Element) {
elements.append(element)
}
func pop() -> Element {
elements.removeLast()
}
func top() -> Element {
elements.last!
}
func size() -> Int {
elements.count
}
}
//類繼承
class SubStack<Element> : Stack<Element> {
}
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.size())//1
//結(jié)構(gòu)體
struct Stack <Element>{
var elements = [Element]()
//結(jié)構(gòu)體、枚舉中修改自身內(nèi)存必須要加mutating
mutating func push(_ element:Element) {
elements.append(element)
}
mutating func pop() -> Element {
elements.removeLast()
}
func top() -> Element {
elements.last!
}
func size() -> Int {
elements.count
}
}
//在類秉馏、結(jié)構(gòu)體耙旦、枚舉的初始化器中,如已添加元素萝究,則根據(jù)元素類型自動(dòng)判斷類型 無需指明類型(即使加上也不會(huì)錯(cuò))
var stack = Stack<Double>(elements: [10])//10可以賦值給Double類型
stack.push(11)
stack.push(22)
stack.push(33)
print(stack.top())//33
print(stack.pop())//33
print(stack.pop())//22
print(stack.size())//2
//枚舉
enum Score <T> {
case point(T)
case grade(String)
}
let score1 = Score.point(100)
let score2 = Score<Int>.point(95)
let score3 = Score.point(99.5)
let score4 = Score<Int>.grade("A")//此處聲明的類型為point的泛型類型 需要確定枚舉類型分配內(nèi)存
print(score1,score2,score3,score4)
關(guān)聯(lián)類型(Associated Type)
- 作用:給協(xié)議中用到的類型定義一個(gè)占位名稱
- 協(xié)議中可以擁有多個(gè)關(guān)聯(lián)類型
protocol Stackable {
associatedtype Element//關(guān)聯(lián)類型
mutating func push(_ element:Element)
mutating func pop() -> Element
func top() -> Element
func size() -> Int
}
//給關(guān)聯(lián)類型設(shè)定真實(shí)類型
class Stack : Stackable{
typealias Element = Int
//或者給所有的用到的Element換為String
var elements = [Element]()
//結(jié)構(gòu)體免都、枚舉中修改自身內(nèi)存必須要加mutating
func push(_ element:Element) {
elements.append(element)
}
func pop() -> Element {
elements.removeLast()
}
func top() -> Element {
elements.last!
}
func size() -> Int {
elements.count
}
}
//使用泛型
class Stack <E>: Stackable{
var elements = [E]()
//結(jié)構(gòu)體、枚舉中修改自身內(nèi)存必須要加mutating
func push(_ element:E) {
elements.append(element)
}
func pop() -> E {
elements.removeLast()
}
func top() -> E {
elements.last!
}
func size() -> Int {
elements.count
}
}
類型約束
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
}//S1 S2必須遵守Stackable 并且S1和S2的關(guān)聯(lián)類型相同 且S1的管理按類型是遵守Hashable協(xié)議
var s1 = Stack<Int>()
var s2 = Stack<Int>()
var s3 = Stack<String>()
equal(s1, s2)//編譯通過
equal(s1, s3)//編譯報(bào)錯(cuò) function 'equal' requires the types 'Int' and 'String' be equivalent
協(xié)議類型的注意點(diǎn)
- 如果協(xié)議中有associatedtype 會(huì)下面的報(bào)錯(cuò)帆竹,具有“自身”或關(guān)聯(lián)的類型要求的協(xié)議只能用作通用約束绕娘,在初始化時(shí),無法明確對(duì)象類型
泛型解決方案
protocol Runnable {
associatedtype Speed
var speed:Speed { get }
}
class Person : Runnable{
var speed: Double {
4.5
}
}
class Car : Runnable{
var speed: Int {
100
}
}
func get<T:Runnable>(_ type: Int) -> T {
if type == 0 {
return Person() as! T//強(qiáng)制類型轉(zhuǎn)換有風(fēng)險(xiǎn) 慎用馆揉!此處僅解決上述報(bào)錯(cuò)
}
return Car() as! T
}
//如果類型聲明與泛型不同則報(bào)錯(cuò)
var r1:Person = get(0)//Person實(shí)例
var r2:Car = get(1)//Car實(shí)例
不透明類型(Opaque Tpye)
上面的問題业舍,解決方案2:使用Opaque Tpye
//some 只能返回一種類型
func get(_ type: Int) -> some Runnable {
// if type == 0 { //取消注釋即報(bào)錯(cuò)
// return Person()
// }
return Car()
}
var r1 = get(0)
var r2 = get(1)
疑問:既然只返回一種類型,這個(gè)some不是很多余嗎升酣?
答:并不多余 可以隱藏返回的真實(shí)類型 且Car中的方法是無法調(diào)用的 只能訪問procotol中的speed屬性
some
some 除了可以用在返回值類型上舷暮,一般還可以用在屬性類型上
protocol Runnable {
associatedtype Speed
var speed:Speed { get }
}
class Dog : Runnable{
typealias Speed = Double
var speed: Speed{
3.0
}
}
class Person{
var pet:some Runnable {
return Dog()
}
}
do實(shí)現(xiàn)局部作用域
可以單獨(dú)使用do實(shí)現(xiàn)局部作用域
do{
let value1:Int = 10
print(value1)
}
do{
let value1:Int = 10
print(value1)
}