Looking for swift syntax help to convert/represent an Error?

I've implemented a protocol called DescribedError that is implemented by NSError.

I'm creating a URLSession data task thusly:

        let session = URLSession.shared
        let task = session.dataTask(with: request) { (data, response, error) in


error is defined as being of type Error?

In practice error is a nullable NSError


I want to create a variable called finalError of type DescribedError?


I've added code so NSError implements DescribedError


extension NSError: DescribedError {
    func title() -> String {
        return "myTitle"
    }

    func message() -> String {
  return "myMessage"
    }
}


now, to my question...


When I do this:

        let session = URLSession.shared
        let task = session.dataTask(with: request) { (data, response, error) in
               let finalError: DescribedError? = error

the compiler sez:

Cannot convert value of type 'Error?' to specified type 'DescribedError?'

Insert ' as! DescribedError'


But I'd rather not do this forced conversion, so I tried doing this:

            var finalError: DescribedError?
            if let error = error as? DescribedError {
                finalError = error
            }

compiler sez:

Conditional downcast from 'Error?' to 'DescribedError' is a bridging conversion; did you mean to use 'as'?


so I try doing this:

            var finalError: DescribedError?
            if let error = error as DescribedError {
                finalError = error
            }

but the compiler now sez:

'Error?' is not convertible to 'DescribedError'; did you mean to use 'as!' to force downcast?

Replace 'as' with 'as!'



basically, I want to do something like:


let finalError: DescribedError = error implements DescribedError ? error : nil


Is there a way to do this that does not generate a compiler warning or error? (or using a forced unwrap)


thanks,


Mike

Replies

I thnk the compiler proposes:


            var finalError: DescribedError? 
            if let error = error as DescribedError? { 
                finalError = error 
            }

when I use that snippet, the compiler returns an error:
'Error?' is not convertible to 'DescribedError?'; did you mean to use 'as!' to force downcast?
Replace 'as' with 'as!'

Why have you created a

DescribedError
protocol? What other classes implement it?

Looking at the code you posted, it seems like you could just implement your methods as an extension on

NSError
without the protocol, allowing you to use
as NSError?
.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

This is a good question. (I'm curious if my answer will still seem justifiable when I get to the end.)


I had originally created an enum called HealthError that included cases for the errors that could occur while fetching and uploading HK data


enum HealthError: Error
     case authError
     case fetchError


In order to present these errors to the user, I added

func title() -> String {

and

func message() -> String {

to HealthError


I eventually realized I can also encounter NSErrors (in the uploading task).

I considered adding

     case networkError(NSError)

to health Error

but instead opted to create the DescribedError protocol (implemented by both HealthError and NSError)


So I suspect I could render my original question moot by associating my network errors with a HealthError case.

This will/would allow me to remove the DescribedError protocol. My network code would then only need to deal in NSErrors.

The calling code could then create a HealthError that contains the original NSError etc.

I will likely end up doing this, however I'd still be curious if there is an answer to my original question. (at least academically)


thanks Quinn

In Swift, some conversions between Objective-C types and corresponding Swift types are non-failable.


NSString <-> String

NSData <-> Data

NSDate <-> Date

:


let name: String = "myName"
if let nsName = name as? NSString {
    //call some NSString only methods...
}

Conditional cast from 'String' to 'NSString' always succeeds


You simply use `as` for such conversion:

let name: String = "myName"
let nsName = name as NSString
//call some NSString only methods...


And the conversion NSError <-> Error is one of them.

You can convert your `HealthError` to `NSError`, always successfully by as-casting.

enum HealthError: Error {
    case authError
    case fetchError
    //...
}
let err = HealthError.authError as NSError

So, if you make `NSError` conform to `DescribedError`, you can convert your `HealthError` to `DescribedError` by as-casting.

protocol DescribedError {
    func title() -> String
    
    func message() -> String
}
extension NSError: DescribedError {
    func title() -> String {
        return "myTitle"
    }
    
    func message() -> String {
        return "myMessage"
    }
}
let descErr = HealthError.fetchError as DescribedError


Better find another way as suggested by eskimo.

hmm. I can definitely recreate your results in a new barebones project.


however in my main/working project the following code:

if let underlyingError = underlyingError as? DescribedError {
                return underlyingError.message()
            }

triggers a compiler warning:

Conditional cast from 'Error' to 'DescribedError' always succeeds


and then immediately following, this line:

let descError = underlyingError as DescribedError

triggers a compiler error:

'Error' is not convertible to 'DescribedError'; did you mean to use 'as!' to force downcast?


I guess this means somewhere in my some magical flag has been set. Otherwise I would have thought the above error conditions should not be able to co-exist. If somebody is curious, I'd be happy to help dig deeper on this, however I've got bigger fish to fry, and for now will just live with the spurious compiler warning.

No magical things. Assuming `underlyingError` is of type `Error?`.


As I wrote, with making `NSError` conform to `DescribedError`, `Error` to `DescribedError` is non-failable convesion,

as well as `Error?` to `DescribedError?`.

Swift compiler outputs warnings when using `as?` (or `as!`) for such conversions.


Please try `as`.

    if let underlyingError = underlyingError as DescribedError? {
        //...
    }

correct; in the above example, NSError does conform to DescribedError

however underlyingError is of type Error (non-optional)
therefore

if let underlyingError = underlyingError as DescribedError? {

generates:
Cannot convert value of type 'Error' to type 'DescribedError?' in coercion

so I tried changing to:

if let underlyingError = underlyingError as DescribedError {

this lead to two errors:
'Error' is not convertible to 'DescribedError'; did you mean to use 'as!' to force downcast?
and

Initializer for conditional binding must have Optional type, not 'DescribedError'


next I switch underlyingError's type to be Error?

I then retried the sytax you suggest:

if let underlyingError = underlyingError as DescribedError? {

In this case the compiler returns:

'Error?' is not convertible to 'DescribedError?'; did you mean to use 'as!' to force downcast?

This is the full code I tested (in Xcode 10.3, Command Line Tool project) and got no warnings nor errors.

import Foundation

protocol DescribedError {
    func title() -> String
    
    func message() -> String
}
extension NSError: DescribedError {
    func title() -> String {
        return "myTitle"
    }
    
    func message() -> String {
        return "myMessage"
    }
}

func test(_ underlyingError: Error?) {
    if let underlyingError = underlyingError as DescribedError? {
        print(underlyingError.message())
    }
}
func test(_ underlyingError: Error) {
    let descError = underlyingError as DescribedError
    print(descError.message())
}


Please show enough code to reproduce the same issue, there may be some magic where you have not shown yet.