I've read the Swift Evolution post on the improved bridging and some other sources online, but still something is missing.
Given this custom Error
enum:
public enum MyNetworkError: Error {
case networkOffline
case httpError(status: Int)
case unknown
case systemError(errno: Int)
}
An Objective-C app should be able to read the error object and somehow extract the error name (networkOffline
, httpError
, unknown
, systemError
) and the case arguments (httpError.status
and systemError.errno
).
The result of converting the above to NSError
is surprising, and I'm trying to understand how to improve things:
let nse_A = MyNetworkError.networkOffline as NSError
let nse_B = MyNetworkError.httpError(status: 503) as NSError
let nse_C = MyNetworkError.unknown as NSError
let nse_D = MyNetworkError.systemError(42) as NSError
First, the generated error codes. It appears that the cases that have arguments, regardless of their order, get code
starting from zero:
print(nse_A.code) // 2 (expected: 0)
print(nse_B.code) // 0 (expected: 1)
print(nse_C.code) // 3 (expected: 2)
print(nse_D.code) // 1 (expected: 3)
Given an error code reported by the app, now it's harder to tell the actual error case.
Second, I expected such a smart mechanism (especially since it's compiler-generated) to also copy the case arguments to the userInfo
dictionary - but it doesn't.
Am I doing it wrong, or do I have to fully implement the CustomNSError
protocol to get meaningful and consistent NSError
objects? That's an option, of course, but I expected it to be done automatically (kind of like how Codable
does its magic).
In addition, can the app get the error case name as a String
?
For reference, the above snippets were executed in a Xcode 10.3 Playground.
Short answer: For integer-based enum error types the error values are mapped to NSError
codes as expected. For all other error types (like enums with associated values) you have to implement the CustomNSError
protocol in order to control the NSError
code and user info.
Some details: Only for integer-based error types the code is bridged from Swift to NSError
, for example:
public enum IntNetworkError: Int, Error {
case networkOffline = 13
case httpError
case unknown
case systemError
}
let err = IntNetworkError.httpError as NSError
print(err.code) // 14
That is a special case implemented in ErrorType.swift. For all other error types, the default implementation is in ErrorDefaultImpls.cpp, and that returns the “tag” for enum types, and 1
for all other types. Example:
struct StringError: Error {}
let serr = StringError() as NSError
print(serr.code) // 1
The enum “tag” is described in the Type Layout document. For enums with associated values, this does not necessarily follow the order in which the cases are declared. That is why you observed “unexpected” NSError
codes.
Therefore the correct approach is to implement the CustomNSError
protocol, the compiler does not synthesize this for you.
extension MyNetworkError: CustomNSError {
public static var errorDomain: String {
return "MyNetworkError"
}
public var errorCode: Int {
switch self {
case .networkOffline: return 1
case .httpError: return 2
case .unknown: return 3
case .systemError: return 4
}
}
public var errorUserInfo: [String : Any] {
switch self {
case .httpError(let status):
return [ "status": status]
case .systemError(let errno):
return [ "errno": errno]
default:
return [:]
}
}
}
let nse_B = MyNetworkError.httpError(status: 503) as NSError
print(nse_B.code) // 2
print(nse_B.userInfo) // ["status": 503]