Swift5.2 拾遗笔记(二)

本文为私人学习笔记,仅仅做为记录使用,详情内容请查阅 中文官方文档。


错误处理

定义和抛出异常

遵循 Error 协议的类型表示可以用于错误处理。

enum VendingMachineError: Error {
    case invalidSelection                     //选择无效
    case insufficientFunds(coinsNeeded: Int) //金额不足
    case outOfStock                             //缺货
}

不符合条件时,用关键字 trow 来抛出异常。

throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

四种处理

  • 传递给调用者
  • do-catch 捕获处理
  • try? 将错误作为可选类型
  • try! 断言此错误不会发生

传递给调用者

可以在函数声明的参数之后返回值之前加上关键字 throws,该函数也被称为 throwing 函数。

func cannotThrowErrors() -> String
func canThrowErrors() throws  // 无返回值
func canThrowErrors() throws -> String // 有返回值

一个 throwing 函数内部也可以抛出错误,将错误传递到调用者的作用域。并且也只有 throwing 函数可以传递错误,普通函数只能在函数内部处理错误。

struct Item {
    var price: Int
    var count: Int
}

class VendingMachine {
    var inventory = [
        "Candy Bar": Item(price: 12, count: 7),
        "Chips": Item(price: 10, count: 4),
        "Pretzels": Item(price: 7, count: 11)
    ]
    func vend(itemNamed name: String) throws {
        guard let item = inventory[name] else {
            throw VendingMachineError.invalidSelection
        }
        print("Dispensing \(name)")
    }
}

throwing 抛出错误:

let favoriteSnacks = [
    "Alice": "Chips",
    "Bob": "Licorice",
    "Eve": "Pretzels",
]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
    let snackName = favoriteSnacks[person] ?? "Candy Bar"
    try vendingMachine.vend(itemNamed: snackName)
}

do-catch 捕获处理

do {
    try expression
    statements
} catch pattern 1 {
    statements
} catch pattern 2 where condition {
    statements
} catch {
    statements
}

catch 子句不必将 do 子句中的代码所抛出的每一个可能的错误都做处理,即所有错误不必都捕获处理。如果列出的所有 catch 子句都没有处理错误,那么这个错误就被传递到周围的作用域,并该错误依旧需要周围作用域处理。

在普通的非 throwing 函数中,必须用 do-catch 语句处理错误。而 throwing 函数中,还可以将错误继续传递抛出,让调用者来处理错误,如果错误没有传递到最顶层作用域却依然没有被处理,那么会得到一个运行时错误。

func nourish(with item: String) throws {
    do {
        try vendingMachine.vend(itemNamed: item)
    } catch is VendingMachineError { // 只处理一种类型的错误,如果是其他错误类型就会继续抛出
        print("Invalid selection, out of stock, or not enough money.")
    }
}

do {
    try nourish(with: "Beet-Flavored Chips")
} catch {
    print("Unexpected non-vending-machine-related error: \(error)")
}
// 打印“Invalid selection, out of stock, or not enough money.”

try? 将错误作为可选类型

try? 可以将错误转换为可选类型,如果没有发生错误,那么你会得到一个可选类型的返回值,如果发生错误,那么你可以不做 do-catch 捕获处理,而只得到一个 nil 返回值。

func someThrowingFunction() throws -> Int {
    // ...
}

let x = try? someThrowingFunction()

x 的结果将是 Int? 可选类型或者是 nil 值。上述 x 语句等同于下面的语句:

let x: Int?
do {
    x = try someThrowingFunction()
} catch {
    x = nil
}

try! 断言此错误不会发生

当你明确知道 throwing 函数运行结果是一个确定值时,可以使用 try! 来禁用错误传递。但是如果抛出了错误,则会得到一个运行时错误,其作用是将 try? 的可选结果上进行了强解操作。

延迟操作

有时我们需要在工作完成结束之后完成一些善后工作,例如在打开文件进行读写之后,我们需要关闭文件。defer 关键字让语句在即将离开当前代码块时执行一系列的善后清理工作,不管是以何种方式离开当前代码块的,如抛出错误离开、returnbreak 等语句,defer 都能将代理的执行延迟到当前作用域退出之前。延迟执行的语句不能包含任何控制转移语句,例如 breakreturn 语句,或是抛出一个错误。

func processFile(filename: String) throws {
    if exists(filename) {
        let file = open(filename)
        defer { 
            close(file)
        }
        while let line = try file.readline() {
            // 处理文件。
        }
        // close(file) 会在这里被调用,即作用域的最后。
    }
}

有了 defer 代码块之后,我们就可以将文件操作最后的关闭操作放在其中。defer 的好处就是能将善后工作统一,有利于后期改造时遗忘而导致错误的发生。

