[swift-evolution] [Accepted] SE-0112: Improved NSError Bridging

Kevin Ballard kevin at sb.org
Fri Aug 5 14:59:00 CDT 2016


If all you want to do is get the localized description, then you can
just say `(error as NSError).localizedDescription`.

-Kevin

On Fri, Aug 5, 2016, at 02:59 AM, Jean-Daniel Dupas wrote:
>
>> Le 5 août 2016 à 05:12, Kevin Ballard via swift-evolution <swift-
>> evolution at swift.org> a écrit :
>>
>> With NSError, you *must* check the domain before trying to interpret
>> the code, or else your code is buggy and will behave incorrectly when
>> receiving an unexpected error.
>
> You must check before interpreting the code, but you don’t have to
> interpret the code to do something useful with an NSError.
>
> I think what Jon is looking for is ‘LocalizedError’ and
> ‘CustomNSError’.
> Is there any guarantee that casting an NSError into a CustomNSError or
> LocalizedError will always succeed ?
>
>> With SE-0112, instead of checking the domain, you check if the Error
>> can be casted to the particular error type that represents the
>> domain. There is a one-to-one correspondence between domains and the
>> new error types. For example, NSCocoaErrorDomain is represented by
>> CocoaError, NSURLErrorDomain is URLError, etc.
>>
>> So previously you might have code that looks like
>>
>> func handleError(error: NSError) {
>>     switch error.domain {
>>     case NSCocoaErrorDomain where error.code ==
>>     NSFileNoSuchFileError:
>>         let path = error.userInfo[NSFilePathErrorKey] as? String
>>         // handle error for path
>>     case NSURLErrorDomain where error.code == NSURLErrorTimedOut:
>>         let url = error.userInfo[NSURLErrorKey] as? NSURL
>>         // handle error for url
>>     default:
>>         // generic handling of other errors
>>     }
>> }
>>
>> And now you'd write that like
>>
>> func handleError(error: Error) {
>>     switch error {
>>     case let error as CocoaError where error.code ==
>>     .fileNoSuchFileError:
>>         let path = error.filePath
>>         // handle error for path
>>     case let error as URLError where error.code == .timedOut:
>>         let url = error.failingURL
>>         // handle error for url
>>     default:
>>         // generic handling of other errors
>>     }
>> }
>>
>> It's the same basic structure, except now you get strong typing, you
>> can't possibly forget to check the domain (which is a surprisingly
>> common bug I see in a lot of code), and you get convenient accessors
>> for the values stored in the user info.
>>
>> And if you don't actually care about any of the user info properties,
>> then the new version is much simpler than the old:
>>
>> func handleError(error: Error) {
>>     switch error {
>>     case CocoaError.fileNoSuchFileError:
>>         // handle error
>>     case URLError.timedOut:
>>         // handle error
>>     default:
>>         // generic handling of other errors
>>     }
>> }
>>
>> It's similar to checking the code without the domain in the old
>> style, except now it checks the domain automatically, so you *still*
>> can't accidentally interpret an error's code in the wrong domain.
>>
>> -Kevin Ballard
>>
>> On Thu, Aug 4, 2016, at 11:00 AM, Jon Shier via swift-
>> evolution wrote:
>>> Doug:
>>> Thanks for indulging me so far, I think I’ve almost got it. Prior to
>>> this, using NSError, I could just look at the relevant properties of
>>> the error if I needed to see what type it was. Network errors had
>>> different codes from CloudKit errors, POSIX errors were underlying
>>> FileManager errors. A bit complex due to the undocumented nature of
>>> so many of these errors, but I could ignore any aspect of the error
>>> I didn’t care about. Now, however, it seems I must always care about
>>> what types of errors come out of various methods, as I’ll need to
>>> cast to the appropriate types to get useful information. For
>>> example, how would you handle the CloudKit errors I mentioned
>>> before? It seems to me like I would need to, at the point where I
>>> need to extract useful information, do a switch on various casts.
>>> First, try casting to CKError, then to CocoaError (?), and then
>>> likely produce a fatalError if there’s an unexpected type. Or is
>>> Error guaranteed to always cast to something useful? I’ve read the
>>> proposal a few times now and it looks like a lot of casting is going
>>> to be required, I’m mostly curious about the recommended patterns,
>>> especially for asynchronous calls that don’t go through throw/catch.
>>>
>>>
>>>
>>> Jon
>>>
>>>
>>>> On Aug 2, 2016, at 5:36 PM, Douglas Gregor <dgregor at apple.com>
>>>> wrote:
>>>>
>>>>
>>>>> On Aug 2, 2016, at 2:19 PM, Jon Shier <jon at jonshier.com> wrote:
>>>>>
>>>>> Thanks Doug. I missed the rename, as earlier points still referred
>>>>> to ErrorProtocol. In regards to the CloudKit errors, I appreciate
>>>>> the strongly typed CKError, but why not have the methods return
>>>>> that type directly?
>>>>
>>>> Generally speaking, Cocoa only uses NSError—not specific subclasses
>>>> or NSError or other error types—because errors can occur at many
>>>> different places in the stack and be propagated up. A CloudKit
>>>> operation could fail because of some problem detected in a
>>>> different error domain—say, the general Cocoa error domain or
>>>> URLError domain—and that non-CloudKit error would get passed
>>>> through immediately. So, if you were assuming that every error you
>>>> get here had to be in the CloudKit error domain, I believe your
>>>> code was already incorrect. It is *possible* that CloudKit
>>>> translates/wraps all other errors, but that would be odd for a
>>>> Cocoa framework.
>>>>
>>>>> Every usage of these methods is going to require such a cast, so
>>>>> why require it in the first place? I don’t understand what
>>>>> advantage erasing the strongly type error that was just created
>>>>> has when the developer will just have to bring it right back. Or
>>>>> is this just a first implementation?
>>>>
>>>> There was never a strongly-typed error, and in most Cocoa cases
>>>> there shouldn’t be one because NSError covers all error domains, by
>>>> design.
>>>>
>>>> - Doug
>>>>
>>>>
>>>>>
>>>>>
>>>>> Jon
>>>>>
>>>>>> On Aug 2, 2016, at 4:20 PM, Douglas Gregor <dgregor at apple.com>
>>>>>> wrote:
>>>>>>
>>>>>>>
>>>>>>> On Aug 2, 2016, at 10:30 AM, Jon Shier via swift-evolution <swift-
>>>>>>> evolution at swift.org> wrote:
>>>>>>>
>>>>>>> I’m not sure where to put such feedback, but the ErrorProtocol
>>>>>>> to Error rename that accompanied the implementation of this
>>>>>>> proposal is very, very painful. It completely eliminates the
>>>>>>> very useful ability to embed an associated Error type inside
>>>>>>> other types, as those types now conflict with the protocol.
>>>>>>> Also, was this rename accompanied by an evolution proposal? It
>>>>>>> seems like the change was just made when this proposal was
>>>>>>> implemented.
>>>>>>
>>>>>> The rename was part of the proposal, in bullet #5 of the proposed
>>>>>> solution (which, amusing, pastes as bullet #1 below):
>>>>>>
>>>>>>  1. Rename ErrorProtocol to Error: once we've completed the
>>>>>>     bridging story, Error becomes the primary way to work with
>>>>>>     error types in Swift, and the value type to which NSError is
>>>>>>     bridged:


>>>>>> func handleError(_ error: Error, userInteractionPermitted: Bool)
>>>>>>
>>>>>>
>>>>>>> Also, the adoption of this proposal by the Cocoa(Touch)
>>>>>>> frameworks as seen in Xcode 8 beta 4 has made asynchronous error
>>>>>>> handling quite a bit more arduous. For example, the CKDatabase
>>>>>>> method fetch(withRecordID recordID: CKRecordID,
>>>>>>> completionHandler: (CKRecord?, Error?) -> Void) returns an
>>>>>>> `Error` now, meaning I have to cast to the specific `CKError`
>>>>>>> type to get useful information out of it. Is this just an
>>>>>>> unfortunate first effort that will be fixed, or is this the
>>>>>>> expected form of these sorts of APIs after this proposal?
>>>>>>
>>>>>> Prior to this proposal, you would have had to check the domain
>>>>>> against CKErrorDomain anyway to determine whether you’re looking
>>>>>> at a CloudKit error (vs. some other error that is passing through
>>>>>> CloudKit), so error bridging shouldn’t actually be adding any
>>>>>> work here—although it might be making explicit work that was
>>>>>> already done or should have been done. Once you have casted to
>>>>>> CKError, you now have typed accessors for information in the
>>>>>> error:
>>>>>>
>>>>>> extension CKError {
>>>>>>   /// Retrieve partial error results associated by item ID.
>>>>>>   public var partialErrorsByItemID: [NSObject : Error]? {
>>>>>>     return userInfo[CKPartialErrorsByItemIDKey] as? [NSObject :
>>>>>>     Error]
>>>>>>   }
>>>>>>
>>>>>>   /// The original CKRecord object that you used as the basis for
>>>>>>   /// making your changes.
>>>>>>   public var ancestorRecord: CKRecord? {
>>>>>>     return userInfo[CKRecordChangedErrorAncestorRecordKey] as?
>>>>>>     CKRecord
>>>>>>   }
>>>>>>
>>>>>>   /// The CKRecord object that was found on the server. Use this
>>>>>>   /// record as the basis for merging your changes.
>>>>>>   public var serverRecord: CKRecord? {
>>>>>>     return userInfo[CKRecordChangedErrorServerRecordKey] as?
>>>>>>     CKRecord
>>>>>>   }
>>>>>>
>>>>>>   /// The CKRecord object that you tried to save. This record is
>>>>>>   based
>>>>>>   /// on the record in the CKRecordChangedErrorAncestorRecordKey
>>>>>>   key
>>>>>>   /// but contains the additional changes you made.
>>>>>>   public var clientRecord: CKRecord? {
>>>>>>     return userInfo[CKRecordChangedErrorClientRecordKey] as?
>>>>>>     CKRecord
>>>>>>   }
>>>>>>
>>>>>>   /// The number of seconds after which you may retry a request.
>>>>>>   This
>>>>>>   /// key may be included in an error of type
>>>>>>   /// `CKErrorServiceUnavailable` or `CKErrorRequestRateLimited`.
>>>>>>   public var retryAfterSeconds: Double? {
>>>>>>     return userInfo[CKErrorRetryAfterKey] as? Double
>>>>>>   }
>>>>>> }
>>>>>> - Doug
>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> Jon Shier
>>>>>>>
>>>>>>>
>>>>>>>> On Jul 12, 2016, at 8:44 AM, Shawn Erickson via swift-evolution
>>>>>>>> <swift-evolution at swift.org> wrote:
>>>>>>>>
>>>>>>>> Thanks for the effort on the proposal and discussion and thanks
>>>>>>>> to those working in the implementation.
>>>>>>>>
>>>>>>>> -Shawn
>>>>>>>> On Tue, Jul 12, 2016 at 12:25 AM Charles Srstka via swift-
>>>>>>>> evolution <swift-evolution at swift.org> wrote:
>>>>>>>>> Wow, thanks! I’m delighted that Apple found this improvement
>>>>>>>>> to be worth inclusion in Swift 3. This will truly make the
>>>>>>>>> language much nicer to use with the Cocoa frameworks.
>>>>>>>>>
>>>>>>>>> Thanks!
>>>>>>>>>
>>>>>>>>> Charles
>>>>>>>>>
>>>>>>>>> > On Jul 11, 2016, at 11:19 PM, Chris Lattner via swift-
>>>>>>>>> > evolution <swift-evolution at swift.org> wrote:
>>>>>>>>> >
>>>>>>>>> > Proposal Link:
>>>>>>>>> > https://github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md
>>>>>>>>> >
>>>>>>>>> > The review of "SE-0112: Improved NSError Bridging" ran from
>>>>>>>>> > June 30 ... July 4, 2016. The proposal has been *accepted*:
>>>>>>>>> >
>>>>>>>>> > The community and core team agree that this proposal is a
>>>>>>>>> > huge step forward that enriches the experience working with
>>>>>>>>> > and extending the Cocoa NSError model in Swift.  The core
>>>>>>>>> > team requests one minor renaming of
>>>>>>>>> > "attemptRecovery(optionIndex:andThen:)" to
>>>>>>>>> > "attemptRecovery(optionIndex:resultHandler:)”.  It also
>>>>>>>>> > discussed renaming CustomNSError and RecoverableError, but
>>>>>>>>> > decided to stay with those names.
>>>>>>>>> >
>>>>>>>>> > Thank you to Doug Gregor and Charles Srstka for driving this
>>>>>>>>> > discussion forward, and for Doug Gregor taking the charge on
>>>>>>>>> > the implementation effort to make this happen for Swift 3!
>>>>>>>>> >
>>>>>>>>> > -Chris Lattner
>>>>>>>>> > Review Manager
>>>>>>>>> >
>>>>>>>>> > _______________________________________________
>>>>>>>>> > swift-evolution mailing list
>>>>>>>>> > swift-evolution at swift.org
>>>>>>>>> > https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>>>>>
>>>>>>>>> _______________________________________________
>>>>>>>>> swift-evolution mailing list
>>>>>>>>> swift-evolution at swift.org
>>>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>>>> _______________________________________________
>>>>>>>> swift-evolution mailing list
>>>>>>>> swift-evolution at swift.org
>>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>>>
>>>>>>> _______________________________________________
>>>>>>> swift-evolution mailing list
>>>>>>> swift-evolution at swift.org
>>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>> _________________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160805/cee8fc12/attachment.html>


More information about the swift-evolution mailing list