在 Swift 中可以通過(guò)Error
協(xié)議自定義運(yùn)行時(shí)的錯(cuò)誤信息.任何實(shí)現(xiàn)了Error
協(xié)議的枚舉,結(jié)構(gòu)體,類(lèi)都可以自定義錯(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ò)誤的幾種方式:
- 使用
do-catch
捕捉錯(cuò)誤
需要注意的是,一旦拋出錯(cuò)誤, try 下一句直到作用域結(jié)束的代碼都不會(huì)執(zhí)行,所以我們寫(xiě)代碼的時(shí)候要考慮好.
- 不捕捉錯(cuò)誤,使用
throws
將錯(cuò)誤一層層向上傳遞:
在調(diào)用有可能會(huì)拋出錯(cuò)誤的當(dāng)前函數(shù)的聲明中加上throws
關(guān)鍵字,錯(cuò)誤將自動(dòng)拋給上層調(diào)用的函數(shù),如果到最頂層的函數(shù)依然沒(méi)有捕捉錯(cuò)誤,程序仍然會(huì)崩潰.
- 使用
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ù):
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í)行
如果我們想控制一段代碼,不管以任何方式離開(kāi)其作用域前都會(huì)執(zhí)行,可以使用defer
關(guān)鍵字:
斷言 assert
我們還可以使用斷言assert
來(lái)處理不符合指定條件時(shí)使程序發(fā)生運(yùn)行時(shí)錯(cuò)誤,強(qiáng)制退出:
//格式
assert(條件 , 提示語(yǔ))
斷言只會(huì)在Debug
模式下才會(huì)生效,在Release
模式下會(huì)失效.
如果我們想讓斷言在Release
模式下也生效,或者如果我們想讓斷言在Debug
模式下失效,可以這樣設(shè)置:
fatalError
如果遇到嚴(yán)重問(wèn)題,想讓程序結(jié)束運(yùn)行,可以使用fatalError
函數(shù)拋出錯(cuò)誤.
fatalError
與assert
的區(qū)別是:assert
默認(rèn)只在Debug
模式向下才有效;fatalError
在Debug
和Release
模式下都有效.
func nickName(name: String){
if name.count > 6 {
//...
}
fatalError("nickName長(zhǎng)度不能小于6位")
}
nickName(name: "ok123")
函數(shù)中使用泛型
泛型在 Swift 中非常普遍,比如 Array
數(shù)組:
我們之前定義方法的時(shí)候都要寫(xiě)清楚參數(shù)類(lèi)型,比如Int , String , Double
等等.其實(shí)我們也可以在聲明方法時(shí),參數(shù)類(lèi)型用泛型表示,不限定參數(shù)類(lèi)型,由具體傳入的實(shí)參推斷參數(shù)類(lèi)型:
比如:
從上圖可以看到,使用泛型作為參數(shù),不管傳入的實(shí)參是什么類(lèi)型,都能正確匹配上.那么Swift
是怎樣實(shí)現(xiàn)泛型的呢?是不是它的底層采用函數(shù)重載的方式,根據(jù)傳入的實(shí)參自動(dòng)生成了很多同名的函數(shù)呢?我們可以通過(guò)匯編分析一下:
我們傳入不同類(lèi)型的的參數(shù)看看底層匯編:
匯編分析:
從匯編中可以看到兩次調(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í)參的元類(lèi)型
metadata也當(dāng)做參數(shù)傳入.而在
metadata中有每個(gè)類(lèi)型詳細(xì)的信息,所以泛型能識(shí)別出各個(gè)類(lèi)型.
類(lèi),結(jié)構(gòu)體,枚舉中使用泛型
Swift 中的Array , Dictionary
的定義中都使用了泛型:
//Array
public struct Array<Element> {...}
//Dictionary
public struct Dictionary<Key, Value> where Key : Hashable {...}
我們也可以在自己寫(xiě)的類(lèi)中使用泛型:
class Pet{
}
class Person<P>{
var age: Int = 1
//泛型
var pets = [P]()
}
var person = Person<Pet>()
之前我們創(chuàng)建對(duì)象的時(shí)候直接類(lèi)型名稱 + ()
就可以了,類(lèi)似這樣:
var person = Person()
但是如果類(lèi)中使用了泛型,就必須在創(chuàng)建實(shí)例時(shí)指明泛型的類(lèi)型,不然會(huì)報(bào)錯(cuò):
從給出的錯(cuò)誤中可以看到,編譯器無(wú)法推斷出泛型的類(lèi)型,所以報(bào)錯(cuò).
正確創(chuàng)建泛型實(shí)例應(yīng)該這樣:
var person = Person<Pet>()
Struct
和class
的泛型使用是一樣的,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)型,不然編譯器推斷不出來(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)其他類(lèi)實(shí)現(xiàn)這個(gè)協(xié)議時(shí),需要指定協(xié)議中的關(guān)聯(lián)類(lèi)型的真實(shí)類(lèi)型:
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)類(lèi)型是Dog
:
class Person: Petable{
// typealias Pet = Dog
func walkThePet(pet: Dog) {
print("遛的寵物是:",pet.type)
}
}
泛型約束
我們可以通過(guò)泛型約束函數(shù)的參數(shù)類(lèi)型和返回值類(lèi)型:
//寵物證協(xié)議
protocol PetCard {}
//寵物
class Pet{}
//收養(yǎng)寵物:必須是Pet的子類(lèi),并且實(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 的子類(lèi),并且實(shí)現(xiàn)了 PetCard 協(xié)議
.如果不滿足任何一個(gè)條件就會(huì)報(bào)錯(cuò).
where
關(guān)鍵字:增加約束條件
如果我們的約束條件比較多,可以使用where
關(guān)鍵字:
//寵物證協(xié)議
protocol PetCard {
//關(guān)聯(lián)屬性
associatedtype Pet
func showCard()
}
//聽(tīng)話協(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è)條件:
- 泛型
T1
,T2
中的Pet
必須類(lèi)型相同 -
T1
還要實(shí)現(xiàn)Tractable
協(xié)議
如果傳入的參數(shù)不符合條件,就會(huì)報(bào)錯(cuò):
含有關(guān)聯(lián)值類(lèi)型的協(xié)議作為函數(shù)返回值
我們經(jīng)常會(huì)使用協(xié)議作為函數(shù)的返回值,能夠限定返回值的類(lèi)型,比如這樣:
但是如果協(xié)議中含有關(guān)聯(lián)值類(lèi)型,并且作為函數(shù)返回值,以上代碼就會(huì)報(bào)錯(cuò):
為什么會(huì)報(bào)這種錯(cuò)呢?因?yàn)?/code>Petable
協(xié)議中有個(gè)關(guān)聯(lián)了類(lèi)型
Color.而
getApet方法的返回值是不確定的.所以編譯器是不知道
Color是哪種類(lèi)型,所以就出現(xiàn)了這種錯(cuò)誤.
解決這種錯(cuò)誤有兩種方式:
使用泛型解決
some
使用some
聲明一個(gè)不透明類(lèi)型
但是使用some
之后會(huì)出現(xiàn)另一個(gè)錯(cuò)誤.這是因?yàn)?/code>some
有一個(gè)限制,只能返回一種類(lèi)型,不能一會(huì)兒是 cat , 一會(huì)兒是 dog
所以如果想讓一個(gè)帶有關(guān)聯(lián)類(lèi)型的協(xié)議作為函數(shù)返回值,可以使用some
關(guān)鍵字.但是必須只能返回一種類(lèi)型.
有人可能會(huì)覺(jué)得some
關(guān)鍵字很多余,既然只能返回一種類(lèi)型,為什么直接把返回值寫(xiě)成Cat
或者Dog
呢?
因?yàn)橹苯訉?xiě)成Cat
或者Dog
,我們通過(guò)函數(shù)獲得的對(duì)象,外界就能直接看到是什么類(lèi)型,并且類(lèi)型中的屬性,方法也會(huì)暴露出去:
而使用協(xié)議作為返回值可以對(duì)外隱藏真實(shí)類(lèi)型:
所以,如果有這種特殊需求:只想返回一個(gè)遵守某種協(xié)議的對(duì)象,并且只想把協(xié)議中的方法暴露出去,不想暴露真實(shí)類(lèi)型和方法,這時(shí)就可以使用 some 關(guān)鍵字.
some
關(guān)鍵字還可以用在屬性中:
其實(shí)如果協(xié)議中沒(méi)有關(guān)聯(lián)類(lèi)型,也不會(huì)暴露真實(shí)類(lèi)型,只不過(guò)some
關(guān)鍵字就是專門(mén)用來(lái)解決協(xié)議中有關(guān)聯(lián)類(lèi)型或者使用Self
關(guān)鍵字的問(wèn)題: