從零學(xué)習(xí)Swift 11: 錯(cuò)誤處理,泛型使用

總結(jié)

在 Swift 中可以通過(guò)Error協(xié)議自定義運(yùn)行時(shí)的錯(cuò)誤信息.任何實(shí)現(xiàn)了Error協(xié)議的枚舉,結(jié)構(gòu)體,類都可以自定義錯(cuò)誤信息.

自定義錯(cuò)誤三部曲:
1: 實(shí)現(xiàn)Error協(xié)議


//第一步: 實(shí)現(xiàn) Error 協(xié)議:
enum MyError: Error{
    case urlError(String)
    case otherError(String)
}

2: 方法聲明和方法體:


//如果有可能拋出錯(cuò)誤的方法,需要在方法聲明中添加`throws`關(guān)鍵字
func isValidURL(url: String) throws{
    print("接口檢查")

    
    guard url.hasPrefix("http") else {
        //使用 throw 拋出錯(cuò)誤
        throw MyError.otherError("url有誤")
    }
    
    print("進(jìn)行網(wǎng)絡(luò)請(qǐng)求")
}

3: 調(diào)用方法時(shí)必須添加try關(guān)鍵字:


try isValidURL(url: "")

以上三步我們就自定義了一個(gè)錯(cuò)誤,并且把這個(gè)錯(cuò)誤拋了出去.但是這個(gè)錯(cuò)誤我們還沒(méi)有處理.如果錯(cuò)誤一直沒(méi)有被處理會(huì)造成程序崩潰:

錯(cuò)誤沒(méi)有處理造成崩潰

處理錯(cuò)誤的幾種方式:

  1. 使用do-catch捕捉錯(cuò)誤
do-catch 捕捉錯(cuò)誤

需要注意的是,一旦拋出錯(cuò)誤, try 下一句直到作用域結(jié)束的代碼都不會(huì)執(zhí)行,所以我們寫代碼的時(shí)候要考慮好.

  1. 不捕捉錯(cuò)誤,使用throws將錯(cuò)誤一層層向上傳遞:
throws 傳遞錯(cuò)誤

在調(diào)用有可能會(huì)拋出錯(cuò)誤的當(dāng)前函數(shù)的聲明中加上throws關(guān)鍵字,錯(cuò)誤將自動(dòng)拋給上層調(diào)用的函數(shù),如果到最頂層的函數(shù)依然沒(méi)有捕捉錯(cuò)誤,程序仍然會(huì)崩潰.

  1. 使用try?,將函數(shù)調(diào)用結(jié)果封裝成可選項(xiàng),如果函數(shù)調(diào)用失敗,結(jié)果為nil,不需要我們處理錯(cuò)誤.

try? isValidURL(url: "")

rethrows
如果函數(shù)本身不會(huì)拋出錯(cuò)誤,但是函數(shù)的閉包參數(shù)可能會(huì)拋出錯(cuò)誤,這時(shí)就要使用rethrows聲明函數(shù):

rethrows

Swift 中的空合并運(yùn)算符其實(shí)就是方法,它的定義就使用了rethrows聲明:


public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T

public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?

defer延遲執(zhí)行
如果我們想控制一段代碼,不管以任何方式離開其作用域前都會(huì)執(zhí)行,可以使用defer關(guān)鍵字:

defer
斷言 assert

我們還可以使用斷言assert來(lái)處理不符合指定條件時(shí)使程序發(fā)生運(yùn)行時(shí)錯(cuò)誤,強(qiáng)制退出:

//格式
assert(條件 , 提示語(yǔ))

示例

斷言只會(huì)在Debug模式下才會(huì)生效,在Release模式下會(huì)失效.
如果我們想讓斷言在Release模式下也生效,或者如果我們想讓斷言在Debug模式下失效,可以這樣設(shè)置:

設(shè)置斷言
fatalError

如果遇到嚴(yán)重問(wèn)題,想讓程序結(jié)束運(yùn)行,可以使用fatalError函數(shù)拋出錯(cuò)誤.
fatalErrorassert的區(qū)別是:assert默認(rèn)只在Debug模式向下才有效;fatalErrorDebugRelease模式下都有效.


func nickName(name: String){
    if name.count > 6 {
        //...
    }
    fatalError("nickName長(zhǎng)度不能小于6位")
}

nickName(name: "ok123")


函數(shù)中使用泛型

泛型在 Swift 中非常普遍,比如 Array數(shù)組:

Array 中的泛型

