Swift5 基础(五)协议、错误处理、泛型
一、协议(Protocol)
定义
协议可以用来定义方法、属性、下标的声明,协议可以被枚举、结构体、类遵守(多个协议之间用逗号隔开)
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}
}
class Person: Drawable{
var x: Int = 0
var y: Int = 0
func draw() {
print("begin drawing")
}
subscript(index: Int) -> Int {
set {}
get {index}
}
}
class Person1: Drawable{
var x: Int {
get {0}
set {}
}
var y: Int {0}
subscript(index: Int) -> Int {
get {
index
}
set {
}
}
func draw() {
print("begin drawing")
}
}
类型方法、类型属性
为了保证通用,协议中必须用static
定义类型方法、类型属性、类型下标
protocol Drawable{
static var pencil: Int{get set}
static func draw()
}
class Person1: Drawable{
static var pencil: Int = 1
static func draw() {
print("draw")
}
}
class Person2: Drawable{
static var pencil: Int = 2
class func draw() {
print("draw")
}
}
mutating
只有将协议中的实例方法标记为mutating
,才允许结构体、枚举的具体实现修改自身内存;类在实现方法时不用加mutating
,枚举和结构体才需要加mutating
protocol Drawable{
mutating func draw()
}
class Person: Drawable{
var age = 20
func draw() {
age += 1
}
}
struct Point: Drawable{
var x: Int = 0
mutating func draw() {
x += 10
}
}
init
协议中还可以定义初始化器init
,非final
类实现时必须加上required
protocol Drawable{
init(x: Int,y: Int)
}
class Point: Drawable{
required init(x: Int, y: Int) {
}
}
final class Size: Drawable{
init(x: Int, y: Int) {
}
}
如果从协议实现的初始化器,刚好是重写了父类的指定初始化器,那么这个初始化必须同时加required
、override
protocol Livable{
init(age: Int)
}
class Person {
init(age: Int){ }
}
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 {
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 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: Livable & Runnable & Person) { }
//可以定义一个别名来用
typealias RealPerson = Livable & Runnable & Person
func fn4(obj: RealPerson) { }
CaseIterable
让枚举遵守CaseIterable
协议,可以实现遍历枚举值
enum Season{
case spring,summer,autumn,winter
}
extension Season: CaseIterable{
}
let allSeasons = Season.allCases
print(allSeasons.count) // 4
for season in allSeasons {
print(season)
}
//spring
//summer
//autumn
//winter
CustomStringConvertible
遵守CustomStringConvertible
协议,可以自定义实例的打印字符串
class Person: CustomStringConvertible{
var age: Int = 14
var name: String = "Tom"
var description: String{
"name = \(name), age = \(age)"
}
}
var p = Person()
print(p)//name = Tom, age = 14
Any、AnyObject
Swift提供了两种特殊的类型:Any
、AnyObject
-
Any
:可以代表任意类型(枚举、结构体、类,也包括函数类型) -
AnyObject
:可以代表任意类类型(在协议后面写上AnyObject
代表只有类能遵守这个协议)
var obj: Any = 10
obj = "Jack"
obj = NSObject()
var data = [Any]()
data.append(1)
data.append(3.2)
data.append(NSObject())
data.append("Tom")
data.append({10})
is、as?、as!、as
is
用来判断是否为某种类型,as
用来做强制类型转换
protocol Driveable {
func drive()
}
class Person {
func eat() {
print(#function)
}
}
class Driver: Person,Driveable{
func drive() {
print("drive")
}
}
var d: Any = 10
print(d is Int)//true
d = "hello"
print(d is String)//true
d = Driver()
print(d is Person)//true
print(d is Driver)//true
print(d is Driveable)//true
d = 10
(d as? Driver)?.eat() //没有调用eat方法
d = Driver()
(d as? Driver)?.eat() //调用了eat 打印为:eat()
(d as! Driver).eat() //调用了eat 打印为:eat()
(d as? Driveable)?.drive()//调用了drive,打印为drive
X.self、X.Type、AnyClass
X.self
是一个元类型(metadata)的指针,metadata存放着类型相关信息
X.self
属于X.Type
类型
protocol Driveable {
func drive()
}
class Person {
func eat() {
print(#function)
}
}
class Driver: Person,Driveable{
func drive() {
print("drive")
}
}
var perType: Person.Type = Person.self
var driType: Driver.Type = Driver.self
var driableType: Driveable.Protocol = Driveable.self
var anyType: AnyObject.Type = Person.self
anyType = Driver.self
public typealias AnyClass = AnyObject.Type
var anyType2: AnyClass = Person.self
anyType2 = Driver.self
var per = Person()
perType = type(of: per)
print(Person.self == type(of: per))//true
可以根据元类型批量操作
class Animal {
var age: Int = 0
required init(){
}
}
class Cat: Animal { }
class Dog: Animal { }
class Pig: Animal { }
func create(_ clses: [Animal.Type]) -> [Animal] {
var arr = [Animal]()
for cls in clses {
arr.append(cls.init())
}
return arr
}
我们查看下内存占用与父类指向
class Person{
var age: Int = 0
}
class Student: Person {
var no: Int = 0
}
print(class_getInstanceSize(Student.self))//32
print(class_getSuperclass(Student.self)!)//Person
print(class_getSuperclass(Person.self)!)//_TtCs_SwiftObject
从结果可以看出,Swift的类有个隐藏的基类的,这里我们在后面的进阶中进行讨论。
Self
Self
一般用作返回值类型,限定返回值跟方法调用者必须是同一类型(也可以作为参数类型)
protocol Driveable{
func drive() -> Self
}
class Person: Driveable{
required init() { }
func drive() -> Self {
type(of: self).init()
}
}
class Student: Person { }
var p = Person()
print(p.drive())//Person
var s = Student()
print(s.drive())//Student
二、错误处理
自定义错误
Swift中可以通过Error
协议自定义运行时的错误信息
enum SomeError: Error{
case illeaglArg(String)
case outOfBounds(Int,Int)
case outOfMemory
}
函数内部通过throw
抛出自定义Error,可能会抛出Error的函数必须加上throws
声明
func divide(_ num1: Int,_ num2: Int) throws -> Int{
if num2 == 0 {
throw SomeError.illeaglArg("0不能作为除数")
}
return num1 / num2
}
需要使用try
调用可能会抛出Error的函数
var result = try divide(30, 10)
do-catch
可以使用do-catch
捕捉Error
func testError(){
print("1")
do {
print("2")
print(try divide(10, 0))
print("3")
} catch let SomeError.illeaglArg(msg) {
print("参数异常:",msg)
} catch let SomeError.outOfBounds(size,index){
print("下标越界","size=\(size)","index=\(index)")
} catch SomeError.outOfMemory{
print("内存溢出")
} catch {
print("其他错误")
}
print("4")
}
testError()
/*
打印结果
1
2
参数异常: 0不能作为除数
4
*/
从上面的打印结果来看,抛出Error后,try下一句直到作用域结束的代码都将停止运行
另外,除了上例这样接收错误外,也可以这样接收
do {
try divide(10, 0)
} catch let error {
switch error {
case let SomeError.illeaglArg(msg):
print("参数异常:",msg)
default:
print("其它错误")
}
}
处理Error
处理Error
有两种方式:
- 通过
do-catch
捕捉Error
- 不捕捉
Error
,在当前函数增加throws
声明,Error
将自动抛给上层函数, 如果最顶层函数(main函数)依然没有捕捉Error
,那么程序将终止
func testThrows() throws {
print("1")
print(try divide(10, 0))
print("2")
}
try testThrows()
//1
//Fatal error: Error raised at top level
想要调用会抛出异常的函数,又不想处理异常的话,可以使用try?
来调用
func testThrows() throws {
print("1")
print(try divide(10, 0))
print("2")
}
try? testThrows()
//1
这里打印到1就终止了,因为后面抛出了异常
这样调用程序是不会报错的,但是它还是没有处理异常,所以我们可以这样
func testThrows() throws {
print("1")
do {
print("2")
print(try divide(10, 0))
print("3")
} catch let error as SomeError {
print(error)
}
print(4)
}
try testThrows()
/*
打印结果
1
2
illeaglArg("0不能作为除数")
4
*/
try?、try!
可以使用try?
、try!
调用可能会抛出Error
的函数,这样就不用去处理Error
func testThrows() throws {
print("1")
print(try? divide(20, 10))
print(try? divide(20, 0))
print(try! divide(20, 10))
print("2")
}
try testThrows()
/*
打印结果
1
Optional(2)
nil
2
2
*/
下面的语句中,a、b是等价的
var a = try? divide(20, 0)
var b: Int?
do {
try divide(20, 0)
} catch {
b = nil
}
rethrows
rethrows
表明:函数本身不会抛出错误,但调用闭包参数抛出错误,那么它会将错误向上抛
func exec(_ fn: (Int,Int) throws -> Int,_ num1: Int,_ num2: Int) rethrows{
try fn(num1,num2)
}
try exec(divide(_:_:), 20, 0)
/*
打印结果
execution terminated: An error was thrown and was not caught:
▿ SomeError
- illeaglArg : "0不能作为除数"
*/
defer
defer
语句:用来定义以任何方式(抛错误、return
等)离开代码块前必须要执行的代码
defer
语句将延迟至当前作用域结束之前执行
func testDefer() {
defer {
print("这里是defer定义的代码")
}
print("end")
}
testDefer()
/*
打印结果
end
这里是defer定义的代码
*/
defer
语句的执行顺序与定义顺序相反
func fn1() {print("fn1")}
func fn2() {print("fn2")}
func test(){
defer { fn1() }
defer { fn2() }
print("end")
}
test()
/*
打印结果
end
fn2
fn1
*/
即使是抛错误、return等,也要在离开代码块前必须执行defer中的代码
func open(_ fileName: String) -> Int{
print("open")
return 0
}
func close(_ file: Int) {
print("close")
}
func processFile(_ fileName: String) throws{
let file = open(fileName)
defer {
close(file)
}
try divide(20, 0)
}
try processFile("fileName")
/*
打印结果
open
close
Playground execution terminated: An error was thrown and was not caught:
▿ SomeError
- illeaglArg : "0不能作为除数"
*/
三、泛型(Generics)
定义
泛型可以将类型参数化,提高代码复用率,减少代码量
//定义一个交换方法,可适用任何类型
func swapValues<T>(_ a: inout T, _ b: inout T) {
(a,b) = (b,a)
}
//交换Int类型
var i1 = 20
var i2 = 10
swapValues(&i1, &i2)
//交换Double类型
var d1 = 10.0
var d2 = 20.0
swapValues(&d1, &d2)
//交换结构体类型
struct Date{
var year = 0, month = 0, day = 0
}
var dd1 = Date(year: 2020, month: 9, day: 10)
var dd2 = Date(year: 2021, month: 2, day: 18)
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 }
}
继承
class SubStack<E>: Stack<E> { }
使用结构体类型定义栈
struct Stack<E> {
var elements = [E]()
mutating func push(_ element: E) { elements.append(element)}
mutating 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)
stack.push(44)
print(stack.top())//44
print(stack.pop())//44
print(stack.pop())//33
print(stack.pop())//22
print(stack.size())//1
泛型用在枚举上
enum Score<T> {
case point(T) //分数
case grade(String) //等级
}
let s1 = Score<Int>.point(100)
let s2 = Score.point(99)
let s3 = Score.point(99.5)
let s4 = Score<Int>.grade("A")
关联类型(Associated Type)
关联类型的作用:给协议中用到的类型定义一个占位名称
协议中可以拥有多个关联类型
//栈能力的协议
protocol Stackable {
associatedtype Element //关联类型
var elements: [Element] {get}
mutating func push(_ element: Element)
mutating func pop() -> Element
func top() -> Element
func size() -> Int
}
//实现通用类型的栈
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
}
}
//实现字符串类型的栈
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("LiLei")
ss.push("HanMeiMei")
类型约束
protocol Runnable { }
class Person { }
func swapValues<T: Person & Runnable>(_ a: inout T,_ b: inout T){
(a,b) = (b,a)
}
protocol Stackable{
associatedtype Element: Equatable
}
class Stack<E: Equatable>: Stackable {
typealias Element = E
}
func equal<S1: Stackable,S2: Stackable>(_ s1: S1,_ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element: Hashable{
false
}
var s1 = Stack<Int>()
var s2 = Stack<String>()
// error: Global function 'equal' requires the types 'Stack<Int>.Element' (aka 'Int') and 'Stack<String>.Element' (aka 'String') be equivalent
equal(s1, s2)
协议类型的注意点
普通的协议使用
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 }
}
//error: Protocol 'Runnable' can only be used as a generic constraint because it has Self or associated type requirements
func get(_ type: Int) -> Runnable {
if type == 0{
return Person()
}
return Car()
}
这时候报错,意思是说Runnable
协议只能用作泛型约束,因为它具有 Self
或关联的类型要求。
解决方案①:使用泛型
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: Person = get(1)
解决方案②:使用不透明类型(Opaque Type)
some
关键字可声明一个不透明类型,但是some
限制只能返回一种类型
也就是说,这样会报错
//error: Function declares an opaque return type, but the return statements in its body do not have matching underlying types
func get(_ type: Int) -> some Runnable {
if type == 0{
return Person()
}
return Car()
}
所以只能这样写
func get(_ type: Int) -> some Runnable {
return Car()
}
some
some
除了用在返回值类型上,一般还可以用在属性类型上
protocol Runnable {
associatedtype Speed
}
class Dog: Runnable {
typealias Speed = Double
}
class Person {
var pet: some Runnable{
Dog()
}
}
可选项的本质
可选项的本质是enum类型
从定义可以看到
@frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral {
/// The absence of a value.
///
/// In code, the absence of a value is typically written using the `nil`
/// literal rather than the explicit `.none` enumeration case.
case none
/// The presence of a value, stored as `Wrapped`.
case some(Wrapped)
/// Creates an instance that stores the given value.
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? = 10
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?:
print("some",v)
case nil:
print("none")
}
switch age {
case let .some(v):
print("some",v)
case .none:
print("none")
}
var age_: Int? = .none
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