在 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: "")
以上三步我们就自定义了一个错误,并且把这个错误抛了出去.但是这个错误我们还没有处理.如果错误一直没有被处理会造成程序崩溃:
处理错误的几种方式:
- 使用
do-catch
捕捉错误
需要注意的是,一旦抛出错误, try 下一句直到作用域结束的代码都不会执行,所以我们写代码的时候要考虑好.
- 不捕捉错误,使用
throws
将错误一层层向上传递:
在调用有可能会抛出错误的当前函数的声明中加上throws
关键字,错误将自动抛给上层调用的函数,如果到最顶层的函数依然没有捕捉错误,程序仍然会崩溃.
- 使用
try?
,将函数调用结果封装成可选项,如果函数调用失败,结果为nil
,不需要我们处理错误.
try? isValidURL(url: "")
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
关键字:
断言 assert
我们还可以使用断言assert
来处理不符合指定条件时使程序发生运行时错误,强制退出:
//格式
assert(条件 , 提示语)
断言只会在Debug
模式下才会生效,在Release
模式下会失效.
如果我们想让断言在Release
模式下也生效,或者如果我们想让断言在Debug
模式下失效,可以这样设置:
fatalError
如果遇到严重问题,想让程序结束运行,可以使用fatalError
函数抛出错误.
fatalError
与assert
的区别是:assert
默认只在Debug
模式向下才有效;fatalError
在Debug
和Release
模式下都有效.
func nickName(name: String){
if name.count > 6 {
//...
}
fatalError("nickName长度不能小于6位")
}
nickName(name: "ok123")
函数中使用泛型
泛型在 Swift 中非常普遍,比如 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>()
Struct
和class
的泛型使用是一样的,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
关键字增加了两个条件:
- 泛型
T1
,T2
中的Pet
必须类型相同 -
T1
还要实现Tractable
协议
如果传入的参数不符合条件,就会报错:
含有关联值类型的协议作为函数返回值
我们经常会使用协议作为函数的返回值,能够限定返回值的类型,比如这样:
但是如果协议中含有关联值类型,并且作为函数返回值,以上代码就会报错:
为什么会报这种错呢?因为
Petable协议中有个关联了类型
Color.而
getApet方法的返回值是不确定的.所以编译器是不知道
Color是哪种类型,所以就出现了这种错误.
解决这种错误有两种方式:
使用泛型解决
some
使用some
声明一个不透明类型
但是使用some
之后会出现另一个错误.这是因为
some有一个限制,只能返回一种类型,不能一会儿是 cat , 一会儿是 dog
所以如果想让一个带有关联类型的协议作为函数返回值,可以使用some
关键字.但是必须只能返回一种类型.
有人可能会觉得some
关键字很多余,既然只能返回一种类型,为什么直接把返回值写成Cat
或者Dog
呢?
因为直接写成Cat
或者Dog
,我们通过函数获得的对象,外界就能直接看到是什么类型,并且类型中的属性,方法也会暴露出去:
而使用协议作为返回值可以对外隐藏真实类型:
所以,如果有这种特殊需求:只想返回一个遵守某种协议的对象,并且只想把协议中的方法暴露出去,不想暴露真实类型和方法,这时就可以使用 some 关键字.
some
关键字还可以用在属性中:
其实如果协议中没有关联类型,也不会暴露真实类型,只不过some
关键字就是专门用来解决协议中有关联类型或者使用Self
关键字的问题: