4

I've looked for similar questions on Google and SO and couldn't find anything directly related. There seem to be two similar (maybe?) questions in C#, but I don't know the language, so I didn't really understand the questions properly (How to cast object to type described by Type class?, and Cast a variable to a type represented by another Type variable?).

I was experimenting with writing a generic scene-changing function in my GameViewController in SpriteKit. I made a SceneChangeType enum to use as a parameter. The error came in trying to optional cast a variable to what I expected to be a generic Type.

Just to clarify, I'm sure there are lots of reasons why this isn't a good idea. I can think of other ways to handle scene-changing, like just writing separate methods for each scene change. I'm just curious from a technical standpoint why I'm getting an error that I can't understand.

The relevant code is as follows:

In GameViewController.swift

func genericSceneChangeWithType(sceneChangeType: SceneChangeType) {
    let expectedType = sceneChangeType.oldSceneType
    guard let oldScene = self.currentScene as? expectedType else { return }
    ...
}

enum SceneChangeType {
    case TitleSceneToGameScene
    var oldSceneType: SKScene.Type {
        switch self {
        case .TitleSceneToGameScene:
            return TitleScene.self
        }
    }
    ...
}

For clarification, TitleScene is a custom subclass of SKScene, and self.currentScene has type SKScene?.

On the line,

guard let oldScene = self.currentScene as? expectedType else { return }

I get the error,

'expectedType' is not a type

Am I just massively misunderstanding things here? I thought you could use similar syntax to return a generic type from a function (e.g. Swift: how to return class type from function).
Is the issue because it's a property?
Is this not even possible, or is there some other way to check the type if the expected type is not known until runtime?

To emphasise again, I'm not asking about better ways to change scenes, and I'm not sure I have any examples where this is absolutely necessary. But understanding why this doesn't work might help me, or others, to understand the workings of the language better.

Thank you.

Community
  • 1
  • 1
Joseph
  • 41
  • 3

2 Answers2

2

You have the right idea, you're just confusing runtime vs. compile-time information. When you cast with as, you need to provide a type at compile time. This could be an explicit type like String, or it could be a generic parameter (as generics are a compile-time feature). You cannot, however, pass a variable containing a type like expectedType.

What you can do instead is check:

if self.currentScene.dynamicType == expectedType

However, as this doesn't cast currentScene, it won't allow you to call any methods that are specific to expectedType. It also won't work if currentScene is a subclass of expectedType, and you probably want it to. The problem is that you're passing around types at runtime, when you need to know types at compile-time in order to call methods or cast.

What you need, therefore, are Swift language features that seem helpful but work at compile-time, and I can think of two:

Generics, something like:

func genericSceneChange<OldType: SKScene, NewType: SKScene>() {
    ...
}

Overloading, something like:

func changeTo(scene: OneType) {
    //do something
}

func changeTo(scene: OtherType) {
    //do something else
}
andyvn22
  • 14,696
  • 1
  • 52
  • 74
2

Thats because when you set expectedType to SceneChangeType, it's actually an instance of MetaType. A metatype type refers to the type of any type, including class types, structure types, enumeration types, and protocol types. Hence why you are unable to cast to it and receive that error message.

By passing in a Generic type, you should be able to get the functionality you are looking for.

func genericSceneChangeWithType<T>(sceneChangeType: T) {
  guard let oldScene = self.currentScene as? T else { return }  
}
brightintro
  • 1,016
  • 1
  • 11
  • 16
  • While generics are totally the way to go, this particular implementation uses his original `sceneChangeType` parameter name in a very strange way. Unfortunately, he's going to need to redesign the whole scene change system--`SceneChangeType` is inherently a runtime way of doing things. – andyvn22 Jul 16 '16 at 18:56
  • I agree with you, I was just trying to help him better understand why he was receiving the error **is not a type**. Don't necessarily agree with my generic function, just wanted to show him how to integrate it with his current approach. – brightintro Jul 16 '16 at 19:40