Swift中的十分和错误管理1,学习笔记少年老成
分类:计算机网络

iOS9-by-Tutorials-学习笔记一:Swift-2-0

异常处理基础篇

只要我们在编程,就一定要面对错误处理的问题。其实,为了让我们少犯错误,Swift在设计的时候就尽可能让我们明确感知错误,明确处理错误。例如:

-只有使用Optional才能处理空值;

-switch...case...必须处理所有的请求;
总之,你处处能感受到Swift为你少犯错的良苦用心。所以,当你真的要处理错误的时候,Swift当然更会要求你严谨处理。

iOS9-by-Tutorials-学习笔记一:Swift-2-0

Apple在前段时间开源了Swift,在iOS开发领域中又制造了一阵骚动,看了一眼Swift的开发路线图,计划在明年的秋天发布Swift 3.0。Apple现在在Swift上变得也更加的开发,鼓励社区贡献代码,也开始接纳社区的一些反馈了。苹果改变以往的封闭的姿态,表明了它对于Swift语言的重视,同时也说明了Swift语言苹果会加大力度去优化,所以现在对于我们iOS开发人员来说,是时候开始学习iOS了。
前段时间也面试了几个人,简历里面好几个都写了精通Swift,但是一问问题好多都答不上来,简历上真的。。。。。更多的人貌似没有开始学Swift,但是最后我都建议他们去学习一下Swift。
扯远了,回到正题,这篇文章是我的学习笔记,非本人原创内容,只是在看《iOS 9 by Tutorials》这本书时候的一些笔记,然后加上自己的一些理解而已。

Swift 2中加入了几个(作者认为)比较重要的改进,如下:
* 新的控制流
* (相对)完善的错误处理模型
* 协议扩展
* 模式匹配的增强
* API可用性检测
* 其他一些。。。。。。

如何描述一个错误?

在Swift里,任何一个遵从ErrorType protocol的类型,都可以用于描述错误。ErrorType是一个空的protocol,它唯一的功能,就是告诉Swift编译器,某个类型用来表示一个错误。而通常,我们使用一个enum来定义各种错误。例如,假设我们有一个机器人类型,我们要定一个表达它工作状态的错误:

enum RobotError: ErrorType {
    case LowPower(Double)
    case Overload(Double)}

其中LowPower表示电量低,它的associated value表示电量的百分比。而Overload表示超过负载,它的associated value表示最大负载值。

控制流

在书中首先作者解释了一下控制流,感觉不错:程序中任何能够影响程序执行到不同的路径的结构或者关键字都可以叫做控制流,原文:any construct or keyword that causes the execution of your program to follow a different path can be considered “control flow”.

如何描述一个会发生错误的方法?

然后,我们来创建一个表示机器人的类:

class Robot {
     var power = 1.0
     let maxLifting = 100.0 // Kg
}

它有两个属性,power表示当前电量,maxLifting表示它可以举起来的最大质量。然后,我们添加一些可以发送给Robot的命令:

enum Command {
    case PowerUp
    case Lifting(Double)
    case Shutdown
}

Command中的三个case分别表示对Robot发送:启动、举重和关机三个命令。接下来,我们给Robot添加一个接受命令的方法 action。

class Robot {
    var power = 1.0
    let maxLifting = 100.0 // Kg

    func action(command: Command) throws { }
}

由于action有可能发生异常,对于这样的方法,我们要明确使用throws关键字标记它。在action的实现里,我们用一个switch...case来遍历Command:

class Robot {
    var power = 1.0
    let maxLifting = 100.0 // Kg

    func action(command: Command) throws {
        switch command {
        case .PowerUp:
            guard self.power > 0.2 else {
                throw RobotError.LowPower(0.2)
            }
            print("Robot started")
        case let .Lifting(weight):
            guard weight <= maxLifting else {
                throw RobotError.Overload(maxLifting)
            }
            print("Lifting weight: (weight) KG")
        case .Shutdown:
            print("Robot shuting down...")
        }
    }
}

