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
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.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
This looks like a bug to me, I have filed it as SR-14322.
According to SE-0112 Improved NSError Bridging,
This works with struct and enum types, but apparently not with class types.
You can replace your
class SwiftError: Error
by astruct SwiftError: Error
to solve the problem. If that is not possible for some reason then the following “trick” worked in my test:That compiles and gives the expected results: