Proper Way to make Errors in Swift 3


(Ronak) #1

Hello all,

We are proceeding to update all of our Swift code to Swift 3 now and had a few questions about the proper way to implement Errors. We need these entities to be available in Objective-C and they are actively being used in Swift classes marked as @objc.

I read: https://github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md completely and came up with this implementation:

/// The enumeration of the possible error codes in the Foundation error domain
@objc public class FoundationError: NSObject, CustomNSError {

    /// The underlying error code
    private let code: FoundationError.Code

    /// The type of an error code.
    @objc public enum Code: Int {

        /// An ARCOperationCondition failed during evaluation
        case operationConditionFailed = 10000

        /// An ARCOperation failed during execution
        case operationExecutionFailed = 10001
    }

    /// The domain of the error.
    public static var errorDomain: String {
        return "FoundationError"
    }

    /// The error code within the given domain.
    public var errorCode: Int {
        return code.rawValue
    }

    /// The user-info dictionary.
    public let errorUserInfo: [String : Any]

    /// Initializes a new FoundationError with an empty userInfo dictionary
    ///
    /// - parameter code: one of the available error codes
    ///
    /// - returns: a new instance of FoundationError
    public convenience init(code: FoundationError.Code) {
        self.init(code: code, userInfo: [:])
    }

    /// Initializes a new FoundationError with an userInfo dictionary
    ///
    /// - parameter code: one of the available error codes
    /// - parameter userInfo: the user-info dictionary
    ///
    /// - returns: a new instance of FoundationError
    public init(code: FoundationError.Code, userInfo: [String : Any]) {
        self.code = code
        errorUserInfo = userInfo
    }

    /// Computes whether two FoundationErrors are equal
    ///
    /// - parameter object: a FoundationError
    ///
    /// - returns: true, if the two errors are equal
    public override func isEqual(_ object: Any?) -> Bool {
        guard let object = object as? FoundationError else { return false }

        return errorCode == object.errorCode && errorUserInfo.keys.elementsEqual(object.errorUserInfo.keys)
    }
}

My question is whether this is the correct way to do this now; or is there another solution we should be doing? We would like to follow Swift Best Practices here, but unfortunately, the documentation is quite vague on this subject.

Thanks for your help!

Ronak Patel


(Zachary Waldowski) #2

Error types themselves shouldn’t generally cross into Objective-C, because you don’t get interop; for that, we have Error, which crosses the bridge as NSError.

If it’s instructive to think of it this way, both Objective-C and Swift should define errors in their best native way, and use NSError. That’s, at least, the use case for CustomNSError and LocalizedError.

If you’re primarily exporting errors from Objective-C to be “seen” in Swift, you want to look into the ns_error_domain attribute on the C side. This generates a good deal of the enum Code: Int boilerplate coming in to Swift, but it’s obnoxious to create those errors from Swift.

If you’re primarily exporting errors from Swift to Objective-C, you can make any Swift type implement Error and CustomNSError, which can then cross the bridge.

The happy path of full error interop in both directions is a little more complicated. Generally you have to start with one of the above approach and “mirror” some values in the other language. Consider the following as a slightly over-wrought example of having your cake and eating it too:

extern NSString *const MyErrorDomain NS_REFINED_FOR_SWIFT;
extern NSString *const MyErrorUserInfoStringKey NS_REFINED_FOR_SWIFT;

typedef NS_ENUM(NSInteger, MyErrorCode) {
    MyErrorCodeOne,
    MyErrorCodeTwo,
    MyErrorCodeThree,
} NS_REFINED_FOR_SWIFT;

enum MyError: CustomNSError {

    case one(String)
    case two
    case three

    static var errorDomain: String {
        return __MyErrorDomain
    }

    var errorCode: Int {
        switch self {
        case .one:
            return __MyErrorCode.one.rawValue
        case .two:
            return __MyErrorCode.two.rawValue
        case .three:
            return __MyErrorCode.three.rawValue
        }
    }

    var errorUserInfo: [String: Any] {
        var userInfo = [String: Any]()
        if case let .one(string) = self {
            userInfo[__MyErrorUserInfoStringKey] = string
        }
        return userInfo
    }
    
}

···

On Sep 29, 2016, at 1:17 PM, Ronak via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hello all,

