[swift-evolution] [swift-evolution-announce] [Review] SE-0112: Improved NSError Bridging

Charles Srstka cocoadev at charlessoft.com
Thu Jun 30 17:36:54 CDT 2016


> On Jun 30, 2016, at 1:30 PM, Scott James Remnant via swift-evolution <swift-evolution at swift.org> wrote:
> 
> +1
> 
> I continually find the use of `as` for bridging between Objective-C and Swift types to be confusing, since they do not have a true place in the Swift type hierarchy and are not simple casts. The `catch let error as NSError` construct always implies that NSError is a true type that can be thrown, and this confusion gets worse in the places where NSError “leaks” out of APIs and you can’t actually cleanly bridge from an ErrorProtocol anyway.

The weird and convoluted behavior of the “as” keyword is one of the uglier (and more error-prone) warts in the language as it currently stands. There was a proposal a while back to remove the bridging behavior from the “as” keyword, greatly simplifying its behavior, but discussion on it seems to have died down. I hope it can be revived once this proposal is accepted, since the need for “as NSError” was one of the main problems with that proposal.

> One comment though:
> 
> Why is the errorDescription of LocalizedError an optional? Why would a type conform to this protocol, and then choose not to provide its only extension to ErrorProtocol?

This one’s my fault; Gregory originally had this as a non-optional and I recommended changing it, because Cocoa uses a nil value for NSLocalizedDescriptionKey to indicate that the default behavior should be used to construct the error string. In my experience, this is usually in fact what you want, and NSLocalizedFailureReasonErrorKey is a better fit for most purposes. For example, when throwing an error in an NSDocument subclass:

override func read(from data: Data, ofType typeName: String) throws {
    let userInfo = [NSLocalizedFailureReasonErrorKey: "Something went wrong."]
    throw NSError(domain: "Foo", code: 1, userInfo: userInfo)
}

In the example above, the error is presented to the user as “The operation could not be completed. Something went wrong.”

However, if you fill in the localized description instead of the failure reason, like this:

override func read(from data: Data, ofType typeName: String) throws {
    let userInfo = [NSLocalizedDescriptionKey: "Something went wrong."]
    throw NSError(domain: "Foo", code: 1, userInfo: userInfo)
}

The user is shown “The operation could not be completed.” with no further information.

Even when you’re reporting errors directly, the behavior is different whether you provide the localized description or omit it. With a nil description, as below:

let userInfo = [NSLocalizedFailureReasonErrorKey: "Something went wrong."]
NSApp.presentError(NSError(domain: "Foo", code: 1, userInfo: userInfo))

The error is presented as “The operation could not be completed. Something went wrong.” By comparison, if we provide the description:

let userInfo = [NSLocalizedDescriptionKey: "Something went wrong."]
NSApp.presentError(NSError(domain: "Foo", code: 1, userInfo: userInfo))

The error is simply reported as “Something went wrong.” This seems somewhat brusque, compared to the more polite and blow-softening behavior of the former example.

Unfortunately, I can’t think of any way for this property to return a non-optional, human-readable string to the end user while still communicating to NSError that the field should be nil, unless the default implementation can either copy the code that NSError uses to generate this value, or call through to NSError to generate it. I do notice that NSError always returns something appropriate when you call -localizedDescription on it, although it has the advantage that that method is only used to retrieve the value, not to provide it, unlike here.

Charles

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160630/2f8ba91b/attachment.html>


More information about the swift-evolution mailing list