在action的实现里,当处理.PowerUp命令时,我们使用了guard确保Robot电量要大于20%,否则,我们使用throw RobotError.LowPower(0.2)的方式抛出了一个异常(throw出来的类型必须是ErrorType)。
处理.Lifting命令时,我们读取了.Liftting的associated value,如果要举起的质量大于maxLifting,则throw RobotError.Overload(maxLifting)。
通常,guard和throw配合在一起,可以让我们的代码变的更加简洁。

repeat…while

repeat…while是重复的意思,类似于其他语言中的do…while。其实在Swift 1.x中还是使用的do…while,在2.x中为了与do…catch区分,所以改成了repeat,但是语义上还是没有变化。这里多说一句,Swift的好多改进,都是为了让程序读上去更加明确,例如Optional、guard等也有这方面的考虑。

本例子中的代码都是在Playground中实现的

var x = 2
repeat {
    print("x:(x)")
    x += 1 // Swift计划在3.0中移除 ++ -- 所以还是尽量少用吧
} while x < 10 // 这个地方可以添加括号

上面while后面可以不适用括号,这个也是Swift的一个改进,Swift中只有必要(即语义不明确)的时候才会要求必须加括号。

如何处理错误?

当我们调用了一个可能会抛出异常的方法时,我们一定要"通过某种方式"处理可能会发生的异常,如果你不处理,iOS会替你处理。当然,作为"代劳"的成本,iOS也会Kill掉你的app。因此,对于"业务逻辑类"的异常,我们还是自己处理好些,Swift允许我们使用三种方式处理异常。为了演示它们的用法,我们先来定义一个让Robot工作的函数,由于它会调用action,因此它也会抛出RobotError异常,我们也需要用throws来定义它:

func working(robot: Robot) throws {
}

guard

guard这个词我也不知道怎么翻译,这里就不翻译了。但是这个关键字的作用的就是一个先决条件的检测。先看下面的例子:

func printName(name: String) {
    guard !name.isEmpty else {
        print("no name")
        return
    }
    print(name)
}
printName("")
printName("MengXiangYue")

上面的例子是一个没有意义的例子,只是为了演示。定义了一个函数打印传入的名字,这个函数的要求如果传入的name为空,就判定程序错误,然后返回不执行代码。guard 后面跟一个条件,条件为真的时候不会执行else,当条件为假的时候将会执行else,这样就能够达到了我们的要求。但是可能又回说,我用一个if-else也能够实现这个功能,但是如果要是跟Optional结合在一起就比if-else方便多了,下面继续看这个例子:

func printName(inName: String?) { // 这里变成了可选值了
    guard let name = inName else {
        print("no name")
        return
    }
    guard !name.isEmpty else {
        print("no name")
        return
    }
    print(name)
}
printName("")
printName("MengXiangYue")

上面的例子中传入的参数是一个可选值,这时候使用『guard let name = _name else…』,这个类似于if let解包的方式,但是看下面我们使用guard声明的name变量,在下面是能够正常使用的,但是考虑如果使用if let这个就不能使用了,所以我认为guard结合Optional是使用起来最方便的。另外这个东西也可以实现类似NSAssert类似的功能,只是这个不会崩溃。

do...catch...

在working的实现里,首先,我们要让Robot"启动":

func working(robot: Robot) throws {
    do { 
       try robot.action(Command.PowerUp)
}
    catch let RobotError.LowPower(percentage){
       print("Low power: (percentage)")
    }
}

通过前面action的代码我们知道,如果传入的robot参数的"电量"低于20%,action会抛出异常,因此在working的实现里:

-我们必须在调用会抛出异常的方法前面使用try关键字;

-如果我们要捕获方法抛出的异常,就需要把会抛出异常的代码放在关键字do包含的代码块里;