We are proceeding to update all of our Swift code to Swift 3 now and had a few questions about the proper way to implement Errors. We need these entities to be available in Objective-C and they are actively being used in Swift classes marked as @objc.

I read: https://github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md completely and came up with this implementation:

/// The enumeration of the possible error codes in the Foundation error domain
@objc public class FoundationError: NSObject, CustomNSError {

    /// The underlying error code
    private let code: FoundationError.Code

    /// The type of an error code.
    @objc public enum Code: Int {

        /// An ARCOperationCondition failed during evaluation
        case operationConditionFailed = 10000

        /// An ARCOperation failed during execution
        case operationExecutionFailed = 10001
    }

    /// The domain of the error.
    public static var errorDomain: String {
        return "FoundationError"
    }

    /// The error code within the given domain.
    public var errorCode: Int {
        return code.rawValue
    }

    /// The user-info dictionary.
    public let errorUserInfo: [String : Any]

    /// Initializes a new FoundationError with an empty userInfo dictionary
    ///
    /// - parameter code: one of the available error codes
    ///
    /// - returns: a new instance of FoundationError
    public convenience init(code: FoundationError.Code) {
        self.init(code: code, userInfo: [:])
    }

    /// Initializes a new FoundationError with an userInfo dictionary
    ///
    /// - parameter code: one of the available error codes
    /// - parameter userInfo: the user-info dictionary
    ///
    /// - returns: a new instance of FoundationError
    public init(code: FoundationError.Code, userInfo: [String : Any]) {
        self.code = code
        errorUserInfo = userInfo
    }

    /// Computes whether two FoundationErrors are equal
    ///
    /// - parameter object: a FoundationError
    ///
    /// - returns: true, if the two errors are equal
    public override func isEqual(_ object: Any?) -> Bool {
        guard let object = object as? FoundationError else { return false }

        return errorCode == object.errorCode && errorUserInfo.keys.elementsEqual(object.errorUserInfo.keys)
    }
}

My question is whether this is the correct way to do this now; or is there another solution we should be doing? We would like to follow Swift Best Practices here, but unfortunately, the documentation is quite vague on this subject.

Thanks for your help!

Ronak Patel
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users


(Ronak) #3

Hi,

I’ve actually switched our implementation to:

/// The type of an error code.
@objc public enum FoundationErrorCode: Int {

    /// An ARCOperationCondition failed during evaluation
    case operationConditionFailed = 10000

    /// An ARCOperation failed during execution
    case operationExecutionFailed = 10001

    /// An unsupported HTTP Method was encountered
    case unsupportedHTTPMethod = 10002
}

/// The enumeration of the possible error codes in the Foundation error domain
@objc public class FoundationError: NSObject, CustomNSError {

    /// The underlying error code
    private let code: FoundationErrorCode

    /// The domain of the error.
    public static var errorDomain: String {
        return "FoundationError"
    }

    /// The error code within the given domain.
    public var errorCode: Int {
        return code.rawValue
    }

    /// The user-info dictionary.
    public let errorUserInfo: [String : Any]

    /// Initializes a new FoundationError with an empty userInfo dictionary
    ///
    /// - parameter code: one of the available error codes
    ///
    /// - returns: a new instance of FoundationError
    public convenience init(code: FoundationErrorCode) {
        self.init(code: code, userInfo: [:])
    }

    /// Initializes a new FoundationError with an userInfo dictionary
    ///
    /// - parameter code: one of the available error codes
    /// - parameter userInfo: the user-info dictionary
    ///
    /// - returns: a new instance of FoundationError
    public init(code: FoundationErrorCode, userInfo: [String : Any]) {
        self.code = code
        errorUserInfo = userInfo
    }

    /// Computes whether two FoundationErrors are equal
    ///
    /// - parameter object: a FoundationError
    ///
    /// - returns: true, if the two errors are equal
    public override func isEqual(_ object: Any?) -> Bool {
        guard let object = object as? FoundationError else { return false }

        return errorCode == object.errorCode && errorUserInfo.keys.elementsEqual(object.errorUserInfo.keys)
    }
}

I hope this is closer to the correct way to implement this.

Thanks!

Ronak

···

On Sep 29, 2016, at 1:17 PM, Ronak via swift-users <swift-users@swift.org> wrote:

