14

I am trying to dyanmically cast to a class in Swift. Is this possible? Here is the code I am trying to use:

let stringClass: AnyClass = NSString.self
let anyObject: AnyObject = "foo"
let string = anyObject as! stringClass

The code fails to compile at the cast. Is this possible and if so, why is the right syntax?

Real use case

Here is the real issue. I am attempting to refactor this code:

switch (value) {
    case "valueOne":
        viewController = storyboard.instantiateViewController(withIdentifier: "foo") as! FirstViewController
    case "valueTwo":
        viewController = storyboard.instantiateViewController(withIdentifier: "bar") as! SecondViewController
    default:
        return nil
}

into:

let controllersDictionary: [String: (String, UIViewController.Type)] = [
    "valueOne" : ("bar", FirstViewController.self),
    "valueTwo" : ("foo", SecondViewController.self)
]
let tuple = controllersDictionary[value]!
let identifier = tuple.0
let cast = tuple.1
let viewController = storyboard.instantiateViewController(withIdentifier: identifier) as! cast
10
  • 2
    Does dynamic casting even make sense in a statically typed system? Commented Aug 5, 2016 at 15:00
  • 1
    Yes! In this case, we were aiming to cast to different types under specific conditions (think of instantiating multiple different ViewControllers from a storyboard) Commented Aug 5, 2016 at 15:07
  • 2
    This problem is usually solved using associated types or generics. Commented Aug 5, 2016 at 15:23
  • 3
    "think of instantiating multiple different ViewControllers from a storyboard" I'm having a failure of imagination. Can you show that code so that we can see what the practical problem really is? Commented Aug 5, 2016 at 17:10
  • 1
    @AlexCurran I still don't see why you need the cast at that moment. Why isn't the identifier sufficient? The viewController will in fact be an instance of the right view controller subclass, automatically, so what's the cast for? Commented Aug 5, 2016 at 20:09

5 Answers 5

7

I'm not sure exactly what you're trying to achieve, but here's a working version of your example:

func cast<T>(value: Any, to type: T) -> T? {
    return castedValue as? T
}

let inputValue: Any = "this is a test"
let inputType = String.self()
let casted = cast(value: inputValue, to: inputType)

print(casted)
Sign up to request clarification or add additional context in comments.

6 Comments

The problem with this is that it works with the static type of inputType (therefore you could simply do the conditional cast directly) – if you write let inputType : Any = Int(), then the cast function will still succeed. Although as you correctly say in your above (and below) comment, this kind of conversion doesn't really make too much sense in a statically typed system anyway.
@Hamish Yep. None of this makes sense :p
Hmm OK. So in terms of class structure, what class is s inputType?
And it does make sense, just because you have static types doesn't mean you always know the type! My example is possible in Java and I would like to understand if it is possible in Swift, and if not, why.
You can option click an identifier to see more information about it, including its (infered or explicit) type
|
3

I'm not seeing what the cast at this point is for. You can write:

let controllersDictionary: [String: String] = [
    "valueOne" : "bar",
    "valueTwo" : "foo"
]
let identifier = controllersDictionary[value]!
let viewController = storyboard.instantiateViewController(withIdentifier: identifier)

The cast does nothing for you in the code that you have shown. viewController is typed as UIViewController, but it is the correct underlying view controller subclass thanks to polymorphism; whatever the class is in the storyboard, that's the class of this instance.

The only time you need to cast down is when you have to message an instance with a message belonging only to the subclass, and you have not shown any such need at this point in your code.

Comments

1

While there are/will be ways to make this kind of thing work, the Swifty solution (IMO) is to have your desired classes adhere to a protocol that defines the shared behavior you're trying to use, or simply use a super class they have in common

This allows the dynamism requried (in most cases at least) while still allowing the compile-time checks that prevent run time errors.

For your example,

protocol Stringable {
    func toString() -> String
}

extension String: Stringable {
    func toString() -> String {
        return self
    }
}

let thing = "foo"
let anything: Any = thing
let test: String? = (anything as? Stringable)?.toString()

Note that this requires "Any" rather than "AnyObject" since you need to cast to a protocol

Since you mentioned ViewControllers, I thought this might help:

static func createViewController<T: UIViewController>(storyboard: String, scene: String) -> T? {
    return  UIStoryboard(name: storyboard, bundle: nil).instantiateViewControllerWithIdentifier(scene) as? T
}

1 Comment

The storyboard example you gave is very close to what I want! This would work well. I was just seeing if there is a way of doing in as I wanted. But thank you :)
0

The x as! Y.Type syntax only works when Y is explicitly stated.

So, the compiler wants x as! NSString. The compiler crash is a bug, I suggest that you file a report.

And just think about it for a second, how would that even help you? stringClass is of type AnyClass, and you're force casting an AnyObject which already conforms to AnyClass. You should cast when you have a static type in mind, because casting doesn't really make any sense otherwise.

If you want to check, however, whether your object's class is a subclass of a particular type, you'd use:

x is Y.Type

5 Comments

"You should cast when you have a static type in mind, because casting doesn't really make any sense otherwise." exactly. If the rest of the static type system doesn't know about your dynamic type, then what good would it be?
Please see my updated question with the real example to see if it is clearer what I'm trying to achieve.
@AlexanderMomchliov please stop implying my question is a "bad" question, that is not really in the community spirit. I'm sorry if it wasn't clear enough for you
@AlexCurran My apologies, if it came off that way, but I don't mean to come off like that at all. I'm merely suggesting that the solution you have in mind might not be a viable one in a static language to solve the problem you have. If you give us more details about the big picture, we can suggest a more appropriate solution
no problem,thanks for the reply. I've updated my question with the exact use case now :)
0

Its possible so long as you can provide "a hint" to the compiler about the type of... T. So in the example below one must use : String?.

func cast<T>(_ value: Any) -> T? {
    return value as? T
}

let inputValue: Any = "this is a test"
let casted: String? = cast(inputValue) 
print(casted) // Optional("this is a test")
print(type(of: casted)) // Optional<String>

Why Swift doesn't just allow us to let casted = cast<String>(inputValue) I'll never know.


One annoying scenerio is when your func has no return value. Then its not always straightford to provide the necessary "hint". Lets look at this example...

func asyncCast<T>(_ value: Any, completion: (T?) -> Void) {
    completion(value as? T)
}

The following client code DOES NOT COMPILE. It gives a "Generic parameter 'T' could not be inferred" error.

let inputValue: Any = "this is a test"
asyncCast(inputValue) { casted in
    print(casted) 
    print(type(of: casted))
}

But you can solve this by providing a "hint" to compiler as follows:

asyncCast(inputValue) { (casted: String?) in
    print(casted) // Optional("this is a test")
    print(type(of: casted)) // Optional<String>
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.