从零学习Swift 11: 错误处理,泛型使用

总结

在 Swift 中可以通过Error协议自定义运行时的错误信息.任何实现了Error协议的枚举,结构体,类都可以自定义错误信息.

自定义错误三部曲:
1: 实现Error协议


//第一步: 实现 Error 协议:
enum MyError: Error{
    case urlError(String)
    case otherError(String)
}

2: 方法声明和方法体:


//如果有可能抛出错误的方法,需要在方法声明中添加`throws`关键字
func isValidURL(url: String) throws{
    print("接口检查")

    
    guard url.hasPrefix("http") else {
        //使用 throw 抛出错误
        throw MyError.otherError("url有误")
    }
    
    print("进行网络请求")
}

3: 调用方法时必须添加try关键字:


try isValidURL(url: "")

以上三步我们就自定义了一个错误,并且把这个错误抛了出去.但是这个错误我们还没有处理.如果错误一直没有被处理会造成程序崩溃:

错误没有处理造成崩溃

处理错误的几种方式:

  1. 使用do-catch捕捉错误
do-catch 捕捉错误

需要注意的是,一旦抛出错误, try 下一句直到作用域结束的代码都不会执行,所以我们写代码的时候要考虑好.

  1. 不捕捉错误,使用throws将错误一层层向上传递:
throws 传递错误

在调用有可能会抛出错误的当前函数的声明中加上throws关键字,错误将自动抛给上层调用的函数,如果到最顶层的函数依然没有捕捉错误,程序仍然会崩溃.

  1. 使用try?,将函数调用结果封装成可选项,如果函数调用失败,结果为nil,不需要我们处理错误.

try? isValidURL(url: "")

rethrows
如果函数本身不会抛出错误,但是函数的闭包参数可能会抛出错误,这时就要使用rethrows声明函数:

rethrows

Swift 中的空合并运算符其实就是方法,它的定义就使用了rethrows声明:


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

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

defer延迟执行
如果我们想控制一段代码,不管以任何方式离开其作用域前都会执行,可以使用defer关键字:

defer
断言 assert

我们还可以使用断言assert来处理不符合指定条件时使程序发生运行时错误,强制退出:

//格式
assert(条件 , 提示语)

示例

断言只会在Debug模式下才会生效,在Release模式下会失效.
如果我们想让断言在Release模式下也生效,或者如果我们想让断言在Debug模式下失效,可以这样设置:

设置断言
fatalError

如果遇到严重问题,想让程序结束运行,可以使用fatalError函数抛出错误.
fatalErrorassert的区别是:assert默认只在Debug模式向下才有效;fatalErrorDebugRelease模式下都有效.


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

nickName(name: "ok123")


函数中使用泛型

泛型在 Swift 中非常普遍,比如 Array数组:

Array 中的泛型

我们之前定义方法的时候都要写清楚参数类型,比如Int , String , Double等等.其实我们也可以在声明方法时,参数类型用泛型表示,不限定参数类型,由具体传入的实参推断参数类型:

比如:

泛型函数

从上图可以看到,使用泛型作为参数,不管传入的实参是什么类型,都能正确匹配上.那么Swift是怎样实现泛型的呢?是不是它的底层采用函数重载的方式,根据传入的实参自动生成了很多同名的函数呢?我们可以通过汇编分析一下:

我们传入不同类型的的参数看看底层汇编:

传入不同的参数

汇编分析:

汇编语言

从汇编中可以看到两次调用test()函数的方法地址是相同的,也就是说底层并没有自动生成其他函数.其实泛型实现的关键就在于绿色标注出来的部分.我们可以看到在调用test()方法之前会把实参的元类型metadata也当做参数传入.而在metadata中有每个类型详细的信息,所以泛型能识别出各个类型.

类,结构体,枚举中使用泛型

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>()

之前我们创建对象的时候直接类型名称 + ()就可以了,类似这样:


var person = Person()

但是如果类中使用了泛型,就必须在创建实例时指明泛型的类型,不然会报错:

泛型类型的创建

从给出的错误中可以看到,编译器无法推断出泛型的类型,所以报错.

正确创建泛型实例应该这样:


var person = Person<Pet>()

Structclass的泛型使用是一样的,Enum稍微有一个不同点,需要注意一下:

枚举中使用泛型

可以看到按照之前创建枚举的方式会报错,报的错误是T无法被推断出来,和我们创建实例对象时候的错误一样.所以我们要在创建时指明泛型是哪种类型,不然编译器推断不出来,分配内存时无法分配:


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

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

协议中使用泛型

协议中使用泛型比较特殊,必须使用associatedtype关键字:


//可以养宠物的协议
protocol Petable {
    associatedtype Pet
    func walkThePet(pet: Pet)
}

当其他类实现这个协议时,需要指定协议中的关联类型的真实类型:


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

typealias Pet = Dog这句代码可以省略掉,因为编译器可以通过walkThePet(pet: Dog)的参数中推断出关联类型是Dog:


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


泛型约束

我们可以通过泛型约束函数的参数类型和返回值类型:


//宠物证协议
protocol PetCard {}
//宠物
class Pet{}

//收养宠物:必须是Pet的子类,并且实现了PetCard协议
func adoptionPet(T: Pet & PetCard){
    
}

class Dog: Pet & PetCard{
}

class Pig: Pet{}

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

以上代码就对adoptionPet ()方法的参数作了约束,只允许传入的参数是Pet 的子类,并且实现了 PetCard 协议.如果不满足任何一个条件就会报错.

协议约束

where关键字:增加约束条件
如果我们的约束条件比较多,可以使用where关键字:


//宠物证协议
protocol PetCard {
    //关联属性
    associatedtype Pet
    func showCard()
}

//听话协议
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关键字增加了两个条件:

  1. 泛型T1 , T2中的Pet必须类型相同
  2. T1还要实现Tractable协议

如果传入的参数不符合条件,就会报错:

不符合条件

含有关联值类型的协议作为函数返回值

我们经常会使用协议作为函数的返回值,能够限定返回值的类型,比如这样:

协议类型作为函数返回值

但是如果协议中含有关联值类型,并且作为函数返回值,以上代码就会报错:

含有关联类型的协议作为返回值

为什么会报这种错呢?因为Petable协议中有个关联了类型Color.而getApet方法的返回值是不确定的.所以编译器是不知道Color是哪种类型,所以就出现了这种错误.

解决这种错误有两种方式:

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

使用some声明一个不透明类型

使用 some 后又出现了另一个错误

但是使用some之后会出现另一个错误.这是因为some有一个限制,只能返回一种类型,不能一会儿是 cat , 一会儿是 dog

some要求只能返回一种类型

所以如果想让一个带有关联类型的协议作为函数返回值,可以使用some关键字.但是必须只能返回一种类型.

有人可能会觉得some关键字很多余,既然只能返回一种类型,为什么直接把返回值写成Cat或者Dog呢?

因为直接写成Cat或者Dog,我们通过函数获得的对象,外界就能直接看到是什么类型,并且类型中的属性,方法也会暴露出去:

暴露类型

而使用协议作为返回值可以对外隐藏真实类型:

隐藏实际类型

所以,如果有这种特殊需求:只想返回一个遵守某种协议的对象,并且只想把协议中的方法暴露出去,不想暴露真实类型和方法,这时就可以使用 some 关键字.

some关键字还可以用在属性中:

some 关键字用在属性中

其实如果协议中没有关联类型,也不会暴露真实类型,只不过some关键字就是专门用来解决协议中有关联类型或者使用Self关键字的问题:

协议中没有关联类型也不会暴露真实类型
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342