我們之前定義方法的時(shí)候都要寫清楚參數(shù)類型,比如Int , String , Double等等.其實(shí)我們也可以在聲明方法時(shí),參數(shù)類型用泛型表示,不限定參數(shù)類型,由具體傳入的實(shí)參推斷參數(shù)類型:

比如:

泛型函數(shù)

從上圖可以看到,使用泛型作為參數(shù),不管傳入的實(shí)參是什么類型,都能正確匹配上.那么Swift是怎樣實(shí)現(xiàn)泛型的呢?是不是它的底層采用函數(shù)重載的方式,根據(jù)傳入的實(shí)參自動(dòng)生成了很多同名的函數(shù)呢?我們可以通過(guò)匯編分析一下:

我們傳入不同類型的的參數(shù)看看底層匯編:

傳入不同的參數(shù)

匯編分析:

匯編語(yǔ)言

從匯編中可以看到兩次調(diào)用test()函數(shù)的方法地址是相同的,也就是說(shuō)底層并沒(méi)有自動(dòng)生成其他函數(shù).其實(shí)泛型實(shí)現(xiàn)的關(guān)鍵就在于綠色標(biāo)注出來(lái)的部分.我們可以看到在調(diào)用test()方法之前會(huì)把實(shí)參的元類型metadata也當(dāng)做參數(shù)傳入.而在metadata中有每個(gè)類型詳細(xì)的信息,所以泛型能識(shí)別出各個(gè)類型.

類,結(jié)構(gòu)體,枚舉中使用泛型

Swift 中的Array , Dictionary的定義中都使用了泛型:


//Array
public struct Array<Element> {...}

//Dictionary
public struct Dictionary<Key, Value> where Key : Hashable {...}

我們也可以在自己寫的類中使用泛型:


class Pet{
    
}

class Person<P>{
    var age: Int = 1
    //泛型
    var pets = [P]()
}

var person = Person<Pet>()

之前我們創(chuàng)建對(duì)象的時(shí)候直接類型名稱 + ()就可以了,類似這樣:


var person = Person()

但是如果類中使用了泛型,就必須在創(chuàng)建實(shí)例時(shí)指明泛型的類型,不然會(huì)報(bào)錯(cuò):

泛型類型的創(chuàng)建

從給出的錯(cuò)誤中可以看到,編譯器無(wú)法推斷出泛型的類型,所以報(bào)錯(cuò).

正確創(chuàng)建泛型實(shí)例應(yīng)該這樣:


var person = Person<Pet>()

Structclass的泛型使用是一樣的,Enum稍微有一個(gè)不同點(diǎn),需要注意一下:

枚舉中使用泛型

可以看到按照之前創(chuàng)建枚舉的方式會(huì)報(bào)錯(cuò),報(bào)的錯(cuò)誤是T無(wú)法被推斷出來(lái),和我們創(chuàng)建實(shí)例對(duì)象時(shí)候的錯(cuò)誤一樣.所以我們要在創(chuàng)建時(shí)指明泛型是哪種類型,不然編譯器推斷不出來(lái),分配內(nèi)存時(shí)無(wú)法分配:


enum Myerror<T>{
    case number(Int)
    case text(T)
}

var error = Myerror<String>.number(1)

協(xié)議中使用泛型

協(xié)議中使用泛型比較特殊,必須使用associatedtype關(guān)鍵字:


//可以養(yǎng)寵物的協(xié)議
protocol Petable {
    associatedtype Pet
    func walkThePet(pet: Pet)
}

當(dāng)其他類實(shí)現(xiàn)這個(gè)協(xié)議時(shí),需要指定協(xié)議中的關(guān)聯(lián)類型的真實(shí)類型:


class Person: Petable{
    
    typealias Pet = Dog
    
    func walkThePet(pet: Dog) {
        print("遛的寵物是:",pet.type)
    }
}

typealias Pet = Dog這句代碼可以省略掉,因?yàn)榫幾g器可以通過(guò)walkThePet(pet: Dog)的參數(shù)中推斷出關(guān)聯(lián)類型是Dog:


class Person: Petable{
    
//    typealias Pet = Dog
    
    func walkThePet(pet: Dog) {
        print("遛的寵物是:",pet.type)
    }
}


泛型約束

我們可以通過(guò)泛型約束函數(shù)的參數(shù)類型和返回值類型:


//寵物證協(xié)議
protocol PetCard {}
//寵物
class Pet{}

//收養(yǎng)寵物:必須是Pet的子類,并且實(shí)現(xiàn)了PetCard協(xié)議
func adoptionPet(T: Pet & PetCard){
    
}