Hello all,

We are proceeding to update all of our Swift code to Swift 3 now and had a few questions about the proper way to implement Errors. We need these entities to be available in Objective-C and they are actively being used in Swift classes marked as @objc.

I read: https://github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md completely and came up with this implementation:

/// The enumeration of the possible error codes in the Foundation error domain
@objc public class FoundationError: NSObject, CustomNSError {

    /// The underlying error code
    private let code: FoundationError.Code

    /// The type of an error code.
    @objc public enum Code: Int {

        /// An ARCOperationCondition failed during evaluation
        case operationConditionFailed = 10000

        /// An ARCOperation failed during execution
        case operationExecutionFailed = 10001
    }

    /// The domain of the error.
    public static var errorDomain: String {
        return "FoundationError"
    }

    /// The error code within the given domain.
    public var errorCode: Int {
        return code.rawValue
    }

    /// The user-info dictionary.
    public let errorUserInfo: [String : Any]

    /// Initializes a new FoundationError with an empty userInfo dictionary
    ///
    /// - parameter code: one of the available error codes
    ///
    /// - returns: a new instance of FoundationError
    public convenience init(code: FoundationError.Code) {
        self.init(code: code, userInfo: [:])
    }

    /// Initializes a new FoundationError with an userInfo dictionary
    ///
    /// - parameter code: one of the available error codes
    /// - parameter userInfo: the user-info dictionary
    ///
    /// - returns: a new instance of FoundationError
    public init(code: FoundationError.Code, userInfo: [String : Any]) {
        self.code = code
        errorUserInfo = userInfo
    }

    /// Computes whether two FoundationErrors are equal
    ///
    /// - parameter object: a FoundationError
    ///
    /// - returns: true, if the two errors are equal
    public override func isEqual(_ object: Any?) -> Bool {
        guard let object = object as? FoundationError else { return false }

        return errorCode == object.errorCode && errorUserInfo.keys.elementsEqual(object.errorUserInfo.keys)
    }
}

My question is whether this is the correct way to do this now; or is there another solution we should be doing? We would like to follow Swift Best Practices here, but unfortunately, the documentation is quite vague on this subject.

Thanks for your help!

Ronak Patel
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Ronak) #4

Ahh..thanks for the reply Zach. I didn’t actually see your reply until now.

I’ll see how I can adjust my code.

Thanks for this!

···

On Sep 29, 2016, at 4:38 PM, Zach Waldowski <zach@waldowski.me> wrote:

Error types themselves shouldn’t generally cross into Objective-C, because you don’t get interop; for that, we have Error, which crosses the bridge as NSError.

If it’s instructive to think of it this way, both Objective-C and Swift should define errors in their best native way, and use NSError. That’s, at least, the use case for CustomNSError and LocalizedError.

If you’re primarily exporting errors from Objective-C to be “seen” in Swift, you want to look into the ns_error_domain attribute on the C side. This generates a good deal of the enum Code: Int boilerplate coming in to Swift, but it’s obnoxious to create those errors from Swift.

If you’re primarily exporting errors from Swift to Objective-C, you can make any Swift type implement Error and CustomNSError, which can then cross the bridge.

The happy path of full error interop in both directions is a little more complicated. Generally you have to start with one of the above approach and “mirror” some values in the other language. Consider the following as a slightly over-wrought example of having your cake and eating it too:

extern NSString *const MyErrorDomain NS_REFINED_FOR_SWIFT;
extern NSString *const MyErrorUserInfoStringKey NS_REFINED_FOR_SWIFT;

typedef NS_ENUM(NSInteger, MyErrorCode) {
    MyErrorCodeOne,
    MyErrorCodeTwo,
    MyErrorCodeThree,
} NS_REFINED_FOR_SWIFT;

enum MyError: CustomNSError {

    case one(String)
    case two
    case three

    static var errorDomain: String {
        return __MyErrorDomain
    }

    var errorCode: Int {
        switch self {
        case .one:
            return __MyErrorCode.one.rawValue
        case .two:
            return __MyErrorCode.two.rawValue
        case .three:
            return __MyErrorCode.three.rawValue
        }
    }

    var errorUserInfo: [String: Any] {
        var userInfo = [String: Any]()
        if case let .one(string) = self {
            userInfo[__MyErrorUserInfoStringKey] = string
        }
        return userInfo
    }
    
}

