SwiftNote-错误处理和代码访问权限

416 阅读6分钟

1.错误处理

1.1 错误表示

在 Swift 中如果我们要定义一个表示错误的类型非常简单,只需要遵循 Error 协议即可,我们通常使用枚举或结构体来表示错误类型,枚举使用更多,因为它能更加直观地表达当前错误类型的每种错误细节。 以下面的自动售货机错误类型来举例说明:

// 自动售货机错误枚举
enum VendingMachineError: Error {
    // 选择了错误的商品
    case invalidSeledtion
    // 付款金额错误(投币个数不够)
    case inSufficientFunds(coinsNeeded: Int)
    // 商品售完了
    case outOfStock
}

1.2 抛出错误

函数、方法和初始化器都可以抛出错误。需要在参数列表后面,返回值前面加 throws 关键字。

// 抛出错误的函数
func canThrowErrors() throws -> String
// 不抛出错误的函数
func cannotThrowErrors() -> String

1.3 使用 Do-Catch 做错误处理

在 Swift 中我们使用 do-catch 块对错误进行捕获,当我们在调用一个 throws 声明的函数或方法时,我们必须把调用语句放在 do 语句块中,同时 do 语句块后面紧跟着使用 catch 语句块。

do-catch

1.4 try?

try? 会将错误转换为可选值,当调用 try? + 函数或方法语句时,如果函数或方法抛出错误,程序不会崩溃,而是会返回一个 nil,如果没有抛出错误则返回可选值。

func someThrowingFunction() throws -> Int {
    // ...
}
let x = try? someThrowingFunction()

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

1.5 try!

如果你确信一个函数或方法不会抛出错误,可以使用 try! 来终端错误的传播。但是如果错误真的发生了,你会得到一个运行时错误。

let photo = try! loadImage(atPath: "./Resource/circle.png")

1.6 指定退出的清理工作

defer 关键字:defer block 里的代码火灾函数 return 之前执行,无论程序是因为异常而中止或其它方式返回终止的,defer block 块的内容一定会被执行 。类似于 Java 中的 finally

// 以处理文件为例,不管怎样最后 defer 代码块里的 close(file) 关闭文件都会执行。
func processFile(fineName: String) throws {
    if exists(fileName) {
        let file = open(fileName)
        defer {
            close(file)
        }
        while let line = try file.readline() {
            // work with the file
        }
        // close(file) is called here, at the end of the scope.
    }
}

示例:

// 自动售货机错误枚举
enum VendingMachineError: Error {
    // 选择了错误的商品
    case invalidSeledtion
    // 付款金额错误(投币个数不够)
    case inSufficientFunds(coinsNeeded: Int)
    // 商品售完了
    case outOfStock
}

// 定义商品模型
struct Item {
    var price: Int    // 价格
    var count: Int    // 数量
}

// 定义自动售货机
class VendingMachine {
    
    // 商品列表
    var goods = [
        "Candy Bar": Item(price: 1, count: 15),
        "Chips": Item(price: 3, count: 6),
        "Bread": Item(price: 2, count: 10)
    ]
    
    // 投币个数
    var coinDesposited: Int = 0
    
    // 售卖商品的方法
    func vend(productName: String) throws {
        
        defer {
            // 不管售卖成功还是发生错误, 这行代码都会执行!
            print("退出清理!")
        }
        print("开始售卖~")
        
        guard let item = goods[productName] else {
            // 如果该商品不存在, 则抛出选择了无效商品的错误
            throw VendingMachineError.invalidSeledtion
        }
        
        guard item.count > 0 else {
            // 该商品没货了
            throw VendingMachineError.outOfStock
        }
        
        guard coinDesposited >= item.price else {
            // 投币不足
            throw VendingMachineError.inSufficientFunds(coinsNeeded: item.price - coinDesposited)
        }
        
        // 付款购买
        coinDesposited -= item.price
        // 更新商品列表, 该商品数量减一
        var newItem = item
        newItem.count -= 1
        goods[productName] = newItem
        
        print("售卖成功!")
    }
}

let machine = VendingMachine()
machine.coinDesposited = 1
    
do {
    try machine.vend(productName: "Bread")
} catch VendingMachineError.invalidSeledtion {
    print("No such product")
} catch VendingMachineError.inSufficientFunds(let coinsNeeded) {
    print("You need more coins: \(coinsNeeded)")
} catch VendingMachineError.outOfStock {
    print("Out of stock")
} catch {
    print("Unexpected Eroor occured!")
}
// 执行结果:
// 开始售卖~
// 退出清理!
// You need more coins: 1

2.代码访问权限控制

2.1 代码组织单元

Swift 的代码组织单元有两块:模块和源文件

  • 模块指的是独立的代码分发单元,框架或应用程序会作为一个独立的模块来构建和帆布。在 Swift 中一个模块可以使用 import 关键字来导入另一个模块。
  • 源文件就是 Swift 中的源代码文件,它通常属于一个模块,寄一个应用程序或框架。尽管我们一般会将不同的类型分别定义在不用的源文件中,但是同一个源文件也可以包含多个类型、函数之类的定义。

2.2 代码访问级别

代码访问级别公开级别以此降低,排序如下:

  1. open:公开权限,最高的权限,可以被其他模块访问、继承和复写。只能用于类和类成员。

  2. public:公有访问权限,类或者类的公有属性或公共方法可以从文件或模块的任何地方进行访问。

    那么什么样才能成为一个模块呢?一个 App 就是一个模块,一个第三方 Api、第三方框架等都是一个完成的模块,这些模块如果要对外留有访问的属性或方法,就可以使用 public 访问权限。public 的权限在 Swift 3.0 后无法在其他模块被复写方法、属性或被继承。

  3. internal:内部访问,即有着 internal 访问权限的属性和方法说明在模块内可以访问,超出模块就不能访问了。在 Swift 中默认就是 internal 访问权限

  4. fileprivate:文件私有访问权限,被 fileprivate 修饰的类或者属性、方法可以在用一个物理文件中访问,若超出该物理文件就不能访问。

  5. private:私有访问权限,被 private 修饰的类或者类的属性或方法可以在同一个物理文件中的同一个类型(包含 extension)访问。若超出该物理文件或不属于同一类型就不能访问。


在访问级别中还有一些潜规则值得我们平时开发注意:

  • 如果一个类的访问级别是 fileprivateprivate 那么该类的所有成员都是 fileprivateprivate (此时成员无法修改访问级别),如果一个类的访问级别是 openinternalpublic 那么他的所有成员都是 internal,类成员的访问级别不能高于类的访问级别(注意:嵌套类型的访问级别也符合此条规则)。
  • 常量、变量、属性、下标脚本访问级别低于其所声明的类型级别,并且如果不是默认访问级别(internal)要明确声明访问级别(例如一个常量是一个 private 级别的类类型,那么此常量必须声明为 privatefileprivate)。
  • 在不违反 1、2 两天潜规则的情况下,setter 的访问级别可以低于 getter 的访问级别(例如一个属性访问级别是 internal,那么可以添加 private(set) 修饰将 setter 权限设置为 private,在当前模块中只有此源文件可以访问,对外就是只读的)。
  • 必要构造方法(required 修饰)的访问级别必须和类访问级别相同,结构体的默认逐一构造函数的访问级别不高于其成员的访问级别(例如一个成员是 private 那么这个构造函数就是 private,但是可以通过自定义声明一个 public 的构造函数),其他方法(包括其它构造方法和普通方法)的访问级别遵循潜规则 1。