Custom Errors

Hi again!


I would like to know, how to define, throw and catch custom errors. The class throwing is a "generic" class (in a framework). Current I have the following:


1. Class definition:

import Foundation
open class Examine<Element: Hashable>: NSObject, NSCopying, NSCoding {
...
override public init() {
        super.init()
        initialize()
}
public init(withObject object: Any!, characterSet: NSCharacterSet?) throws {
        super.init()
        if object is NSString  {
            self.initialize(withString: object as! String, characterSet: characterSet!)
        }
        else if object is NSArray {
            self.initialize(withArray: object as! [Element])
        }
        else {
            throw ExamineError.invalidArgument(line: #line, function: #function)
        }
}
private func initialize(withString string: String, characterSet: NSCharacterSet) {

     ...
}
private func initialize(withArray array: [Element]!) {
     ...
}
....   
}


2. Custom Error definiton:


public enum ExamineError: Error {
    case invalidArgument(line: Int, function: String)
}
extension ExamineError: LocalizedError {
    public var errorDescription: String? {
        switch self {
        case .invalidArgument(let line, let function):
            return "Invalid argument: " + "\(line) in: " + function
        }
    }
public var failureReason: String? {
        return "A Reason"
}
    public var helpAnchor: String? {
        return "Nope"
    }
    public var recoverySuggestion: String? {
        return "try it again"
    }
}


3. Test

import Foundation
import Examine
var test: Examine<Double>
do {
    // the following lines should throw an error
    var dbl: Double = 3.141

    tes = try Examine<Double>(withObject: dbl, characterSet: NSCharacterSet.alphanumerics as NSCharacterSet)
}
catch let e as ExamineError {
    print(e.localizedDescription)
}


When running this code, I get an error:

Terminated due to signal 9


Catch should "CATCH" the error and an error message should be printed. Is that right?


When trying this in playground (all definitions in playground), all works fine....


Who can help?


Best regards!

How is VTExamine defined ?

Hi!


VTExamine === Examine... A typo...

OK, let's go on.


Where exactly does the app crash ?


I would add a catch statement to catch all other throw cases :


do { 
    // the following lines should throw an error 
    var dbl: Double = 3.141 

    tes = try Examine<Double>(withObject: dbl, characterSet: NSCharacterSet.alphanumerics as NSCharacterSet) 
} 
catch let e as ExamineError { 
    print(e.localizedDescription) 
} 
catch {
     print(error)
}

Hi!


Thanks for your support. :-)

I have to correct a little part of the code. The class Examine is part of a framework named Stats. The correct test code reads as follows (note my comments):


import Foundation
// import Framework where Examine is defined
import Stats
var test: Examine<Double>
do {
    var dbl: Double = 3.141
    test = try Examine<Double>(withObject: dbl, characterSet: NSCharacterSet.alphanumerics as NSCharacterSet)
}
catch let e as ExamineError {
// This branch is never be reached
    print(e.localizedDescription)
}
catch {
// --> here the app crashes (EXC_BAD_INSTRUCTION...)
    print(error.localizedDescription)
}


What I do not understand is: I'm throwing an ExamineError (in init(withObject:characterSet:). But the error throwed will not be recognised by the catch statement...

In


catch {

// --> here the app crashes (EXC_BAD_INSTRUCTION...)

print(error.localizedDescription)

}


I would not use error.localizedDescription, but just error, to see what it is ; you will see if it is not an ExamineError

The debugger shows:


error = Stats.ExamineError


As written above: When moving ALL definitions to a playground project, the catch-clause works properly... I'm stumped...


Could it be an access-level issue?

Looks like ExamineError is not visible ; try :