On Sep 29, 2016, at 1:17 PM, Ronak via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hello all,

We are proceeding to update all of our Swift code to Swift 3 now and had a few questions about the proper way to implement Errors. We need these entities to be available in Objective-C and they are actively being used in Swift classes marked as @objc.

I read: https://github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md completely and came up with this implementation:

/// The enumeration of the possible error codes in the Foundation error domain
@objc public class FoundationError: NSObject, CustomNSError {

    /// The underlying error code
    private let code: FoundationError.Code

    /// The type of an error code.
    @objc public enum Code: Int {

        /// An ARCOperationCondition failed during evaluation
        case operationConditionFailed = 10000

        /// An ARCOperation failed during execution
        case operationExecutionFailed = 10001
    }

    /// The domain of the error.
    public static var errorDomain: String {
        return "FoundationError"
    }

    /// The error code within the given domain.
    public var errorCode: Int {
        return code.rawValue
    }

    /// The user-info dictionary.
    public let errorUserInfo: [String : Any]

    /// Initializes a new FoundationError with an empty userInfo dictionary
    ///
    /// - parameter code: one of the available error codes
    ///
    /// - returns: a new instance of FoundationError
    public convenience init(code: FoundationError.Code) {
        self.init(code: code, userInfo: [:])
    }

    /// Initializes a new FoundationError with an userInfo dictionary
    ///
    /// - parameter code: one of the available error codes
    /// - parameter userInfo: the user-info dictionary
    ///
    /// - returns: a new instance of FoundationError
    public init(code: FoundationError.Code, userInfo: [String : Any]) {
        self.code = code
        errorUserInfo = userInfo
    }

    /// Computes whether two FoundationErrors are equal
    ///
    /// - parameter object: a FoundationError
    ///
    /// - returns: true, if the two errors are equal
    public override func isEqual(_ object: Any?) -> Bool {
        guard let object = object as? FoundationError else { return false }

        return errorCode == object.errorCode && errorUserInfo.keys.elementsEqual(object.errorUserInfo.keys)
    }
}

My question is whether this is the correct way to do this now; or is there another solution we should be doing? We would like to follow Swift Best Practices here, but unfortunately, the documentation is quite vague on this subject.

Thanks for your help!

Ronak Patel
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users


(Ronak) #5

Hi everyone,

It looks like I’m still having issues exposing a CustomNSError to Objective-C. I am generating errors of this type in Swift and then trying to bridge them in one direction over to Objective-C.
From Objective-C, this Error type is being exposed as a _SwiftValue.

Do I have to mark this error as @objc and switch to using a raw enum? If so, I fail to see the benefit of using CustomNSError or any of the new error related protocols from Swift -> Objective-C.

Here’s my implementation:

public enum MyError: CustomNSError, Equatable {

  case one([String : Any])

  case two([String : Any])

  case three([String : Any])

  /// The domain of the error.
  public static var errorDomain: String {
    return “MyError"
  }

  /// The error code within the given domain.
  public var errorCode: Int {
    switch self {
    case .one:
      return 50000
    case .two:
      return 50001
    case .three:
      return 50002
    }
  }

  /// The user-info dictionary.
  public var errorUserInfo: [String : Any] {
    var userInfo = [String: Any]()
    if case let .one(info) = self {
      userInfo = info
    } else if case let .two(info) = self {
      userInfo = info
    } else if case let .three(info) = self {
      userInfo = info
    }

    return userInfo
  }
}

Thanks

Ronak

···

On Sep 29, 2016, at 5:46 PM, Ronak via swift-users <swift-users@swift.org> wrote:

Ahh..thanks for the reply Zach. I didn’t actually see your reply until now.

I’ll see how I can adjust my code.

Thanks for this!

On Sep 29, 2016, at 4:38 PM, Zach Waldowski <zach@waldowski.me <mailto:zach@waldowski.me>> wrote:

Error types themselves shouldn’t generally cross into Objective-C, because you don’t get interop; for that, we have Error, which crosses the bridge as NSError.

If it’s instructive to think of it this way, both Objective-C and Swift should define errors in their best native way, and use NSError. That’s, at least, the use case for CustomNSError and LocalizedError.

