Swift5 基础(五)协议、错误处理、泛型

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) {
        
    }
}

如果从协议实现的初始化器,刚好是重写了父类的指定初始化器,那么这个初始化必须同时加requiredoverride

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提供了两种特殊的类型:AnyAnyObject

  • 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(_ 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: T1,_ t2: T2) {}
var fn: (Int, Double) -> () = test

使用泛型定义栈

class Stack {
    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: Stack { }

使用结构体类型定义栈

struct Stack {
    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()
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 {
    case point(T) //分数
    case grade(String) //等级
}
let s1 = Score.point(100)
let s2 = Score.point(99)
let s3 = Score.point(99.5)
let s4 = Score.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: 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(_ a: inout T,_ b: inout T){
    (a,b) = (b,a)
}

protocol Stackable{
    associatedtype Element: Equatable
}
class Stack: Stackable {
    typealias Element = E
}

func equal(_ s1: S1,_ s2: S2) -> Bool where S1.Element == S2.Element, S1.Element: Hashable{
    false
}
var s1 = Stack()
var s2 = Stack()
// error: Global function 'equal' requires the types 'Stack.Element' (aka 'Int') and 'Stack.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(_ 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 : 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 = Optional.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.none
var age1: Optional = .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 = .some(.some(10))
age1 = .none
var age: Int?? = 10
var age0: Optional = 10

你可能感兴趣的:(Swift5 基础(五)协议、错误处理、泛型)