McFly says: I keep getting the following error and can’t seem to find solution on stackoverflow: “Cannot convert value of type ‘String?’ to type ‘NSString’ in coercion”
First the answer: you have to unwrap an optional before casting it to a different type. “String?” means a value is stored within an Optional enumeration, which can contain a value or be nil. That’s why you’re getting the error.
You don’t want to force unwrap so you’ll want to conditionally bind that string and cast it to NSString. But before going there, let me step back a moment and discuss why you have an optional string to begin with. You may have used an optional initializer, or called a function that returns an optional string:
let test = "abcdef" let optionalString = String(test)
Or you more typically may have looked up a key in a dictionary, and your variable is actually typed as Any?
, not String?
.
let optionalValue = dict["key"]
The thing that kept nagging at me is that it’s pretty rare to pick up optional strings and then want to convert them to NSString. At first I thought McFly was pulling values from a JSON dictionary or the like (which is [AnyHashable: Any]) but the error message for calling a function that expects an NSString value with an Any optional is “error: cannot convert value of type ‘Any?’ to expected argument type ‘NSString'” so no, he really was working with String?
and NSString
.
I asked McFly why he had a String?, and it turns out he was using this API:
My Post class has the following declared:
private var _imageURL: String?
In any case (leaving apart the sin of naming a string as “URL”, argh), given this scenario, you have two goals:
- Safely unwrap the value to get at the string
- Convert that string to NSString so you can pass it to the function
Don’t do this:
functionCall(string! as NSString)
Forced unwrapping kills Tinkerbell quicker than kids who refuse to applaud. Instead, conditionally unwrap and *then* cast:
if let string = optionalString { myFunction(string as NSString) }
(And yes, you usually shadow with the same name as the optional string so you don’t accidentally collide with a different existing symbol. I wanted to make clear the roles of the newly bound unwrapped variable and the existing optional string)
To do everything at once, so you have a bound local variable that stores an NSString instance, you can cast your optional string to Optional<NSString> and then conditionally bind:
if let string = (optionalString as Optional<NSString>) { myFunction(string) }
This solution is easy for new devs because you can explain: “What you’ve got is an Optional<String> and what you want is an Optional<NSString>, so cast with as
, which can be checked at compile time and is guaranteed to succeed.
Experienced developers might prefer optional mapping:
if let string = optionalString.map({ $0 as NSString }) { myFunction(string) }
This is not an especially obvious solution for new learners. Assuming you’ve already gotten past the basic concept of optionals that can store either a value or nil via the Optional enumeration, picking up the notion of “mapping” optionals let alone collections isn’t intuitively clear especially without an existing background in functional languages.
The nice advantage to this approach though is that it easily expands to type-erased casting. Substitute flatMap
for map
so you don’t conditionally bind an Optional(Optional).
if let string = string.flatMap({ $0 as? NSString }) { myFunction(string) }
In any case, I told McFly, I’d write this up. And, as it turned out, I cut out about half or more of this post to save for another day because there are some particularly interesting ways that Swift handles casting “Any?” lookups from dictionaries.
As always, I’m sure I probably messed up somewhere in this post, so I invite you as always to offer feedback and corrections. Thanks!
6 Comments
I think I’ve got my head around mapping and how it could be applied to the contents of an optional. But I still don’t see why the map solution is superior to the preceding code.
But overall, helpful as usual.
The as? operator is also able to look through Optionals, handling all the mapping on your behalf, so that ‘if let nsstring = optionalString as? NSString’ works.
Not exactly:
In my (not very exhaustive) tests last night, that only works for Any? Look for discussion on Swift Evolution soon.
That warning is a bug. The compiled program should work, though.
I usually do one of these :
if let string = optionalString as NSString? {
myFunction(string)
}
or
if let string: NSString = optionalString {
myFunction(string)
}
Hi Erica.
1) Maybe you mean:
> functionCall(optionalString! as NSString)
2) There is no shadowing in your example:
> if let string = optionalString { … }
3) Maybe you mean:
> if let string = optionalString.flatMap({ $0 as? NSString }) { … }