36

I have a generic class of type T and I would like to get the name of the type that passed into the class when instantiated. Here is an example.

class MyClass<T> {
    func genericName() -> String {
        // Return the name of T.
    }
}

I have been looking around for hours and I can't seem to find any way to do this. Has anyone tried this yet?

Any help is greatly appreciated.

Thanks

Rob
  • 4,149
  • 5
  • 34
  • 48
  • It depends on what you mean by "name". You can get the name according to Objective-C really easily, by printing the class object. However, for a class defined in Swift, this name may be mangled. If all you need is to have some idea of what it is, then it should be sufficient. – newacct Aug 25 '14 at 20:15

6 Answers6

34

You can return any types' name by using string interpolation:

class MyClass<T> {
    func genericName() -> String {
        return "\(T.self)"
    }
}

You can try it in a playground and it works as expected:

var someClass = MyClass<String>()
someClass.genericName() // Returns "Swift.String"
Bradley Hilton
  • 858
  • 1
  • 8
  • 7
27

String(describing: T.self) in Swift 3+

var genericTypeName: String {
    return String(describing: T.self)
}

Within the generic type, get the name of type T by converting T.self or type(of: T.self) to a String. I found that type(of:) was not necessary but it's worth being aware of since in other cases it removes other details about the Type.

The following example demonstrates getting the name of the generic type T within a struct and a class. It includes code to get the name of the containing type.

Example including class and struct

struct GenericStruct<T> {
    var value: T

    var genericTypeName: String {
        return String(describing: T.self)
    }

    var genericTypeDescription: String {
        return "Generic Type T: '\(genericTypeName)'"
    }

    var typeDescription: String {
        // type(of:) is necessary here to exclude the struct's properties from the string
        return "Type: '\(type(of: self))'"
    }
}

class GenericClass<T> {
    var value: T

    var genericTypeName: String {
        return String(describing: T.self)
    }

    var genericTypeDescription: String {
        return "Generic Type T: '\(genericTypeName)'"
    }

    var typeDescription: String {
        let typeName = String(describing: self)
        return "Type: '\(typeName)'"
    }

    init(value: T) {
        self.value = value
    }
}

enum TestEnum {
    case value1
    case value2
    case value3
}

let intGenericStruct: GenericStruct<Int> = GenericStruct(value: 1)
print(intGenericStruct.typeDescription)
print(intGenericStruct.genericTypeDescription)

let enumGenericStruct: GenericStruct<TestEnum> = GenericStruct(value: .value2)
print(enumGenericStruct.typeDescription)
print(enumGenericStruct.genericTypeDescription)

let intGenericClass: GenericClass<Int> = GenericClass(value: 1)
print(intGenericClass.typeDescription)
print(intGenericClass.genericTypeDescription)

let enumGenericClass: GenericClass<TestEnum> = GenericClass(value: .value2)
print(enumGenericClass.typeDescription)
print(enumGenericClass.genericTypeDescription)

Console Output

/*
Type: 'GenericStruct<Int>'
Generic Type T: 'Int'

Type: 'GenericStruct<TestEnum>'
Generic Type T: 'TestEnum'

Type: 'GenericClass<Swift.Int>'
Generic Type T: 'Int'

Type: 'GenericClass<TestEnum>'
Generic Type T: 'TestEnum'
*/
Mobile Dan
  • 6,444
  • 1
  • 44
  • 44
17

A pure swift way to achieve that is not possible.

A possible workaround is:

class MyClass<T: AnyObject> {
    func genericName() -> String {
        let fullName: String = NSStringFromClass(T.self)
        let range = fullName.rangeOfString(".", options: .BackwardsSearch)
        if let range = range {
            return fullName.substringFromIndex(range.endIndex)
        } else {
            return fullName
        }
    }
}

The limitations relies on the fact that it works with classes only.

If this is the generic type:

class TestClass {}

NSStringFromClass() returns the full name (including namespace):

// Prints something like "__lldb_expr_186.TestClass" in playground
NSStringFromClass(TestClass.self)

That's why the func searches for the last occurrence of the . character.

Tested as follows:

var x = MyClass<TestClass>()
x.genericName() // Prints "TestClass"

UPDATE Swift 3.0

func genericName() -> String {
    let fullName: String = NSStringFromClass(T.self)
    let range = fullName.range(of: ".")
    if let range = range {
        return fullName.substring(from: range.upperBound)
    }
    return fullName
}
geekay
  • 1,655
  • 22
  • 31
Antonio
  • 71,651
  • 11
  • 148
  • 165
  • Thank you for your reply. I tried your idea. Unfortunately most of the types that I will be setting in the generic do not conform to AnyObject. There is a chance that T could also be a protocol. – Rob Aug 26 '14 at 00:37
  • In that case I'm afraid you have to wait for better introspection support in swift. I have troubles achieving a simple thing like getting the [name of a protocol at runtime](http://stackoverflow.com/q/25442765/148357), using pure swift – Antonio Aug 26 '14 at 05:09
2

works for me:

class MyClass<T> {
    func genericName() -> String {
        return "\(type(of: self))"
    }
}
yonivav
  • 994
  • 11
  • 18
1

It's possible if your type parameter implements a common naming protocol.

In the example below the protocol Named ensures that the generic type implements the name class property.

Note that this works with both classes and value types since the latter can also be extended to conform to protocols, as illustrated with the Int below.

protocol Named {
    class var name: String { get }
}

class MyClass<T: Named> {
    func genericName() -> String {
        return T.name
    }
}

extension Int: Named {
    static var name: String { return "I am an Int" }
}

class Foo: Named {
    class var name: String { return "I am a Foo" }
}

enum Drink: Named {
    static var name: String { return "I am a Drink" }
}

MyClass<Int>().genericName()  // I am an Int
MyClass<Foo>().genericName()  // I am a Foo
MyClass<Drink>().genericName()  // I am a Drink
augustzf
  • 2,385
  • 1
  • 16
  • 22
  • 1
    This is a nice trick, but you should mention that it's pretty limited- e.g. no generic parameters for generic types, and you can't provide public extensions to internal types, can't mix and match access specifiers, and can't extend protocols this way, etc. – Puppy Sep 11 '15 at 11:15
1

Another possible solution that might help somebody:

Playground

import Foundation

class ClassType<T> {

    static func name () -> String
    {
        return "\(T.self)".componentsSeparatedByString(".").last!
    }
}

class MyClass {

}

func testClassName(){

    let className = ClassType<MyClass>.name()
    print(className)
}

testClassName()
brduca
  • 3,573
  • 2
  • 22
  • 30