Foundation likes to pass optional errors (versus, say, a unified Result
type) to completion handlers. A typical closure uses a (Value?, Error?) -> Void
signature,where Value
is some sort of data result that varies by operation.
A colleague was struggling to use conditional binding along with casting in his handler. Leaving aside for the moment any rational need to cast to NSError
, this is an interesting demonstration of how you perform these two operations synchronously in code.
A cast from Error
to NSError
is guaranteed to succeed, so you can use the as
operator. However, you must phrase the cast just right. Otherwise the Swift compiler emits warnings, as you see in the following screenshot. Here is an example of how you don’t get this job done properly:
As my friend complained in frustration: “But it tells me “did I mean as
” and when I switch `as?
` to `as
` then it complains that error
is Error?
and isn’t convertible to NSError
.”
This attempt followed the normal pattern of conditional casting. Swift automatically lifts double optionals into a single optional result when used this way, but the cast from Error
to NSError
will always succeed, so you can’t use as?
here.
To resolve, use a non-conditional cast to NSError?
and then perform conditional binding to unwrap the value:
enum Bad: Error { case luck } let error: Bad? = Bad.luck if let error = (error as NSError?) { print("Worked") }
The parens around the non-conditional cast make all the difference. Swift removes its warnings and makes everything work as expected.
8 Comments
Thank you!
I’ve been struggling with this for weeks, frustrated at the 1 build warning sitting there taunting me.
Interestingly, at least in my testing, Xcode 9 was not producing the same warning…
All my screen shots and code are Xcode 9 if that helps
I spoke to soon. Xcode 9 Beta 1 I wasn’t seeing this as a warning (AFAIR), but just tried in Beta 2 before fixing it and it showed the warning.
I’m still learning Swift and am probably out to lunch, but in the first example it appears that as? is being used to convert an optional to non-optional as well as changing types. Wouldn’t it make more sense to just cast optional to optional (Bad? to NSError?) and use “if let” to handle the optional to non-optional unwrapping?
Switching NSError to NSError? in the first example works but with a warning that the conditional cast always succeeds. Switching “as?” to “as” then gets rid of this warning. I’m confused why the parens would be needed — the last code example seems to work fine without them.
Am I missing something?
Oh, some of you folks are on Xcode 9 Betas. I used Xcode 8.3.3 and the last example worked fine without parens.
Also running Xcode 8.3.3, and the warning on let error = error as? NSError is intermittently generated.
Hi Erica,
Thanks for the tips! I’ve tried running the code without the parentheses
enum Bad: Error { case luck }
let error: Bad? = Bad.luck
if let error = error as NSError? {
print(“Worked”)
}
and it works for me. the error inside if statement is correctly cast to NSError.
i’m running this on Xcode 8.3.2 playground though so i’m not sure if things changed on Xcode 9.
This works in Xcode 9 beta 2, as well. The only warning that remains is for not consuming the value “error”.
if let _ = error as NSError? {
print("Worked")
}
works just fine