Exception handling 异常处理的Swift异常

Exception handling 异常处理的Swift异常,exception-handling,swift,nsdecimalnumber,Exception Handling,Swift,Nsdecimalnumber,在仔细阅读了论坛和Swift文档之后(我承认这并不完全),我们似乎不想尝试捕获机制,而是鼓励在Swift中编写更安全的代码,以避免异常。有鉴于此,我有一个关于示例API的问题,希望了解如何更安全地处理这种情况: 例如,我可以使用NSDecimalNumberHandler创建以下类: class MathWhiz { init() { let defaultBehavior: NSDecimalNumberHandler = NSDecimalNumberHandler

在仔细阅读了论坛和Swift文档之后(我承认这并不完全),我们似乎不想尝试捕获机制,而是鼓励在Swift中编写更安全的代码,以避免异常。有鉴于此,我有一个关于示例API的问题,希望了解如何更安全地处理这种情况:

例如,我可以使用NSDecimalNumberHandler创建以下类:

class MathWhiz {

    init() {
    let defaultBehavior: NSDecimalNumberHandler =
    NSDecimalNumberHandler.defaultDecimalNumberHandler()
    }
    func add(op1: String, op2: String) ->NSDecimalNumber {
        return NSDecimalNumber.decimalNumberWithString(op1).decimalNumberByAdding(NSDecimalNumber.decimalNumberWithString(op2))
    }
}
如果我使用以下选项,我会得到一个数字:

let brain = MathWhiz()
brain.add("1", op2: "1e127")
但是,如果我导致溢出异常,则:

brain.add("1", op2: "1e128")
我将按预期使程序崩溃

所以,我的问题是,API会引发异常,但我不在这里处理它们。还有其他一些帖子指出Swift没有异常处理,但是这个问题正在寻找一种很好的方法来处理这个问题,就像语言创建者认为应该这样做一样。
有没有一种推荐的方法来处理这个问题,而不必编写自己的代码来检查溢出、下溢、精度损失等。。。?我希望NSDecimalNumberHandler能帮我做到这一点。

好吧,您使用的是ObjC API。所以只需处理ObjC内部的异常。编写一个ObjC类,该类处理异常并返回值供Swift代码使用

一种可能是将
MathWhiz
编写为一个ObjC类,并返回一个
inout-NSError
参数(即按照核心数据的方式执行,获取一个
**NSError
),并在遇到可恢复错误时用适当的值填充它。然后,您可以读取
NSDecimalNumber
中的异常,并将其转换为
NSError


您还可以为Swift消费编写一个完整的
NSDecimalNumber
包装器,然后用它代替Swift代码中的
NSDecimalNumber
。也许您可以重载该类的
运算符+
及其同级,然后确定如何毫无例外地表示各种可能的错误。

如果您正在用Swift设计函数(或方法),您至少有3种处理错误的选择:

选项1:返回可选类型

如果您的函数可能失败,并且这是定期发生的,那么考虑返回一个可选类型变量。例如,在您的示例中,您的方法

add
可能返回
NSDecimalNumber?
而不是普通的
NSDecimalNumber
。在这种情况下,您的方法将检查可能出错的所有内容,并在这些情况下返回
nil
。溢出和下溢将返回
nil
,所有其他情况将返回
NSDecimalNumber
。呼叫者必须检查并打开可选的NSDecimalNumber,如下所示:

let brain = MathWhiz()
if let sum = brain.add("1", op2: "1e127") {
    println("the result was \(sum)")
} else
    println("something went wrong with MathWhiz add")
}
选项2:返回枚举类型

如果您想返回有关出错情况的更多信息,可以创建一个枚举类型,每个错误都有一个值,一个成功值嵌入答案。例如,您可以执行以下操作:

enum MathWhizResult {
    case Overflow
    case Underflow
    case Success(NSDecimalNumber)
}
然后定义add以返回
MathWhizResult

func add(op1: String, op2: String) -> MathWhizResult
如果出现错误,
add
将返回
.Overflow
.Underflow
。如果成功,
add
将返回
success(result)
。调用方必须检查枚举并解压缩结果。可使用
开关

switch (brain.add("1", op2: "1e128")) {
case .Overflow
    println("darn, it overflowed")
case .Underflow
    println("underflow condition happened")
case .Success(let answer)
    println("the result was \(answer)"
}
选择3:选择不显式处理错误

对于非常罕见的错误,在前两个选项中解包结果可能会有太多开销。您可以选择只返回一个结果,让调用者处理下溢或溢出情况的可能性。在这种情况下,他们必须在调用
add
之前自己检查这些条件。好处是,如果他们知道他们的程序永远不会导致下溢或溢出(因为他们处理的是个位数),他们就不需要解包结果


我创建了一个小应用程序来演示如何使用
NSDecimalNumbers
实现这一点。我在
Xcode
中创建了一个
单视图应用程序。在
情节提要的
视图控制器
中,我添加了3个
文本字段
s(操作数1、操作数2和结果各一个)和一个标记为
+
按钮

ViewController.swift

import UIKit

class ViewController: UIViewController {
    @IBOutlet var operand1 : UITextField!
    @IBOutlet var operand2 : UITextField!
    @IBOutlet var result   : UITextField!

    var brain = MathWhiz()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func addButton(sender : UIButton) {
        var op1 = operand1.text
        var op2 = operand2.text

        // Perform the add with the contents of the operand fields.
        // Print the answer, or "No Result" if add returns nil.
        if let answer = brain.add(op1, op2: op2)?.description {
            result.text = answer
        } else {
            result.text = "No Result"
        }
    }
}
import UIKit

// Declare that we implement NSDecimalNumberBehaviors so that we can handle
// exceptions without them being raised.
class MathWhiz: NSDecimalNumberBehaviors {
    var badException = false

    // Required function of NSDecimalNumberBehaviors protocol
    func roundingMode() -> NSRoundingMode {
        return .RoundPlain
    }

    // Required function of NSDecimalNumberBehaviors protocol
    func scale() -> CShort {
        return CShort(NSDecimalNoScale)
    }

    // Required function of NSDecimalNumberBehaviors protocol
    // Here we process the exceptions
    func exceptionDuringOperation(operation: Selector, error: NSCalculationError, leftOperand: NSDecimalNumber, rightOperand: NSDecimalNumber) -> NSDecimalNumber? {
        var errorstr = ""

        switch(error) {
        case .NoError:
            errorstr = "NoError"
        case .LossOfPrecision:
            errorstr = "LossOfPrecision"
        case .Underflow:
            errorstr = "Underflow"
            badException = true
        case .Overflow:
            errorstr = "Overflow"
            badException = true
        case .DivideByZero:
            errorstr = "DivideByZero"
            badException = true
        }
        println("Exception called for operation \(operation) -> \(errorstr)")

        return nil
    }

    // Add two numbers represented by the strings op1 and op2.  Return nil
    // if a bad exception occurs.
    func add(op1: String, op2: String) -> NSDecimalNumber? {
        let dn1 = NSDecimalNumber(string: op1)
        let dn2 = NSDecimalNumber(string: op2)

        // Init badException to false.  It will be set to true if an
        // overflow, underflow, or divide by zero exception occur.
        badException = false

        // Add the NSDecimalNumbers, passing ourselves as the implementor
        // of the NSDecimalNumbersBehaviors protocol.
        let dn3 = dn1.decimalNumberByAdding(dn2, withBehavior: self)

        // Return nil if a bad exception happened, otherwise return the result
        // of the add.
        return badException ? nil : dn3
    }
}
MathWhiz.swift

import UIKit

class ViewController: UIViewController {
    @IBOutlet var operand1 : UITextField!
    @IBOutlet var operand2 : UITextField!
    @IBOutlet var result   : UITextField!

    var brain = MathWhiz()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func addButton(sender : UIButton) {
        var op1 = operand1.text
        var op2 = operand2.text

        // Perform the add with the contents of the operand fields.
        // Print the answer, or "No Result" if add returns nil.
        if let answer = brain.add(op1, op2: op2)?.description {
            result.text = answer
        } else {
            result.text = "No Result"
        }
    }
}
import UIKit

// Declare that we implement NSDecimalNumberBehaviors so that we can handle
// exceptions without them being raised.
class MathWhiz: NSDecimalNumberBehaviors {
    var badException = false

    // Required function of NSDecimalNumberBehaviors protocol
    func roundingMode() -> NSRoundingMode {
        return .RoundPlain
    }

    // Required function of NSDecimalNumberBehaviors protocol
    func scale() -> CShort {
        return CShort(NSDecimalNoScale)
    }

    // Required function of NSDecimalNumberBehaviors protocol
    // Here we process the exceptions
    func exceptionDuringOperation(operation: Selector, error: NSCalculationError, leftOperand: NSDecimalNumber, rightOperand: NSDecimalNumber) -> NSDecimalNumber? {
        var errorstr = ""

        switch(error) {
        case .NoError:
            errorstr = "NoError"
        case .LossOfPrecision:
            errorstr = "LossOfPrecision"
        case .Underflow:
            errorstr = "Underflow"
            badException = true
        case .Overflow:
            errorstr = "Overflow"
            badException = true
        case .DivideByZero:
            errorstr = "DivideByZero"
            badException = true
        }
        println("Exception called for operation \(operation) -> \(errorstr)")

        return nil
    }

    // Add two numbers represented by the strings op1 and op2.  Return nil
    // if a bad exception occurs.
    func add(op1: String, op2: String) -> NSDecimalNumber? {
        let dn1 = NSDecimalNumber(string: op1)
        let dn2 = NSDecimalNumber(string: op2)

        // Init badException to false.  It will be set to true if an
        // overflow, underflow, or divide by zero exception occur.
        badException = false

        // Add the NSDecimalNumbers, passing ourselves as the implementor
        // of the NSDecimalNumbersBehaviors protocol.
        let dn3 = dn1.decimalNumberByAdding(dn2, withBehavior: self)

        // Return nil if a bad exception happened, otherwise return the result
        // of the add.
        return badException ? nil : dn3
    }
}

更新swift 2.0的答案

正如您所提到的,现在Swift 2.0支持
try
throw
,以及
catch
关键字

这是官方公告

错误处理模型:Swift 2.0中的新错误处理模型将 使用熟悉的try、Trow和catch关键字,立即感觉自然。 最棒的是,它被设计成可以完美地与苹果SDK和iPhone配合使用 错误。事实上,N错误符合Swift的错误类型。你会 肯定想看一下WWDC会议,了解Swift to的最新内容 多听听

e、 g


非常感谢。也许最好的方法是使用我已经编写的Objective-C版本,并研究核心数据,以实现您提到的参数和返回值。苹果表示,他们支持Swift中所有当前的API,但我不确定他们是否明确表示,如果我们不使用objective-C,我们应该如何处理objective-C API中的异常(我上面的例子是一个使用Swift的练习,看看它与我在Objective-C中所做的相比如何,我想看看我能多么容易地将我的工作转换为Swift).是的,我不确定他们是否充分回答了这个问题。我的猜测是,我们将看到那些不使用异常的API的Swift等价物。谢谢你的回答;这似乎是一个不错的解决方案。我现在就去试试。嗯,在#1和#2中,在返回nil之前如何检查错误(在add函数中)?--似乎是