skip to Main Content

We had an issue with an Error object that crashes in Crashlytics’ Objective-c code because it doesn’t respond to userInfo (a member of NSError), while investigating I stumbled upon this weird behavior in Swift.

In a playground, I tried creating a class SwiftError implementing the Error protocol:

class SwiftError: Error {
}

let sError = SwiftError()

if sError is NSError { // Generates a warning: 'is' test is always true
    print("Success")
} else {
    print("Fail")
}
// Prints Fail

let nsError = sError as NSError
//Compiler Error: 'SwiftError' is not convertible to 'NSError'; did you mean to use 'as!' to force downcast?
  • Checking if the SwiftError is NSError gives a warning that it always succeeds but fails runtime.
  • Casting SwiftError as an NSError gives a compiler error.

Can someone help explain to me why this happens and how could I know if a class implementing the Error protocol actually is an NSError or not ?

Thanks!

2

Answers


  1. There is something odd about the nature of the bridging between NSError and Error. They are bridged for communication purposes — that is, they can travel back and forth between Swift and Cocoa; and an error that comes from Cocoa is an NSError (and NSError adopts the Error protocol in Swift, to allow for this); but your class that you declare as conforming to Error is itself not an NSError.

    because it doesn’t respond to userInfo

    If you need your Swift Error type to carry userInfo information for Cocoa’s benefit, then you were looking for the CustomNSError protocol.

    https://developer.apple.com/documentation/foundation/customnserror

    Login or Signup to reply.
  2. This looks like a bug to me, I have filed it as SR-14322.

    According to SE-0112 Improved NSError Bridging,

    Every type that conforms to the Error protocol is implicitly bridged to NSError.

    This works with struct and enum types, but apparently not with class types.

    You can replace your class SwiftError: Error by a struct SwiftError: Error to solve the problem. If that is not possible for some reason then the following “trick” worked in my test:

    let nsError = sError as Error as NSError
    

    That compiles and gives the expected results:

    class SwiftError: Error {}
    
    extension SwiftError: CustomNSError {
        public static var errorDomain: String { "MyDomain" }
        public var errorCode: Int { 13 }
        public var errorUserInfo: [String : Any] { ["Foo" : "Bar" ] }
    }
    
    let sError = SwiftError()
    
    let nsError = sError as Error as NSError
    print(nsError.userInfo)
    // Output: ["Foo": "Bar"]
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search