If you’re primarily exporting errors from Objective-C to be “seen” in Swift, you want to look into the ns_error_domain attribute on the C side. This generates a good deal of the enum Code: Int boilerplate coming in to Swift, but it’s obnoxious to create those errors from Swift.

If you’re primarily exporting errors from Swift to Objective-C, you can make any Swift type implement Error and CustomNSError, which can then cross the bridge.

The happy path of full error interop in both directions is a little more complicated. Generally you have to start with one of the above approach and “mirror” some values in the other language. Consider the following as a slightly over-wrought example of having your cake and eating it too:

extern NSString *const MyErrorDomain NS_REFINED_FOR_SWIFT;
extern NSString *const MyErrorUserInfoStringKey NS_REFINED_FOR_SWIFT;

typedef NS_ENUM(NSInteger, MyErrorCode) {
    MyErrorCodeOne,
    MyErrorCodeTwo,
    MyErrorCodeThree,
} NS_REFINED_FOR_SWIFT;

enum MyError: CustomNSError {

    case one(String)
    case two
    case three

    static var errorDomain: String {
        return __MyErrorDomain
    }

    var errorCode: Int {
        switch self {
        case .one:
            return __MyErrorCode.one.rawValue
        case .two:
            return __MyErrorCode.two.rawValue
        case .three:
            return __MyErrorCode.three.rawValue
        }
    }

    var errorUserInfo: [String: Any] {
        var userInfo = [String: Any]()
        if case let .one(string) = self {
            userInfo[__MyErrorUserInfoStringKey] = string
        }
        return userInfo
    }
    
}

On Sep 29, 2016, at 1:17 PM, Ronak via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hello all,

We are proceeding to update all of our Swift code to Swift 3 now and had a few questions about the proper way to implement Errors. We need these entities to be available in Objective-C and they are actively being used in Swift classes marked as @objc.

I read: https://github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md completely and came up with this implementation:

/// The enumeration of the possible error codes in the Foundation error domain
@objc public class FoundationError: NSObject, CustomNSError {

    /// The underlying error code
    private let code: FoundationError.Code

    /// The type of an error code.
    @objc public enum Code: Int {

        /// An ARCOperationCondition failed during evaluation
        case operationConditionFailed = 10000

        /// An ARCOperation failed during execution
        case operationExecutionFailed = 10001
    }

    /// The domain of the error.
    public static var errorDomain: String {
        return "FoundationError"
    }

    /// The error code within the given domain.
    public var errorCode: Int {
        return code.rawValue
    }

    /// The user-info dictionary.
    public let errorUserInfo: [String : Any]

    /// Initializes a new FoundationError with an empty userInfo dictionary
    ///
    /// - parameter code: one of the available error codes
    ///
    /// - returns: a new instance of FoundationError
    public convenience init(code: FoundationError.Code) {
        self.init(code: code, userInfo: [:])
    }

    /// Initializes a new FoundationError with an userInfo dictionary
    ///
    /// - parameter code: one of the available error codes
    /// - parameter userInfo: the user-info dictionary
    ///
    /// - returns: a new instance of FoundationError
    public init(code: FoundationError.Code, userInfo: [String : Any]) {
        self.code = code
        errorUserInfo = userInfo
    }

    /// Computes whether two FoundationErrors are equal
    ///
    /// - parameter object: a FoundationError
    ///
    /// - returns: true, if the two errors are equal
    public override func isEqual(_ object: Any?) -> Bool {
        guard let object = object as? FoundationError else { return false }

        return errorCode == object.errorCode && errorUserInfo.keys.elementsEqual(object.errorUserInfo.keys)
    }
}

My question is whether this is the correct way to do this now; or is there another solution we should be doing? We would like to follow Swift Best Practices here, but unfortunately, the documentation is quite vague on this subject.

Thanks for your help!

Ronak Patel
_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Ole Begemann) #6

Have you tried explicitly casting the value to NSError when you pass it to Objective-C? I think it should work then.

     let myError: MyError = ...
     myObjCFunc(myError as NSError)

···

On 02/03/2017 17:29, Ronak via swift-users wrote:

Hi everyone,

It looks like I’m still having issues exposing a CustomNSError to
Objective-C. I am generating errors of this type in Swift and then
trying to bridge them in one direction over to Objective-C.
From Objective-C, this Error type is being exposed as a _SwiftValue.