-我们使用catch关键字匹配要捕捉的各种异常,例如在上面的例子里,我们捕捉了.LowPower,并且读取了它的associated value;
如果我们要捕获多个异常,就可以在do代码块后面,串联多个catch,例如,我们添加一个让Robot举起某个东西的命令:

func working(robot: Robot) throws {
    do {
        try robot.action(Command.PowerUp)
        try robot.action(Command.Lifting(52))
    }
    catch let RobotError.LowPower(percentage) {
        print("Low power: (percentage)")
    }
    catch let RobotError.Overload(maxWeight) {
        print("Overloading, max (maxWeight) KG is allowd")
    }
}

我们就需要在do后面多串联一个catch,用来捕获Robot"超载"的异常。

(相对)完善的错误处理模型

这里我加了一个相对,主要是指的相对于Swift 1.x,2.x的错误处理好用了不少,但是相比于java等其他部分语言,还是不完善,Swift中的错误处理,对于抛出错误来说,你只是知道该函数抛出了错误,但是不清楚这个函数抛出了什么错误,书中有句话写的很正确,这个要求写程序的时候一定要在文档中写明,会抛出的各种异常(在java中会明确的抛出Exception,Exception与Swift的Error功能一致)。

另外相对于Objective-C的NSError把指针传递进去,然后等函数执行完成之后检查,已经先进了不少,鼓掌。。。。。
定义下面的一个协议:

{% codeblock %} swift
protocol JSONParsable {
static func parse(json: [String: AnyObject]) throws -> Self
}

<code class=" hljs coffeescript">
这个协议定义了一个静态方法,这里不能叫做类方法,以为协议同时可以应用到Struct上,可以叫类型方法。这个函数使用了**throws** 关键字,这个关键字表示该方法可能会抛出一个错误,这里也看不出来抛出什么错误(你妹啊,啥错误都不知道),所以就更加突出这时候注释的重要性(可以写篇文章:论注释的重要性,哈哈哈)。   

那既然说到抛出错误,那我们就得定义错误,在Swift中定义错误比较容易,只要定义一个枚举类型,然后遵守**ErrorType** 协议就可以了。OC中的NSError同样也实现了**ErrorType** 协议,所以我们能够在OC和Swift中使用NSError没有问题。下面定义一个错误:

