Swift第七章 协议、错误处理
协议
-
协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)
protocol Drawable { func draw() var x: Int { get set } var y: Int { get } subscript(index: Int) -> Int { get set } } protocol Test1 {} protocol Test2 {} protocol Test3 {} class TestClass : Test1, Test2, Test3 {} - 协议中定义方法时不能有默认参数值
- 默认情况下,协议中定义的内容必须全部都实现
协议中的属性
- 协议中定义属性时必须用var关键字
- 实现协议时的属性权限要不小于协议中定义的属性权限
- 协议定义get、set,代表该属性可读写,用var存储属性或get、set计算属性去实现
- 协议定义get,代表该属性可读,用任何属性都可以实现。
protocol Drawable {
func draw()
//代表这个属性是可读可写
var x: Int { get set }
//这个属性是可读的
var y: Int { get }
//可读可写
subscript(index: Int) -> Int { get set }
}
//方式一:
Person : Drawable {
//用var存储属性实现
var x: Int = 0
//let代表只读,存储属性实现
let y: Int = 0
func draw() {
print("Person draw")
}
//get、set计算属性去实现
subscript(index: Int) -> Int {
set {}
get { index }
}
}
//方式二:
class Person : Drawable {
//get、set计算属性去实现
var x: Int {
get { 0 }
set {}
}
//计算属性.get方法简写
var y: Int { 0 }
func draw() { print("Person draw") }
subscript(index: Int) -> Int {
set {}
get { index }
}
}
static、class
- 为了保证通用,协议中必须用static定义类型方法、类型属性、类型下标
protocol Drawable {
//协议必须用static修饰
static func draw()
}
class Person1 : Drawable {
//实现可以用class,允许子类重写
class func draw() {
print("Person1 draw")
}
}
class Person2 : Drawable {
//也可以用static,不允许子类重写
static func draw() {
print("Person2 draw")
}
}
mutating
- 只有将协议中的实例方法标记为mutating,才允许结构体、枚举的具体实现修改自身内存
- 类在实现方法时不用加mutating,枚举、结构体才需要加mutating
protocol Drawable {
mutating func draw()
}
class Size : Drawable {
var width: Int = 0
//类不加mutating
func draw() {
width = 10
}
}
struct Point : Drawable {
var x: Int = 0
//结构体加mutating
mutating func draw() {
x = 10
}
}
init
- 协议中还可以定义初始化器init
-
非final类实现时必须加上required
protocol Drawable { init(x: Int, y: Int) } class Point : Drawable { //非final类实现时必须加上required required init(x: Int, y: Int) {} } //final类实现时不用加required final class Size : Drawable { init(x: Int, y: Int) {} } -
如果从协议实现的初始化器,刚好是重写了父类的指定初始化器,那么这个初始化必须同时加required、override
protocol Livable { init(age: Int) } class Person { init(age: Int) {} } //协议和父类的init相同 class Student : Person, Livable { required override init(age: Int) { super.init(age: age) } }
init、init?、init!
- 协议中定义的init?、init!,可以用init、init?、init!去实现
- 协议中定义的init,可以用init、init!去实现
protocol Livable {
init()
init?(age: Int)
init!(no: Int)
}
class Person : Livable {
//非可失败初始化器,实现有2种方案
required init() {}
// required init!() {}
//有三种方案
required init?(age: Int) {}
// required init!(age: Int) {}
// required init(age: Int) {}
//有三种方案
required init!(no: Int) {}
// required init?(no: Int) {}
// required init(no: Int) {}
}
协议的继承
- 一个协议可以继承其他协议
protocol Runnable {
func run()
}
protocol Livable : Runnable {
func breath()
}
class Person : Livable {
func breath() {}
func run() {}
}
协议组合
- 协议组合,可以包含1个类类型(最多1个) protocol Livable {}
protocol Livable {}
protocol Runnable {}
class Person {}
// 接收Person或者其子类的实例
func fn0(obj: Person) {}
// 接收遵守Livable协议的实例
func fn1(obj: Livable) {}
// 接收同时遵守Livable、Runnable协议的实例
func fn2(obj: Livable & Runnable) {}
// 接收同时遵守Livable、Runnable协议、并且是Person或者其子类的实例
func fn3(obj: Person & Livable & Runnable) {}
typealias RealPerson = Person & Livable & Runnable
// 接收同时遵守Livable、Runnable协议、并且是Person或者其子类的实例
func fn4(obj: RealPerson) {}
CaseIterable
- 让枚举遵守CaseIterable协议,可以实现遍历枚举值
enum Season : CaseIterable {
case spring, summer, autumn, winter
}
let seasons = Season.allCases
print(seasons.count) // 4
for season in seasons {
print(season)
} // spring summer autumn winter
CustomStringConvertible
-
遵守CustomStringConvertible、 CustomDebugStringConvertible协议,都可以自定义实例的打印字符串
class Person : CustomStringConvertible, CustomDebugStringConvertible { var age = 0 //实现协议的只读属性 var description: String { "person_\(age)" } var debugDescription: String { "debug_person_\(age)" } } var person = Person() print(person) // person_0 debugPrint(person) // debug_person_0 - print调用的是CustomStringConvertible协议的description
- debugPrint、po调用的是CustomDebugStringConvertible协议的debugDescription
Any、AnyObject
- Swift提供了2种特殊的类型:Any、AnyObject
- Any:可以代表任意类型(枚举、结构体、类,也包括函数类型)
- AnyObject:可以代表任意类类型(在协议后面写上: AnyObject代表只有类能遵守这个协议)
-
在协议后面写上: class也代表只有类能遵守这个协议
//这个协议只能被类遵守 protocol Runnable : AnyObject { func run() } class Person : Runnable {} //报错 //struct Size : Runnable {} //创建一个任意类型的stu var stu: Any = 10 stu = "Jack" stu = Student() // 创建1个能存放任意类型的数组 // var data = Array<Any>() var data = [Any]() data.append(1) data.append(3.14) data.append(Student()) data.append("Jack") //闭包表达式 data.append({ 10 })
is、as?、as!、as
-
is用来判断是否为某种类型,as用来做强制类型转换
protocol Runnable { func run() } class Person {} class Student : Person, Runnable { func run() { print("Student run") } func study() { print("Student study") } } var stu: Any = 10 print(stu is Int) // true stu = "Jack" print(stu is String) // true stu = Student() print(stu is Person) // true print(stu is Student) // true print(stu is Runnable) // true var stu: Any = 10 //as强制转换。as?强制转换,但是结果可以是nil //转换结果是可选类型,调用方法,因此:()?.方法 (stu as? Student)?.study() // 没有调用study stu = Student() (stu as? Student)?.study() // Student study //强制转换,并隐式解包,有风险 (stu as! Student).study() // Student study (stu as? Runnable)?.run() // Student run var data = [Any]() data.append(Int("123") as Any) var d = 10 as Double print(d) // 10.0
X.self、X.Type、AnyClass
- X.self是一个元类型(metadata)的指针,metadata存放着类型相关信息
- X.self属于X.Type类型
-
也是实例对象前8个字节的地址值。
class Student : Person {} var perType: Person.Type = Person.self var stuType: Student.Type = Student.self perType = Student.self var anyType: AnyObject.Type = Person.self anyType = Student.self public typealias AnyClass = AnyObject.Type var anyType2: AnyClass = Person.self anyType2 = Student.self var per = Person() //取出Person实例对象的前8个字节 var perType = type(of: per) //type函数的返回值为 Person.self print(Person.self == type(of: per)) // true
元类型的应用
-
案例1:
class Animal { required init() {} } class Cat : Animal {} class Dog : Animal {} class Pig : Animal {} //参数是Animal.Type元类型数组,返回值为Animal类型的实例对象 func create(_ clses: [Animal.Type]) -> [Animal] { var arr = [Animal]() for cls in clses { //元类型.init(),创建实例。为确保每个子类型类型都有init,所以父类必须使用required arr.append(cls.init()) } return arr } print(create([Cat.self, Dog.self, Pig.self])) -
案例2
class Person { var age: Int = 0 } class Student : Person { var no: Int = 0 } //有些runtime的方法,在swift里面也能用 print(class_getInstanceSize(Student.self)) // 32 print(class_getSuperclass(Student.self)!) // Person //Swift还有个隐藏的基类:Swift._SwiftObject print(class_getSuperclass(Person.self)!) // Swift._SwiftObject- 从结果可以看得出来,Swift还有个隐藏的基类:Swift._SwiftObject
- 可以参考Swift源码:https://github.com/apple/swift/blob/master/stdlib/public/runtime/SwiftObject.h
Self
-
Self代表当前类型
class Person { //实例属性 var age = 1 //类属性 static var count = 2 func run() { //小写的self代表当前实例 print(self.age) // 1 //大写Self代表当前的类 print(Self.count) // 2 } } -
Self一般用作返回值类型,限定返回值跟方法调用者必须是同一类型(也可以作为参数类型)
protocol Runnable { func test() -> Self } class Person : Runnable { required init() {} //这个函数的返回值是,方法调用者的类型 func test() -> Self { type(of: self).init() } } class Student : Person {} var p = Person() // Person print(p.test()) var stu = Student() // Student print(stu.test())
错误处理
自定义错误
-
Swift中可以通过Error协议自定义运行时的错误信息
enum SomeError : Error { //非法参数 case illegalArg(String) //数组越界 case outOfBounds(Int, Int) case outOfMemory } -
函数内部通过throw抛出自定义Error,可能会抛出Error的函数必须加上throws声明
//throws,代表当前函数可能会抛出异常 func divide(_ num1: Int, _ num2: Int) throws -> Int { if num2 == 0 { //函数内部通过throw抛出自定义Error throw SomeError.illegalArg("0不能作为除数") } return num1 / num2 } -
需要使用try调用可能会抛出Error的函数
//要使用try调用 var result = try divide(20, 10)
do-catch
-
可以使用do-catch捕捉Error
func test() { print("1") do { print("2") //这抛出异常 print(try divide(20, 0)) //永远不会被执行 print("3") } catch let SomeError.illegalArg(msg) { print("参数异常:", msg) } catch let SomeError.outOfBounds(size, index) { print("下标越界:", "size=\(size)", "index=\(index)") } catch SomeError.outOfMemory { print("内存溢出") } catch { print("其他错误") } print("4") } test() // 1 // 2 // 参数异常: 0不能作为除数 // 4 //第二种实现方式 do { try divide(20, 0) } catch let error { switch error { case let SomeError.illegalArg(msg): print("参数错误:", msg) default: print("其他错误") } } -
抛出Error后,try下一句直到作用域结束的代码都将不会执行
处理Error的2种方式
- 通过do-catch捕捉Error
-
不捕捉Error,在当前函数增加throws声明,Error将自动抛给上层函数,如果最顶层函数(main函数)依然没有捕捉Error,那么程序将终止
func test() throws { print("1") print(try divide(20, 0)) print("2") } try test() // 1 // Fatal error: Error raised at top level func test() throws { print("1") do { print("2") print(try divide(20, 0)) print("3") } catch let error as SomeError { //将error强制转换为SomeError类型 print(error) } print("4") } try test() // 1 // 2 // illegalArg("0不能作为除数") // 4 do { print(try divide(20, 0)) } catch is SomeError { //如果错误类型是SomeError就处理 print("SomeError") } -
注意点:
//单独放这一句,不会报错,因为异常会抛出给顶层 try divide(20, 0) //如果放到函数里面,就会报错,要求函数必须处理异常,要么do-catch、要么throws func test() { //try divide(20, 0) }
try?、try!
- 可以使用try?、try!调用可能会抛出Error的函数,这样就不用去处理Error。
-
try?调用结果,一旦有异常就为nil,否则返回可选类型。
//此时test函数不用处理异常 func test() { print("1") var result1 = try? divide(20, 10) // Optional(2), Int? var result2 = try? divide(20, 0) // nil var result3 = try! divide(20, 10) // 2, Int print("2") } test() -
a、b是等价的
var a = try? divide(20, 0) var b: Int? do { b = try divide(20, 0) } catch { b = nil }
rethrows
-
rethrows表明:函数本身不会抛出错误,但调用闭包参数抛出错误,那么它会将错误向上抛
//fn: (Int, Int) throws -> Int 参数fn是个函数,这个函数可能会抛出异常 //exec函数不会抛出异常,但是参数是一个函数,调用的时候可能会抛出异常,此时用rethrows func exec(_ fn: (Int, Int) throws -> Int, _ num1: Int, _ num2: Int) rethrows { print(try fn(num1, num2)) } // Fatal error: Error raised at top level try exec(divide, 20, 0)
defer
- defer语句:用来定义以任何方式(抛错误、return等)离开代码块前必须要执行的代码
-
defer语句将延迟至当前作用域结束之前执行
//打开文件 func open(_ filename: String) -> Int { print("open") return 0 } //关闭文件 func close(_ file: Int) { print("close") } func processFile(_ filename: String) throws { //1. 打开文件 let file = open(filename) //4. 通过defer,确保异常出现之前,执行关闭 defer { close(file) } // 2. 使用file // .... try divide(20, 0) //3. 关闭文件 // 上面一旦抛出异常,这句就不会执行 //close(file) } try processFile("test.txt") // open // close---抛出异常之前执行 // Fatal error: Error raised at top level -
defer语句的执行顺序与定义顺序相反
func fn1() { print("fn1") } func fn2() { print("fn2") } func test() { defer { fn1() } defer { fn2() } } test() // fn2 // fn1
assert(断言)
- 很多编程语言都有断言机制:不符合指定条件就抛出运行时错误,常用于调试(Debug)阶段的条件判断
-
默认情况下,Swift的断言只会在Debug模式下生效,Release模式下会忽略
func divide(_ v1: Int, _ v2: Int) -> Int { assert(v2 != 0, "除数不能为0") return v1 / v2 } print(divide(20, 0)) - 增加Swift Flags修改断言的默认行为
- -assert-config Release:强制关闭断言
- -assert-config Debug:强制开启断言

fatalError
- 如果遇到严重问题,希望结束程序运行时,可以直接使用fatalError函数抛出错误(这是无法通过do-catch捕捉的错误)
- 使用了fatalError函数,就不需要再写return
func test(_ num: Int) -> Int { if num >= 0 { return 1 } fatalError("num不能小于0") } -
在某些不得不实现、但不希望别人调用的方法,可以考虑内部使用fatalError函数
class Person { required init() {} } class Student : Person { required init() { fatalError("don't call Student.init") } init(score: Int) {} } var stu1 = Student(score: 98) var stu2 = Student()
局部作用域
-
swift不支持使用大括号{}实现局部作用域,但是可以使用 do 实现局部作用域
do { let dog1 = Dog() dog1.age = 10 dog1.run() } do { let dog2 = Dog() dog2.age = 10 dog2.run() }
泛型(Generics)
- 泛型可以将类型参数化,提高代码复用率,减少代码量
泛型函数
-
举例
func swapValues<T>(_ a: inout T, _ b: inout T) { //两个元组 (a, b) = (b, a) } var i1 = 10 var i2 = 20 swapValues(&i1, &i2) var d1 = 10.0 var d2 = 20.0 swapValues(&d1, &d2) struct Date { var year = 0, month = 0, day = 0 } var dd1 = Date(year: 2011, month: 9, day: 10) var dd2 = Date(year: 2012, month: 10, day: 11) swapValues(&dd1, &dd2) -
泛型函数赋值给变量
func test<T1, T2>(_ t1: T1, _ t2: T2) {} //必须明确泛型的具体类型,才能赋值 var fn: (Int, Double) -> () = test
泛型类型
-
举例
class Stack<E> { var elements = [E]() func push(_ element: E) { elements.append(element) } func pop() -> E { elements.removeLast() } func top() -> E { elements.last! } func size() -> Int { elements.count } } 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.pop()) // 11 print(stack.size()) // 0 //继承 class SubStack<E> : Stack<E> {} //struct struct Stack<E> { var elements = [E]() //必须加mutating mutating func push(_ element: E) { elements.append(element) } mutating func pop() -> E { elements.removeLast() } func top() -> E { elements.last! } func size() -> Int { elements.count } } //enum enum Score<T> { case point(T) case grade(String) } let score0 = Score<Int>.point(100) let score1 = Score.point(99) let score2 = Score.point(99.5) let score3 = Score<Int>.grade("A")
关联类型(Associated Type)
- 协议只能使用关联类型实现泛型的效果
- 关联类型的作用:给协议中用到的类型定义一个占位名称
-
协议中可以拥有多个关联类型
protocol Stackable { associatedtype Element // 关联类型 mutating func push(_ element: Element) mutating func pop() -> Element func top() -> Element func size() -> Int } class StringStack : Stackable { // 给关联类型设定真实类型 // typealias Element = String var elements = [String]() func push(_ element: String) { elements.append(element) } func pop() -> String { elements.removeLast() } func top() -> String { elements.last! } func size() -> Int { elements.count } } var ss = StringStack() ss.push("Jack") ss.push("Rose") //将类的泛型,与协议的关联类型设置一致 class Stack<E> : Stackable { // typealias Element = E var elements = [E]() func push(_ element: E) { elements.append(element) } func pop() -> E { elements.removeLast() } func top() -> E { elements.last! } func size() -> Int { elements.count } }
类型约束
-
对泛型的约束,举例
protocol Runnable { } class Person { } //T必须是Person类,且遵守Runnable协议 func swapValues<T : Person & Runnable>(_ a: inout T, _ b: inout T) { (a, b) = (b, a) } protocol Stackable { associatedtype Element: Equatable } //E必须遵守Equatable协议 class Stack<E : Equatable> : Stackable { typealias Element = E } //用where加限制 func equal<S1: Stackable, S2: Stackable>(_ s1: S1, _ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element : Hashable { return false } var stack1 = Stack<Int>() var stack2 = Stack<String>() // error: requires the types 'Int' and 'String' be equivalent equal(stack1, stack2)
协议类型的注意点
-
举例
protocol Runnable {} class Person : Runnable {} class Car : Runnable {} func get(_ type: Int) -> Runnable { if type == 0 { return Person() } return Car() } var r1 = get(0) var r2 = get(1) -
如果协议中有associatedtype
protocol Runnable { associatedtype Speed var speed: Speed { get } } class Person : Runnable { var speed: Double { 0.0 } } class Car : Runnable { var speed: Int { 0 } } //下面报错,因为编译器不知道Speed到底是什么类型,Double还是Int? func get(_ type: Int) -> Runnable { if type == 0 { return Person() } return Car() } var r1 = get(0) var r2 = get(1) -
解决方案
-
方案1:使用泛型
func get<T : Runnable>(_ type: Int) -> T { if type == 0 { return Person() as! T } return Car() as! T } var r1: Person = get(0) var r2: Car = get(1) -
方案2: 不透明类型(Opaque Type),不希望外部知道具体是什么类型
//使用some关键字声明一个不透明类型 func get(_ type: Int) -> some Runnable { //some限制只能返回一种类型,因此只能返回Car()或者Person() //如果只返回一种类型,那编译器就知道speed具体是哪种类型了,因此不报错 /* if type == 0 { return Person() } */ return Car() } var r1 = get(0) var r2 = get(1)
-
some
-
some除了用在返回值类型上,一般还可以用在属性类型上
protocol Runnable { associatedtype Speed } class Dog : Runnable { typealias Speed = Double } class Person { //不想让外界知道这个人具体有什么样的宠物 var pet: some Runnable { return Dog() } }
可选项的本质
-
可选项的本质是enum类型
public enum Optional<Wrapped> : ExpressibleByNilLiteral { case none //空值 case some(Wrapped) //非空值 public init(_ some: Wrapped) } var age: Int? = 10 var age0: Optional<Int> = Optional<Int>.some(10) var age1: Optional = .some(10) var age2 = Optional.some(10) var age3 = Optional(10) age = nil age3 = .none var age: Int? = nil var age0 = Optional<Int>.none var age1: Optional<Int> = .none var age: Int? = .none age = 10 age = .some(20) age = nil switch age { case let v?: //本质将可选项age强制解包,如果有值复制给v。否则进入下一个case print("some", v) case nil: print("none") } //等价上面 switch age { case let .some(v): print("some", v) case .none: print("none") } var age_: Int? = 10 var age: Int?? = age_ age = nil var age0 = Optional.some(Optional.some(10)) age0 = .none var age1: Optional<Optional> = .some(.some(10)) age1 = .none var age: Int?? = 10 var age0: Optional<Optional> = 10