Do I have to mark this error as @objc and switch to using a raw enum? If
so, I fail to see the benefit of using CustomNSError or any of the new
error related protocols from Swift -> Objective-C.

Here’s my implementation:

publicenumMyError: CustomNSError, Equatable{

  caseone([String: Any])

  casetwo([String: Any])

  casethree([String: Any])

  /// The domain of the error.
  publicstaticvarerrorDomain: String{
    return“MyError"
  }

  /// The error code within the given domain.
  publicvarerrorCode: Int{
    switchself{
    case.one:
      return50000
    case.two:
      return50001
    case.three:
      return50002
    }
  }

  /// The user-info dictionary.
  publicvarerrorUserInfo: [String: Any] {
    varuserInfo = [String: Any]()
    ifcaselet.one(info) = self{
      userInfo = info
    } elseifcaselet.two(info) = self{
      userInfo = info
    } elseifcaselet.three(info) = self{
      userInfo = info
    }

    returnuserInfo
  }
}

Thanks

Ronak

On Sep 29, 2016, at 5:46 PM, Ronak via swift-users >> <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Ahh..thanks for the reply Zach. I didn’t actually see your reply until
now.

I’ll see how I can adjust my code.

Thanks for this!

On Sep 29, 2016, at 4:38 PM, Zach Waldowski <zach@waldowski.me >>> <mailto:zach@waldowski.me>> wrote:

Error types themselves shouldn’t generally cross into Objective-C,
because you don’t get interop; for that, we have Error, which crosses
the bridge as NSError.

If it’s instructive to think of it this way, both Objective-C and
Swift should define errors in their best native way, and use NSError.
That’s, at least, the use case for CustomNSError and LocalizedError.

If you’re primarily exporting errors from Objective-C to be “seen” in
Swift, you want to look into the ns_error_domain attribute on the C
side. This generates a good deal of the enum Code: Int boilerplate
coming in to Swift, but it’s obnoxious to create those errors from Swift.

If you’re primarily exporting errors from Swift to Objective-C, you
can make any Swift type implement Error and CustomNSError, which can
then cross the bridge.

The happy path of full error interop in both directions is a little
more complicated. Generally you have to start with one of the above
approach and “mirror” some values in the other language. Consider the
following as a slightly over-wrought example of having your cake and
eating it too:

externNSString*constMyErrorDomain NS_REFINED_FOR_SWIFT;
externNSString*constMyErrorUserInfoStringKey NS_REFINED_FOR_SWIFT;

typedefNS_ENUM(NSInteger, MyErrorCode) {
    MyErrorCodeOne,
    MyErrorCodeTwo,
    MyErrorCodeThree,
} NS_REFINED_FOR_SWIFT;

enumMyError: CustomNSError{

    caseone(String)
    casetwo
    casethree

    staticvarerrorDomain: String{
        return__MyErrorDomain
    }

    varerrorCode: Int{
        switchself{
        case.one:
            return__MyErrorCode.one.rawValue
        case.two:
            return__MyErrorCode.two.rawValue
        case.three:
            return__MyErrorCode.three.rawValue
        }
    }

    varerrorUserInfo: [String: Any] {
        varuserInfo = [String: Any]()
        ifcaselet.one(string) = self{
            userInfo[__MyErrorUserInfoStringKey] = string
        }
        returnuserInfo
    }

}

On Sep 29, 2016, at 1:17 PM, Ronak via swift-users >>>> <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Hello all,

We are proceeding to update all of our Swift code to Swift 3 now and
had a few questions about the proper way to implement Errors. We
need these entities to be available in Objective-C and they are
actively being used in Swift classes marked as @objc.

I
read: https://github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md completely
and came up with this implementation:

/// The enumeration of the possible error codes in the Foundation
error domain
@objcpublicclassFoundationError: NSObject, CustomNSError{

    /// The underlying error code
    privateletcode: FoundationError.Code

    /// The type of an error code.
    @objcpublicenumCode: Int{

        /// An ARCOperationCondition failed during evaluation
        caseoperationConditionFailed = 10000

        /// An ARCOperation failed during execution
        caseoperationExecutionFailed = 10001
    }

    /// The domain of the error.
    publicstaticvarerrorDomain: String{
        return"FoundationError"
    }

    /// The error code within the given domain.
    publicvarerrorCode: Int{
        returncode.rawValue
    }

    /// The user-info dictionary.
    publicleterrorUserInfo: [String: Any]

    /// Initializes a new FoundationError with an empty userInfo
dictionary
    ///
    /// - parameter code: one of the available error codes
    ///
    /// - returns: a new instance of FoundationError
    publicconvenienceinit(code: FoundationError.Code) {
        self.init(code: code, userInfo: [:])
    }

    /// Initializes a new FoundationError with an userInfo dictionary
    ///
    /// - parameter code: one of the available error codes
    /// - parameter userInfo: the user-info dictionary
    ///
    /// - returns: a new instance of FoundationError
    publicinit(code: FoundationError.Code, userInfo: [String: Any]) {
        self.code = code
        errorUserInfo = userInfo
    }

    /// Computes whether two FoundationErrors are equal
    ///
    /// - parameter object: a FoundationError
    ///
    /// - returns: true, if the two errors are equal
    publicoverridefuncisEqual(_object: Any?) -> Bool{
        guardletobject = object as? FoundationErrorelse{ returnfalse}

        returnerrorCode == object.errorCode &&
errorUserInfo.keys.elementsEqual(object.errorUserInfo.keys)
    }
}

My question is whether this is the correct way to do this now; or is
there another solution we should be doing? We would like to follow
Swift Best Practices here, but unfortunately, the documentation is
quite vague on this subject.

Thanks for your help!

Ronak Patel


(Ronak) #7

Hi Ole,

Yes that’s what I’m resorting to now. I had to change my Swift code to return NSError to all clients, regardless of language.

Therefore, I really wonder why CustomNSError even exists. I could have just used NSError from the get go...

Ronak

···

On Mar 5, 2017, at 2:20 PM, Ole Begemann <ole@oleb.net> wrote:

Have you tried explicitly casting the value to NSError when you pass it to Objective-C? I think it should work then.

   let myError: MyError = ...
   myObjCFunc(myError as NSError)

On 02/03/2017 17:29, Ronak via swift-users wrote:

Hi everyone,

It looks like I’m still having issues exposing a CustomNSError to
Objective-C. I am generating errors of this type in Swift and then
trying to bridge them in one direction over to Objective-C.
From Objective-C, this Error type is being exposed as a _SwiftValue.

Do I have to mark this error as @objc and switch to using a raw enum? If
so, I fail to see the benefit of using CustomNSError or any of the new
error related protocols from Swift -> Objective-C.

Here’s my implementation:

publicenumMyError: CustomNSError, Equatable{

caseone([String: Any])

casetwo([String: Any])

casethree([String: Any])

/// The domain of the error.
publicstaticvarerrorDomain: String{
   return“MyError"
}

/// The error code within the given domain.
publicvarerrorCode: Int{
   switchself{
   case.one:
     return50000
   case.two:
     return50001
   case.three:
     return50002
   }
}

/// The user-info dictionary.
publicvarerrorUserInfo: [String: Any] {
   varuserInfo = [String: Any]()
   ifcaselet.one(info) = self{
     userInfo = info
   } elseifcaselet.two(info) = self{
     userInfo = info
   } elseifcaselet.three(info) = self{
     userInfo = info
   }

   returnuserInfo
}
}

Thanks

Ronak

On Sep 29, 2016, at 5:46 PM, Ronak via swift-users >>> <swift-users@swift.org <mailto:swift-users@swift.org> <mailto:swift-users@swift.org <mailto:swift-users@swift.org>>> wrote:

Ahh..thanks for the reply Zach. I didn’t actually see your reply until
now.

I’ll see how I can adjust my code.

Thanks for this!

On Sep 29, 2016, at 4:38 PM, Zach Waldowski <zach@waldowski.me <mailto:zach@waldowski.me> >>>> <mailto:zach@waldowski.me <mailto:zach@waldowski.me>>> wrote:

Error types themselves shouldn’t generally cross into Objective-C,
because you don’t get interop; for that, we have Error, which crosses
the bridge as NSError.

If it’s instructive to think of it this way, both Objective-C and
Swift should define errors in their best native way, and use NSError.
That’s, at least, the use case for CustomNSError and LocalizedError.