```swift
enum ParseError: ErrorType {
    case MissingAttribute(message: String)
}

</code>

定义一个错误比较简单,跟普通的枚举没什么不同,这里定义了一个有关联值的枚举。关联值这里要多扯一句,关联值这个东西在Swift中能够解决好多与类型相关的东西,有时候我们经常会遇到某个类型与值相关,比如我们自己的工程中,网络请求错误需要带着错误码和错误提示,这时候我在OC中可能需要返回三个参数,但是在Swift中我可以只是返回一个枚举,然后关联上另外的两个值。对于多个有关系的值,同样也可以使用元组,曾经看kingfisher的时候,作者把一个类的配置参数都放到一个元组里面,然后解析这个元组,这样参数可能更加清晰。
又扯远了,回到正题。下面我们实现一个结构体Person:

struct Person: JSONParsable {
    let firstName: String
    let lastName: String

    static func parse(json: [String : AnyObject]) throws -> Person {
        guard let firstName = json["first_name"] as? String else {
            let message = "Expected first_name String"
            throw ParseError.MissingAttribute(message: message) // 1
        }

        guard let lastName = json["last_name"] as? String else {
            let message = "Expected last_name String"
            throw ParseError.MissingAttribute(message: message) // 2
        }
        return Person(firstName: firstName, lastName: lastName)
    }
}

代码比较简单就不过多解释了,就是在不同情况下抛出不同的异常。我们在调用这个方法的时候,需要处理这些异常,这时候就使用到了Swift中的do…catch。下面是代码:

do {
    let person = try Person.parse(["foo": "bar"])
} catch ParseError.MissingAttribute(let message) {
        print(message)
} catch {
        print("Unexpected ErrorType")
}

do后面需要使用{}将抛出异常的函数包起来,调用抛出异常的方法的时候,需要使用try关键字,然后后面跟着需要捕获的异常,如果清楚需要捕获的异常的类型,可以再catch后面加上异常类型,如果没有异常类型,那表示捕获所有的异常。异常会按照catch的顺序挨个匹配,直到找到第一个匹配的结束。

如果我们对于异常不关心,我们可以使用try?、try!调用方法,其中try?调用方法会返回一个Optional值,如果调用成功将会返回对应的结果,如果失败则返回nil,程序一定不会崩溃,但是如果我们直接使用try!如果有异常抛出,程序将会崩溃。所以只有在保证我们调用的函数不会抛出异常的时候才能使用try!。

let p1 = try? Person.parse(["foo": "bar"])  // nil
let p2 = try! Person.parse(["first_name": "Ray", "last_name": "Wenderlich"]) // Person
let p3 = try! Person.parse(["foo": "bar"]) // error crash

错、不错都会执行的代码

在Swift的异常处理机制理,有一个允许我们添加无论代码执行正常与否,只要离开当前作用域,就一定会执行的代码。我们使用defer关键字来指定这样的代码。例如,我们给working添加一个defer,它用来让Robot关机。

func working(robot: Robot) throws {
    defer {
        try! robot.action(Command.Shutdown)
    }
    do {
        try robot.action(Command.PowerUp)
        try robot.action(Command.Lifting(52))
    }
    catch let RobotError.LowPower(percentage) {
        print("Low power: (percentage)")
    }
    catch let RobotError.Overload(maxWeight) {
        print("Overloading, max (maxWeight) KG is allowd")
    }
}

协议扩展

在这一部分使用一个例子来介绍协议扩展,协议扩展是在Swift 2.x中一个比较重要的思想。详细的可以看看WWDC 2015 Session 408了解。下面定义一个验证字符串规则的一个协议:

protocol StringValidationRule {
    func validate(string: String) throws -> Bool // 验证是否合法的方法
    var errorType: StringValidationError { get }  // error的类型
}

上面定义了校验规则的协议,下面定义一个校验器协议:

protocol StringValidator {
    var validationRules: [StringValidationRule] { get }
    func validate(string: String) -> (valid: Bool, errors: [StringValidationError])
}

StringValidator这个校验器,有一个保存校验规则的数组,然后有一个校验方法,返回一个元祖,包含最终的校验结果,及错误。这里我们考虑一下对于校验器可能我们处理的逻辑都是一样的,就是循环所有的校验规则,然后查看是否校验成功。这个逻辑算是比较一致,如果我们把这个放到每个实现该协议的类型里面,那代码可能会重复。这时候我们可以提供一个默认的实现,这就是协议扩展(类似于虚函数的功能)。

extension StringValidator {
    func validate(string: String) -> (valid: Bool, errors:[StringValidationError]) {

        var errors = [StringValidationError]()
        for rule in validationRules {
            do {
                try rule.validate(string)
            } catch let error as StringValidationError {
                errors.append(error)
            } catch let error {
                fatalError("Unexpected error type: (error)")
            }
        }
        return (valid: errors.isEmpty, errors: errors)
    }
}

下面我们实现一个字符串以某些字符开始和以某些字符结束的的规则。首先定义一下上面的StringValidationError

// 错误类型
enum StringValidationError: ErrorType {
    case MustStartWith(set: NSCharacterSet, description: String)
    case MustEndWith(set: NSCharacterSet, description: String)
    var description: String {
      let errorString: String
      switch self {
      case .MustStartWith(_, let description):
        errorString = "Must start with (description)."
      case .MustEndWith(_, let description):
        errorString = "Must end with (description)."
      }
      return errorString
    }
}   

// 扩展String
extension String {
    public func startsWithCharacterFromSet(set: NSCharacterSet) -> Bool {
        guard !isEmpty else {
            return false
        }

        return rangeOfCharacterFromSet(set, options: [], range: startIndex.. Bool {
        guard !isEmpty else {
            return false
        }

        return rangeOfCharacterFromSet(set, options: [], range: endIndex.predecessor().. Bool {
        string
        if string.startsWithCharacterFromSet(characterSet) {
            return true
        } else{
            throw errorType // 4
        }
    }
}

struct EndsWithCharacterStringValidationRule: StringValidationRule {
    let characterSet: NSCharacterSet
    let description: String
    var errorType: StringValidationError {
        return .MustEndWith(set: characterSet, description: description)
    }
    func validate(string: String) throws -> Bool {
        if string.endsWithCharacterFromSet(characterSet) {
            return true
        } else {
            throw errorType
        }
    }
}

两个验证规则创建好了,下面我们创建一个校验器:

// 这个校验器实现了StringValidator,但是由于StringValidator存在扩展,所以可以不用实现该协议中的func validate(string: String) -> (valid: Bool, errors:[StringValidationError])方法
struct StartsAndEndsWithStringValidator: StringValidator {
  let startsWithSet: NSCharacterSet
  let startsWithDescription: String
  let endsWithSet: NSCharacterSet
  let endsWithDescription: String
  var validationRules: [StringValidationRule] {
    return [
      StartsWithCharacterStringValidationRule(characterSet: startsWithSet, description: startsWithDescription),
      EndsWithCharacterStringValidationRule(characterSet: endsWithSet, description: endsWithDescription)
    ]
  }
}

// 下面使用一下
et numberSet = NSCharacterSet.decimalDigitCharacterSet()
let startsAndEndsWithValidator = StartsAndEndsWithStringValidator(startsWithSet: letterSet, startsWithDescription: "letter", endsWithSet: numberSet, endsWithDescription: "number")

startsAndEndsWithValidator.validate("1foo").errors.description

上面的内容是一个简单的例子,我将书中的例子做了一些简化。

下面我们再看一个例子,在扩展协议的时候我们可以结合where关键字,使符合where条件的类型,才会自动的存在默认的协议扩展。

// 扩展了MutableCollectionType协议,这个协议仅对Index为Int类型的实现了MutableCollectionType的类型生效  
// Index是定义在MutableCollectionType的父协议MutableIndexable中的关联类型
extension MutableCollectionType where Index == Int {
  // 该方法任意的交换集合元素
  mutating func shuffleInPlace() {
    let c = self.count
    for i in 0..<(c-1) {
      let j = Int(arc4random_uniform(UInt32(c - i))) + i
      guard i != j else { continue }
      swap(&self[i], &self[j])
    }
  }
}

var people = ["Chris", "Ray", "Sam", "Jake", "Charlie"]
people.shuffleInPlace()

断言肯定不会错哒~

在上面的defer代码块里,我们使用了"try!"这样的形式。这是由于defer代码块中,不允许我们包含任何会跳出当前代码块的语句,例如:break / return / 抛出异常等。因此,我们使用try!告诉Swift我们确定这个调用不会发生异常(如果你对Swift说谎,是会引发运行时异常的 .)。
另外,使用"try!"标记的函数调用,可以不放在do代码块里。

模式匹配的增强

在Swift中可以不仅可以再实现协议扩展的时候使用,还可以在for循环,也可以在if-let、switch、if-case的使用,如下例子:

let names = ["Charlie", "Chris", "Mic", "John", "Craig", "Felipe"]
var namesThatStartWithC = [String]()
// 将以"C"开头的名字,加入到数组namesThatStartWithC中
for cName in names where cName.hasPrefix("C") {
  namesThatStartWithC.append(cName)
}

// 定义一个Author
public struct Author {
    public let name: String
    public let status: Additional_Things_PageSources.AuthorStatus
    public init(name: String, status: Additional_Things_PageSources.AuthorStatus)
}
let authors = [
  Author(name: "Chris Wagner", status: .Late(daysLate: 5)),
  Author(name: "Charlie Fulton", status: .Late(daysLate: 10)),
  Author(name: "Evan Dekhayser", status: .OnTime)
]
var slapLog = ""
for author in authors {
  if case .Late(let daysLate) = author.status where daysLate > 2 {
    slapLog += "Ray slaps (author.name) around a bit with a large trout n"
  }
}

把错误变成一个Optional

最后,我们调用working函数,让Robot完成工作:

let iRobot = Robot()
try? working(iRobot)

在这里,我们我们使用了"try?"的形式调用了一个会抛出异常的方法,它把表达式的评估结果转换为一个Optional。例如,我们让working返回一个Int:

func working(robot: Robot) throws -> Int {
    defer {
        try! robot.action(Command.Shutdown)
    }
    do {
        try robot.action(Command.PowerUp)
        try robot.action(Command.Lifting(52))
    }
    catch let RobotError.LowPower(percentage) {
        print("Low power: (percentage)")
    }
    catch let RobotError.Overload(maxWeight) {
        print("Overloading, max (maxWeight) KG is allowd")
    }
return 0
}

从上面的代码里可以看到,当函数有返回值的时候,我们要把throws写在返回值前面。
然后,我们查看working的返回值和类型:

let a = try? working(iRobot)
print("value: (a)n type: (a.dynamicType)")

这里,由于我们处理异常,因此a的值是0,但是,a的类型,是一个Optional。

图片 1

enter image description here

如果我们把RobotError.Overload注释掉,然后让Robot举起超过100KG的物体:

func working(robot: Robot) throws -> Int {
    defer {
        try! robot.action(Command.Shutdown)
    }
    do {
        try robot.action(Command.PowerUp)
        try robot.action(Command.Lifting(152))
    }
    catch let RobotError.LowPower(percentage) {
         print("Low power: (percentage)")
    }
    /*catch let RobotError.Overload(maxWeight) {
      print("Overloading, max (maxWeight) KG is allowd")
    }*/
    return 0}

这样异常就会被抛到working外围,此时Swift运行时会捕捉到这个异常,并且,把a的值设置成nil:

let a = try? working(iRobot)
print("value: (a)n type: (a.dynamicType)")

图片 2

enter image description here

接下来?在下一段中,我们将向大家介绍多线程环境中的异常处理。

API可用性检测

在Swift 2.x中检测某个API是否可用,不用像原来一样判断是否能够响应某个API,直接使用如下代码,使其在该版本系统下生效即可:

if #available(iOS 9.0, *) {
  // 调用在iOS 9下才能使用的API
}

defer关键字

defer在Swift中表示,在方法结束的时候一定会调用的代码。在程序中我们经常将一些内存回收、状态回复等动作放在代码的最后,但是如果在前面代码执行的过程中,发生了异常,那么可能后面的代码就不能执行,造成程序错误。但是使用defer关键字,能够保证不管程序是否正常结束,该代码一定会被执行。

例如在使用ATM的时候,不管使用的过程中发生了什么异常都必须保证最后必须把银行卡退给用户,这个在这里使用defer关键字就比较合适。

struct ATM {
  mutating func dispenseFunds(amount: Float, inout account: Account) throws{
   defer {  // 保证一定能够退卡成功
     log += "Card for (account.name) has been returned to customer.n"
     ejectCard()
   }
   // 其他的逻辑处理
 }
  func ejectCard() {
    // physically eject card
  }
}

终于是把这篇文章算是写完了,后面的一部分都是一些小的知识点,慢慢积累吧,自己的读书笔记,希望对别人有帮助吧。

iOS9-by-Tutorials-学习笔记一:Swift-2-0 Apple在前段时间开源了Swift,在iOS开发领域中又制造了一阵骚动,看了...

本文由正版必中一肖图发布于计算机网络,转载请注明出处:Swift中的十分和错误管理1,学习笔记少年老成

上一篇:NET与Ajax的实现方式小总结 下一篇:没有了
猜你喜欢
热门排行
精彩图文