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

推荐阅读更多精彩内容