If you’re primarily exporting errors from Objective-C to be “seen” in
Swift, you want to look into the ns_error_domain attribute on the C
side. This generates a good deal of the enum Code: Int boilerplate
coming in to Swift, but it’s obnoxious to create those errors from Swift.

If you’re primarily exporting errors from Swift to Objective-C, you
can make any Swift type implement Error and CustomNSError, which can
then cross the bridge.

The happy path of full error interop in both directions is a little
more complicated. Generally you have to start with one of the above
approach and “mirror” some values in the other language. Consider the
following as a slightly over-wrought example of having your cake and
eating it too:

externNSString*constMyErrorDomain NS_REFINED_FOR_SWIFT;
externNSString*constMyErrorUserInfoStringKey NS_REFINED_FOR_SWIFT;

typedefNS_ENUM(NSInteger, MyErrorCode) {
   MyErrorCodeOne,
   MyErrorCodeTwo,
   MyErrorCodeThree,
} NS_REFINED_FOR_SWIFT;

enumMyError: CustomNSError{

   caseone(String)
   casetwo
   casethree

   staticvarerrorDomain: String{
       return__MyErrorDomain
   }

   varerrorCode: Int{
       switchself{
       case.one:
           return__MyErrorCode.one.rawValue
       case.two:
           return__MyErrorCode.two.rawValue
       case.three:
           return__MyErrorCode.three.rawValue
       }
   }

   varerrorUserInfo: [String: Any] {
       varuserInfo = [String: Any]()
       ifcaselet.one(string) = self{
           userInfo[__MyErrorUserInfoStringKey] = string
       }
       returnuserInfo
   }

}

On Sep 29, 2016, at 1:17 PM, Ronak via swift-users >>>>> <swift-users@swift.org <mailto:swift-users@swift.org> <mailto:swift-users@swift.org <mailto:swift-users@swift.org>>> wrote:

Hello all,

We are proceeding to update all of our Swift code to Swift 3 now and
had a few questions about the proper way to implement Errors. We
need these entities to be available in Objective-C and they are
actively being used in Swift classes marked as @objc.

I
read: https://github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md completely
and came up with this implementation:

/// The enumeration of the possible error codes in the Foundation
error domain
@objcpublicclassFoundationError: NSObject, CustomNSError{

   /// The underlying error code
   privateletcode: FoundationError.Code

   /// The type of an error code.
   @objcpublicenumCode: Int{

       /// An ARCOperationCondition failed during evaluation
       caseoperationConditionFailed = 10000

       /// An ARCOperation failed during execution
       caseoperationExecutionFailed = 10001
   }

   /// The domain of the error.
   publicstaticvarerrorDomain: String{
       return"FoundationError"
   }

   /// The error code within the given domain.
   publicvarerrorCode: Int{
       returncode.rawValue
   }

   /// The user-info dictionary.
   publicleterrorUserInfo: [String: Any]

   /// Initializes a new FoundationError with an empty userInfo
dictionary
   ///
   /// - parameter code: one of the available error codes
   ///
   /// - returns: a new instance of FoundationError
   publicconvenienceinit(code: FoundationError.Code) {
       self.init(code: code, userInfo: [:])
   }

   /// Initializes a new FoundationError with an userInfo dictionary
   ///
   /// - parameter code: one of the available error codes
   /// - parameter userInfo: the user-info dictionary
   ///
   /// - returns: a new instance of FoundationError
   publicinit(code: FoundationError.Code, userInfo: [String: Any]) {
       self.code = code
       errorUserInfo = userInfo
   }

   /// Computes whether two FoundationErrors are equal
   ///
   /// - parameter object: a FoundationError
   ///
   /// - returns: true, if the two errors are equal
   publicoverridefuncisEqual(_object: Any?) -> Bool{
       guardletobject = object as? FoundationErrorelse{ returnfalse}

       returnerrorCode == object.errorCode &&
errorUserInfo.keys.elementsEqual(object.errorUserInfo.keys)
   }
}

My question is whether this is the correct way to do this now; or is
there another solution we should be doing? We would like to follow
Swift Best Practices here, but unfortunately, the documentation is
quite vague on this subject.

Thanks for your help!

Ronak Patel