class Dog: Pet & PetCard{
}

class Pig: Pet{}

adoptionPet(T: Dog())
adoptionPet(T: Pig())

以上代碼就對(duì)adoptionPet ()方法的參數(shù)作了約束,只允許傳入的參數(shù)是Pet 的子類,并且實(shí)現(xiàn)了 PetCard 協(xié)議.如果不滿足任何一個(gè)條件就會(huì)報(bào)錯(cuò).

協(xié)議約束

where關(guān)鍵字:增加約束條件
如果我們的約束條件比較多,可以使用where關(guān)鍵字:


//寵物證協(xié)議
protocol PetCard {
    //關(guān)聯(lián)屬性
    associatedtype Pet
    func showCard()
}

//聽話協(xié)議
protocol Tractable {
    
}

class Dog: PetCard , Tractable{
    typealias Pet = Dog
    func showCard() {
    
    }
    
    let type = "狗"
}

class Cat: PetCard{
    typealias Pet = Cat
    func showCard() {
        
    }
    
}

func play<T1: PetCard,T2: PetCard>(animal1: T1 , animal2: T2) where
    T1.Pet == T2.Pet , T1.Pet : Tractable
{
    
}

var dog = Dog()
var cat = Cat()
play(animal1: dog, animal2: cat)

上面代碼我們使用where關(guān)鍵字增加了兩個(gè)條件:

  1. 泛型T1 , T2中的Pet必須類型相同
  2. T1還要實(shí)現(xiàn)Tractable協(xié)議

如果傳入的參數(shù)不符合條件,就會(huì)報(bào)錯(cuò):

不符合條件

含有關(guān)聯(lián)值類型的協(xié)議作為函數(shù)返回值

我們經(jīng)常會(huì)使用協(xié)議作為函數(shù)的返回值,能夠限定返回值的類型,比如這樣:

協(xié)議類型作為函數(shù)返回值

但是如果協(xié)議中含有關(guān)聯(lián)值類型,并且作為函數(shù)返回值,以上代碼就會(huì)報(bào)錯(cuò):

含有關(guān)聯(lián)類型的協(xié)議作為返回值

為什么會(huì)報(bào)這種錯(cuò)呢?因?yàn)?/code>Petable協(xié)議中有個(gè)關(guān)聯(lián)了類型Color.而getApet方法的返回值是不確定的.所以編譯器是不知道Color是哪種類型,所以就出現(xiàn)了這種錯(cuò)誤.

解決這種錯(cuò)誤有兩種方式:

  1. 使用泛型解決
使用泛型解決
  1. some

使用some聲明一個(gè)不透明類型

使用 some 后又出現(xiàn)了另一個(gè)錯(cuò)誤

但是使用some之后會(huì)出現(xiàn)另一個(gè)錯(cuò)誤.這是因?yàn)?/code>some有一個(gè)限制,只能返回一種類型,不能一會(huì)兒是 cat , 一會(huì)兒是 dog

some要求只能返回一種類型

所以如果想讓一個(gè)帶有關(guān)聯(lián)類型的協(xié)議作為函數(shù)返回值,可以使用some關(guān)鍵字.但是必須只能返回一種類型.

有人可能會(huì)覺(jué)得some關(guān)鍵字很多余,既然只能返回一種類型,為什么直接把返回值寫成Cat或者Dog呢?

因?yàn)橹苯訉懗?code>Cat或者Dog,我們通過(guò)函數(shù)獲得的對(duì)象,外界就能直接看到是什么類型,并且類型中的屬性,方法也會(huì)暴露出去:

暴露類型

而使用協(xié)議作為返回值可以對(duì)外隱藏真實(shí)類型:

隱藏實(shí)際類型

所以,如果有這種特殊需求:只想返回一個(gè)遵守某種協(xié)議的對(duì)象,并且只想把協(xié)議中的方法暴露出去,不想暴露真實(shí)類型和方法,這時(shí)就可以使用 some 關(guān)鍵字.

some關(guān)鍵字還可以用在屬性中:

some 關(guān)鍵字用在屬性中

其實(shí)如果協(xié)議中沒(méi)有關(guān)聯(lián)類型,也不會(huì)暴露真實(shí)類型,只不過(guò)some關(guān)鍵字就是專門用來(lái)解決協(xié)議中有關(guān)聯(lián)類型或者使用Self關(guān)鍵字的問(wèn)題:

協(xié)議中沒(méi)有關(guān)聯(lián)類型也不會(huì)暴露真實(shí)類型
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容