catch let e as Stats.ExamineError {

I've tried this. It doesn't work...

I'm not sure what's going on. The basic code works in a single target (I tried using Xcode 9).


However, I think you're making this harder than it needs to be. You only need to declare the error like this:


public enum ExamineError: LocalizedError {
    case invalidArgument(line: Int, function: String)
    public var errorDescription: String? {
    …


There's no need to conform to Error "first" and extend it, and it's possible this may have a side effect on the enum (via type inference), but IDK.


Then, there's no need to match on the type. A plain catch will work:


     do {
          throw …
     }
     catch {
          print (error.localizedDescription)
     }


If you make those changes, does it still crash in your cross-module situation?

Why do you import Examine and not Stats ?

Hi!


The suggested changes have no effect. The error will be catched but the line


print(error.localizedDescription)


produces a crash (EXC_BAD_INSTRUCTION, code= EXC_i386_INVOP) as before...


Following the all the code:


1. FRAMEWORK-Code (framework is called: teststats)

1.1. Examine.swift

import Foundation
open class Examine<Element: Hashable>: NSObject, NSCopying, NSCoding {
    // human readable description
    open var descriptionString: String?
    override public init() {
        super.init()
        initialize()
    }

    public init(withObject object: Any!, characterSet: NSCharacterSet?) throws {
        super.init()
        if object is NSString  {
            self.initialize(withString: object as! String, characterSet: characterSet!)
        }
        else if object is NSArray {
            self.initialize(withArray: object as! [Element])
        }
        else {
            throw ExamineError.invalidArgument(line: #line, function: #function)
        }
    }
    private func initialize(withString string: String, characterSet: NSCharacterSet) {
        initialize()
        return
    }

    private func initialize(withArray array: [Element]!) {
        initialize()
        return
    }


    public func encode(with aCoder: NSCoder) {
        aCoder.encode(42, forKey: "magic")
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init()
    }

    public func copy(with zone: NSZone? = nil) -> Any {
        let res: Examine = Examine()
        return res
    }
    private func initialize() {
        // initialize defaults here
    }
}


1.2. ExamineError.swift


import Foundation
public enum ExamineError: LocalizedError {
    case invalidArgument(line: Int, function: String)
    public var errorDescription: String? {
        switch self {
        case .invalidArgument(let line, let function):
            return "Invalid argument: " + "\(line) in: " + function
         }
    }
    public var failureReason: String? {
        return "A Reason"
    }
    public var helpAnchor: String? {
        return "Nope"
    }
    public var recoverySuggestion: String? {
        return "try it again"
    }
}


2. Test-Code (main.swift, command-line, linked against teststats.framework)


import Foundation
import teststats
var test: Examine<Double>
do {
    var dbl: Double = 3.141
    // this line throws an ExamineError (as is should be)
    test = try Examine<Double>(withObject: dbl, characterSet: NSCharacterSet.alphanumerics as NSCharacterSet)
}
catch {
    // --> here EXC_BAD_INSTRUCTION
    print(error.localizedDescription)
}

In addition to the previous post:


Changing to


catch {
    let e = error as! ExamineError
    print(e.localizedDescription)
}


produces the error below:

Could not cast value of type '_SwiftNativeNSError'  to 'teststats . ExamineError'

I'm not expert enough to be sure. But looks like the framework throws a generic exception (an NSError) and not the ExamineError.


I found 2 links that have some similarities with what you see :


The first is about objC framworks, but may be something similar happening :

h ttps://stackoverflow.com/questions/33788183/error-handling-an-objective-c-framework-in-swift-language


the second is Swift ; here they throw NSError from the framework:

h ttps://stackoverflow.com/questions/37410192/how-to-throw-error-exception-from-swift-singleton-init

I tried the code you last posted in new project (both Xcode 8.3.3 and Xcode 9, Swift 3.x and 4) and it works fine. At this point, I'd say the problem is not the ExamineError declaration, but something else is going wrong in your project. If you're using a current version of Xcode, I'd suggest you submit a bug report and see what comes back.


Incidentally, just for interest, you could try:


catch { 
    let e = error as! ExamineError
     print (type(of: e))
}

and see what it says.

Hi!


I've submitted a bug report ( 33107674 ). Nevertheless I have "solved" the problem by making ExamineError a subclass of NSError. That is rather not Swift-like.

I'll wait for response to my bug report.


Thank you very much!


Best regards!

Custom Errors
 
 
Q