Swift: More than you probably want to know about type introspection

Objective-C is very very good at introspection. You can pull things into and out of types, and generally have a blast metaprogramming yourself to whatever goal you have in mind. Swift? Not so much.

Here is the example that the Swift Programming Language manual gives for printing a type name:

class SomeBaseClass {
class func printClassName() {
println("SomeBaseClass")
}

If you type "Hello".dynamicType into a playground, it returns (Metatype), which is as frustrating as it is useless.

This morning, I was kicking around a bunch of code, trying to search for something better. This is where I got started, which is pretty unsatisfying. Everything that follows requires importing Foundation.

func PrintableTypeName(variable : T) -> String
{
    // Test for direct ObjC
    if let v = variable as? NSObject
    {
        return v.classForCoder.description()
    }
    
    // If the object *can* be converted to ObjC type, do so
    // However you lose any specifics as to type
    // Comment this out for more interesting results
    
    if let v : NSObject = bridgeToObjectiveC(variable) as? NSObject
    {
        if let result : NSString = v.classForCoder.description()
        {
            return result
        }
    }
    
    // Native Swift
    switch variable {
    case let test as Double: return "Double"
    case let test as Int: return "Int"
    case let test as Bool: return "Bool"
    case let test as String: return "String"
    default: break
    }
    
    switch variable {
    case let test as Double[]: return "Double[]"
    case let test as Int[]: return "Int[]"
    case let test as Bool[]: return "Bool[]"
    case let test as String[]: return "String[]"
    default: break
    }
    
    return "*Unknown*"
}

Tom D. looked at that, chuckled, and suggested I try doing a protocol instead. So I came up with this, which has the advantage of being shorter and more extendable.

protocol DiscoverableTypeName {
    var typeName : String {get}
}

extension NSObject : DiscoverableTypeName {
    var typeName: String {
    get {return self.classForCoder.description()}}}

So if you want to create a new class, so long as it’s Objective-C-y, it will work automatically — although it returns a “mangled” version of custom class names. (More about that below.) You can also add conformance to structures but you have to do so on a one-by-one-basis.

extension CGRect : DiscoverableTypeName {
    var typeName : String {
    get {return "CGRect"}
    }
}

Lily Ballard finally woke up at that point, having had his morning coffee and a good chuckle at my struggles, and pointed out that I could simply use NSStringFromClass() with an object’s dynamic type. (This doesn’t work for structs.) For example:

class Foo {}
var f = Foo()
NSStringFromClass(f.dynamicType)

This returns _TtC11lldb_expr_03Foo. This is an LLDB “mangled” Swift string that specifies that Foo is a type (_T) of class (tC), folowed by 11-ish bytes of “lldb_expr_0”, followed by 3 bytes of the actual name, Foo. You can demangle that at the command line, although he adds this will probably go away in the GM.

% xcrun swift-demangle _TtC11lldb_expr_03Foo
_TtC11lldb_expr_03Foo ---> lldb_expr_0.Foo

Getting back to my typeName thing, keep in mind that it is bridging to ObjC instead of operating on a Swift string. Once you’ve gotten an ObjC class string, you can get the class back out from that, although I’m not sure how useful that is:

let classTest : AnyClass! = NSClassFromString("Hello".typeName)

As a final note, you can test for class using is.

let testArray : AnyObject[] = ["a", 1, 2.0]
var results : Bool[] = []
for t : AnyObject in testArray
{
results += t is String
}
results

2 Comments