即使在普通的函数代码中,你也可以使用 defer 语句。

扩展

使用 extension 关键字声明扩展:

extension SomeType {
  // 在这里给 SomeType 添加新的功能
}

Swift 中扩展的功能很多,具体可以:

  • 添加计算型属性
  • 添加方法
  • 提供新的构造器(便利构造器)
  • 定义下标
  • 定义和使用新的嵌套类型
  • 遵循协议

注意

扩展可以给一个类型添加新的功能,但是不能重写已经存在的功能。
扩展可以添加新的计算属性,但是不能添加存储属性,或者向现有的属性添加属性观察者。
扩展可以给现有的类型添加新的构造器。它使你可以把自定义的类型提供给其他类型的构造器使用,或者只作为额外构造选项。
扩展可以给一个类添加新的便利构造器,但是不能给类添加新的指定构造器或者析构器指定构造器或者析构器只能由原始提供。
如果你通过扩展提供一个新的构造器,那么你由责任确保每个通过该构造器创建的实例都是初始化完整的。

协议

类、结构体或枚举都可以遵循协议,并为协议定义的这些要求提供具体实现。

对属性的要求

协议不指定属性是存储属性还是计算属性,它只指定属性的名称和类型,此外,协议还可以指定属性是可读还是可读可写的。

协议总是用 var 关键字来声明变量属性。

protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}

对方法的要求

协议不支持方法提供默认参数。类方法的要求依然适用。

对异变方法的要求

实现协议中的 mutating 方法时,若是类,则不用写 mutating 关键字,而对于结构体和枚举,则必须写 mutating 关键字。

协议作为类型

尽管协议本身并未实现任何功能,但是协议可以被当做一个功能完备的类型来使用。协议可以像其他普通类型一样使用,使用场景如下:

  • 作为函数、方法或构造器中的参数、返回值
  • 作为常量、变量或者属性
  • 作为数组、字典或其他容器的元素

协议和委托

委托是一种常见的设计模式,它允许类或者结构体将一些需求功能委托给其他类型的实例。
委托模式实现是这样的:定义协议来封装那些需要被委托的功能,确保遵循该协议的类型(被委托者)能够提供这些功能。
委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的提供者。

通过扩展让类型遵循协议

上一节中提到了扩展可以另已有的类型遵循并符合协议。
通过扩展是的已有类型遵循协议时,该类型的所有实例(包括之前创建使用的实例)都随之获得协议中定义的各项功能。

当一个类型已经遵循了某个类型中的所有要求,但是没有声明遵循该协议时,可以通过扩展显示的遵循该协议。

protocol TextRepresentable {
    var textualDescription: String { get }
}

struct Hamster {
    var name: String
       var textualDescription: String {
        return "A hamster named \(name)"
    }
}
extension Hamster: TextRepresentable {}

从现在起,Hamster 的实例可以作为 TextRepresentable 类型使用。

协议合成

协议组合使用 SomeProtocol & AnotherProtocol 的形式。你可以列举任意数量的协议,用 & 符号分开。

protocol Named {
    var name: String { get }
}
protocol Aged {
    var age: Int { get }
}
func wishHappyBirthday(to celebrator: Named & Aged) {
    print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}

检查协议一致性

isas 操作符来检查协议的一致性,转换到指定的协议类型。

可选协议

optional 关键字作为前缀来定义可选项。可选项用在你需要和 OC 打交道的代码中时,协议和可选项都必须带上 @objc 属性。标记 @objc 特性的协议只能被继承自 OC 的类或者 @objc 类遵循,其他类以及结构体或者枚举均不能遵循这种协议。

@objc protocol CounterDataSource {
    @objc optional func increment(forCount count: Int) -> Int
    @objc optional var fixedIncrement: Int { get }
}

为协议提供默认实现

Swift 可以通过扩展来为协议中的方法、计算属性提供默认的实现。如果遵循协议的类型也为这些方法、计算属性提供了自己的实现,那么这些实现将会替代扩展中的默认实现。

和协议中的可选项不同,提供默认实现的可以直接调用,而不需要使用可选链式调用。

// 协议的默认实现
extension PrettyTextRepresentable  {
    var prettyTextualDescription: String {
        return textualDescription
    }
}

为协议扩展添加限制条件

在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,可能获得协议扩展提供的默认实现。限制条件写在协议名之后,使用 where 子句来描述。

extension Collection where Element: Equatable {
    func allEqual() -> Bool {
        for element in self {
            if element != self.first {
                return false
            }
        }
        return true
    }
}

上述例子扩展 Collection 协议,适用于集合中的元素遵循了 Equatable 协议的情况。

你可能感兴趣的:(Swift5.2 拾遗笔记(二))