Obtener el nombre de clase del objeto como cadena en Swift

320

Obtener el nombre de clase de un objeto como Stringusando:

object_getClassName(myViewController)

devuelve algo como esto:

_TtC5AppName22CalendarViewController

Busco a la pura versión: "CalendarViewController". ¿Cómo obtengo una cadena limpia del nombre de la clase en su lugar?

Encontré algunos intentos de preguntas sobre esto, pero no una respuesta real. ¿No es posible en absoluto?

Bernd
fuente
alternativamente ... ¿cómo sería una función de analizador eficaz para esto?
Bernd
En caso de que desee verificar si un objeto es de cierta clase, vea esta respuesta .
significado-asuntos

Respuestas:

529

Cadena de una instancia :

String(describing: YourType.self)

Cadena de un tipo :

String(describing: self)

Ejemplo:

struct Foo {

    // Instance Level
    var typeName: String {
        return String(describing: Foo.self)
    }

    // Instance Level - Alternative Way
    var otherTypeName: String {
        let thisType = type(of: self)
        return String(describing: thisType)
    }

    // Type Level
    static var typeName: String {
        return String(describing: self)
    }

}

Foo().typeName       // = "Foo"
Foo().otherTypeName  // = "Foo"
Foo.typeName         // = "Foo"

Probado con class, structy enum.

Rudolf Adamkovič
fuente
44
¿Es este realmente el caso? De acuerdo con los documentos de String para este inicializador, "la biblioteca estándar de Swift proporciona automáticamente un resultado no especificado" cuando no se ajusta a Streamable, CustomStringConvertible o CustomDebugStringConvertible. Entonces, si bien puede estar mostrando el valor deseado ahora, ¿continuará haciéndolo en el futuro?
Tod Cunningham el
3
Usar esto en Xcode 7.3.1 y Swift 2.2 produce lo siguiente, lo cual no es ideal. '' let name = String (self) name String "<MyAppName.MyClassName: 0x ########>" ''
CodeBender
13
En Swift 3, la respuesta correcta es llamar String(describing: MyViewController.self)como se indica en algunas respuestas a continuación.
AmitaiB
10
¡Pero devuelve "MyViewController.TYPE"!
orkenstein
3
@ RudolfAdamkovič Sí, eso sería cierto. Pero no me gusta escribir el nombre de la clase de forma explícita (¿Qué pasa si cambio el nombre de la clase Fooa Foo2, pero crear una clase Fooa otra parte, que la fuerza de frenado del código). En caso de subclases, debe usar cualquiera type(of:)o Type.selfsegún lo que se adapte a sus necesidades. Probablemente type(of:)sea ​​más apropiado para obtener el nombre de la clase.
Marián Černý
182

ACTUALIZADO A SWIFT 5

Podemos obtener descripciones bonitas de nombres de tipos usando la variable de instancia a través del Stringinicializador y crear nuevos objetos de una clase determinada

Me gusta, por ejemplo print(String(describing: type(of: object))). Donde objectpuede haber una variable de instancia como array, un diccionario, an Int, a NSDate, etc.

Debido a que NSObjectes la clase raíz de la mayoría de las jerarquías de clase Objective-C, podría intentar hacer una extensión para NSObjectobtener el nombre de clase de cada subclase de NSObject. Me gusta esto:

extension NSObject {
    var theClassName: String {
        return NSStringFromClass(type(of: self))
    }
}

O podría hacer una función estática cuyo parámetro sea de tipo Any(El protocolo al que todos los tipos se ajustan implícitamente) y devuelva el nombre de la clase como Cadena. Me gusta esto:

class Utility{
    class func classNameAsString(_ obj: Any) -> String {
        //prints more readable results for dictionaries, arrays, Int, etc
        return String(describing: type(of: obj))
    }
} 

Ahora puedes hacer algo como esto:

class ClassOne : UIViewController{ /* some code here */ }
class ClassTwo : ClassOne{ /* some code here */ }

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Get the class name as String
        let dictionary: [String: CGFloat] = [:]
        let array: [Int] = []
        let int = 9
        let numFloat: CGFloat = 3.0
        let numDouble: Double = 1.0
        let classOne = ClassOne()
        let classTwo: ClassTwo? = ClassTwo()
        let now = NSDate()
        let lbl = UILabel()

        print("dictionary: [String: CGFloat] = [:] -> \(Utility.classNameAsString(dictionary))")
        print("array: [Int] = [] -> \(Utility.classNameAsString(array))")
        print("int = 9 -> \(Utility.classNameAsString(int))")
        print("numFloat: CGFloat = 3.0 -> \(Utility.classNameAsString(numFloat))")
        print("numDouble: Double = 1.0 -> \(Utility.classNameAsString(numDouble))")
        print("classOne = ClassOne() -> \((ClassOne).self)") //we use the Extension
        if classTwo != nil {
            print("classTwo: ClassTwo? = ClassTwo() -> \(Utility.classNameAsString(classTwo!))") //now we can use a Forced-Value Expression and unwrap the value
        }
        print("now = Date() -> \(Utility.classNameAsString(now))")
        print("lbl = UILabel() -> \(String(describing: type(of: lbl)))") // we use the String initializer directly

    }
}

Además, una vez que podamos obtener el nombre de la clase como String, podemos crear instancias de nuevos objetos de esa clase :

// Instantiate a class from a String
print("\nInstantiate a class from a String")
let aClassName = classOne.theClassName
let aClassType = NSClassFromString(aClassName) as! NSObject.Type
let instance = aClassType.init() // we create a new object
print(String(cString: class_getName(type(of: instance))))
print(instance.self is ClassOne)

¡Quizás esto ayude a alguien!

mauricioconde
fuente
2
Esta debería ser la respuesta aceptada. Gracias, estaba buscando una forma de imprimir el nombre de la clase sin tener que instanciarlo, y encontré la respuesta aquí. Gracias imprimir (String (BaseAsyncTask))
Bruno Coelho
44
classNameAsStringse puede simplificar a className, porque ese "nombre" implica su tipo. theClassNamees similar a la historia de Facebook, que originalmente se llama thefacebook.
DawnSong
"diccionary" ... la versión italiana de la colección de diccionarios de Swift:]
Andrew Kirna
Esta solución antepone el nombre del proyecto para cada etiqueta de clase, es decir ProjectTest.MyViewController. ¿Cómo me deshago de este nombre de proyecto? Solo quiero el nombre de la clase.
Sazzad Hissain Khan
132

Swift 5

Aquí está la extensión para obtener el typeNamecomo una variable (trabajar tanto con el tipo de valor como con el tipo de referencia).

protocol NameDescribable {
    var typeName: String { get }
    static var typeName: String { get }
}

extension NameDescribable {
    var typeName: String {
        return String(describing: type(of: self))
    }

    static var typeName: String {
        return String(describing: self)
    }
}

Cómo utilizar:

// Extend with class/struct/enum...
extension NSObject: NameDescribable {}
extension Array: NameDescribable {}
extension UIBarStyle: NameDescribable { }

print(UITabBarController().typeName)
print(UINavigationController.typeName)
print([Int]().typeName)
print(UIBarStyle.typeName)

// Out put:
UITabBarController
UINavigationController
Array<Int>
UIBarStyle
nahung89
fuente
12
¿Alguna idea de por qué obtengo MyAppName.MyClassName de eso? Solo estoy interesado en MyClassName
Andrew
@ Andrew, ¿podrías dar algo más específico en tu caso? Estoy usando esta extensión en muchos proyectos y todos responden según lo previsto.
nahung89
1
Bueno, si lo llamo desde la clase en sí misma, devuelve MyClassName, pero si lo envuelvo en un método que devuelve la cadena del nombre de la clase y lo llamo desde otra clase, devuelve MyAppName.MyClassName. Mientras buscaba, vi que todas las clases tienen un espacio de nombres implícito, por lo que si debe llamarlo fuera de su clase, obtendrá MyAppName.MyClassName en lugar de MyClassName. Luego debe confiar en la manipulación de cadenas para obtener el nombre de la clase.
Andrew
1
extensión muy útil
Hattori Hanzō
@ Andrew, no estoy seguro del funcionamiento actual, pero recuerdo que String (que describe: tipo (de: self)) solía devolver ModuleName.ClassName en un punto. Me había separado en base a. personaje y traído el último objeto.
BangOperator
31

Swift 3.0

String(describing: MyViewController.self)

Delgado
fuente
2
En realidad type(of: )es una exageración allí, String(describing: MyViewController.self)será suficiente
Alexander Vasenin
2
Esto crea una cadena como <App_Name.ClassName: 0x79e14450> para mí, que no es lo ideal
Doug Amos
¿No es así String.init(describing: MyViewController.self)?
Iulian Onofrei
2
@IulianOnofrei, puedes hacer eso, pero no inites necesario.
cuña
¡Deshazte por completo del prefijo AppName! Gracias
yuchen
28

Sugiero tal enfoque ( muy Swifty ):

// Swift 3
func typeName(_ some: Any) -> String {
    return (some is Any.Type) ? "\(some)" : "\(type(of: some))"
}

// Swift 2
func typeName(some: Any) -> String {
    return (some is Any.Type) ? "\(some)" : "\(some.dynamicType)"
}

No utiliza ni introspección ni demanda manual (¡ sin magia! ).


Aquí hay una demostración:

// Swift 3

import class Foundation.NSObject

func typeName(_ some: Any) -> String {
    return (some is Any.Type) ? "\(some)" : "\(type(of: some))"
}

class GenericClass<T> {
    var x: T? = nil
}

protocol Proto1 {
    func f(x: Int) -> Int
}


@objc(ObjCClass1)
class Class1: NSObject, Proto1 {
    func f(x: Int) -> Int {
        return x
    }
}

struct Struct1 {
    var x: Int
}

enum Enum1 {
    case X
}

print(typeName(GenericClass<Int>.self)) // GenericClass<Int>
print(typeName(GenericClass<Int>()))  // GenericClass<Int>

print(typeName(Proto1.self)) // Proto1

print(typeName(Class1.self))   // Class1
print(typeName(Class1())) // Class1
print(typeName(Class1().f)) // (Int) -> Int

print(typeName(Struct1.self)) // Struct1
print(typeName(Struct1(x: 1))) // Struct1
print(typeName(Enum1.self)) // Enum1
print(typeName(Enum1.X)) // Enum1
werediver
fuente
Lo cambiaron en Swift 3.0. Ahora puede obtener una secuencia detype(of: self)
Asad Ali
Actualizado a Swift 3.
werediver
Creo que esta es la mejor manera de obtener el nombre del tipo: cualquier instancia de clase, basada en NSObject, enum, typealiase, funciones, propiedades var y cualquier tipo, incluso constantes. Para los tipos, debe usar .self. Por ejemplo, typeName (Int.self), typeName (true.self). Tampoco debe preocuparse por el nombre del proyecto como parte (como AppProj. [Typename]) ¡Genial!
David.Chu.ca
23

Si tiene tipo Foo, el siguiente código le dará "Foo"en Swift 3 y Swift 4:

let className = String(describing: Foo.self) // Gives you "Foo"

El problema con la mayoría de las respuestas aquí es que te dan "Foo.Type"como la cadena resultante cuando no tienes ninguna instancia del tipo, cuando lo que realmente quieres es justo "Foo". Lo siguiente le da "Foo.Type", como se menciona en un montón de las otras respuestas.

let className = String(describing: type(of: Foo.self)) // Gives you "Foo.Type"

La type(of:)parte es innecesaria si solo quieres "Foo".

sethfri
fuente
21

En Swift 4.1 y ahora Swift 4.2 :

import Foundation

class SomeClass {
    class InnerClass {
        let foo: Int

        init(foo: Int) {
            self.foo = foo
        }
    }

    let foo: Int

    init(foo: Int) {
        self.foo = foo
    }
}

class AnotherClass : NSObject {
    let foo: Int

    init(foo: Int) {
        self.foo = foo
        super.init()
    }
}

struct SomeStruct {
    let bar: Int

    init(bar: Int) {
        self.bar = bar
    }
}

let c = SomeClass(foo: 42)
let s = SomeStruct(bar: 1337)
let i = SomeClass.InnerClass(foo: 2018)
let a = AnotherClass(foo: 1<<8)

Si no tiene una instancia alrededor:

String(describing: SomeClass.self) // Result: SomeClass
String(describing: SomeStruct.self) // Result: SomeStruct
String(describing: SomeClass.InnerClass.self) // Result: InnerClass
String(describing: AnotherClass.self) // Result: AnotherClass

Si tiene una instancia alrededor:

String(describing: type(of: c)) // Result: SomeClass
String(describing: type(of: s)) // Result: SomeStruct
String(describing: type(of: i)) // Result: InnerClass
String(describing: type(of: a)) // Result: AnotherClass
McNight
fuente
14

Para obtener el nombre de una clase Swift de un objeto, por ejemplo, para un objeto var: SomeClass (), use

String(describing: type(of: object))

Para obtener el nombre de una clase Swift de un tipo de clase, por ejemplo, SomeClass, use:

String(describing: SomeClass.self)

Salida:

"SomeClass"

Gurjit Singh
fuente
13

Puedes probar de esta manera:

self.classForCoder.description()
陈方涛
fuente
Esto funciona muy bien ya que incluye el "espacio de nombres" completo (el prefijo de membresía objetivo apropiado) también.
BonanzaDriver
Por cierto, también encontré que usar "String (self)" donde self es una instancia de MyViewController también funciona ... proporciona la misma información ... Xcode 7.1.
BonanzaDriver
12

Puede usar la función de biblioteca estándar Swift llamada _stdlib_getDemangledTypeNameasí:

let name = _stdlib_getDemangledTypeName(myViewController)
Jernej Strasner
fuente
@NeilJaff, ¿qué quieres decir con "solo una cadena opcional"? Devuelve una cadena y no es opcional.
Jernej Strasner
3
Este enfoque no devuelve los nombres de las clases en las API privadas para su información. Devolverá el nombre del primer nombre de clase base pública.
Tylerc230
Recibo el mensaje: Uso del identificador no resuelto '_stdlib_getDemangledTypeName'
Adobels
12

Para obtener el nombre del tipo como una cadena en Swift 4 (no he verificado las versiones anteriores), solo use la interpolación de cadenas:

"\(type(of: myViewController))"

Puede usar .selfun tipo en sí mismo y la type(of:_)función en una instancia:

// Both constants will have "UIViewController" as their value
let stringFromType = "\(UIViewController.self)"
let stringFromInstance = "\(type(of: UIViewController()))"
Misha Karpenko
fuente
8

También se pueden usar espejos:

let vc = UIViewController()

String(Mirror(reflecting: vc).subjectType)

NB: este método también se puede utilizar para estructuras y enumeraciones. Hay un displayStyle que da una indicación de qué tipo de estructura:

Mirror(reflecting: vc).displayStyle

El retorno es una enumeración para que pueda:

Mirror(reflecting: vc).displayStyle == .Class
Paul Ardeleanu
fuente
Gracias. Eso fue lo que funcionó para mí. Lo único (al menos en iOS 9) es que el subjectType terminaba en ".Type", por lo que tuve que eliminar esa parte de la cadena.
Nikolay Suvandzhiev
8

Swift 5.1

Sin embargo, puede obtener nombres de clase, estructura, enumeración, protocolo y NSObject Self.self.

print("\(Self.self)")
Victor Kushnerov
fuente
Tenga en cuenta que esto puede anteponer el nombre del módulo (diferente a String(describing: type(of: self)))
Ixx
7

Swift 3.0: puede crear una extensión como esta. Devuelve el nombre de la clase sin el nombre del proyecto

extension NSObject {
    var className: String {
        return NSStringFromClass(self as! AnyClass).components(separatedBy: ".").last ?? ""
    }

    public class var className: String {
        return NSStringFromClass(self).components(separatedBy: ".").last ?? ""
    }
}
Excepción
fuente
Cuando intento usar esto, aparece el error: No se pudo emitir el valor del tipo 'ProjectName.MyViewController' (0x1020409e8) a 'Swift.AnyObject.Type' (0x7fb96fc0dec0).
tylerSF
6

Puede ampliar NSObjectProtocolen Swift 4 de esta manera:

import Foundation

extension NSObjectProtocol {

    var className: String {
        return String(describing: Self.self)
    }
}

Esto hará que la variable calculada classNameesté disponible para TODAS las clases. El uso de este dentro de una print()en CalendarViewControllerimprimirá "CalendarViewController"en la consola.

Siddharth Bhatt
fuente
4

He estado buscando esta respuesta de vez en cuando. Uso GKStateMachine y me gusta observar los cambios de estado y quería una manera fácil de ver solo el nombre de la clase. No estoy seguro de si es solo iOS 10 o Swift 2.3, pero en ese entorno, lo siguiente hace exactamente lo que quiero:

let state:GKState?
print("Class Name: \(String(state.classForCoder)")

// Output:    
// Class Name: GKState
Troya
fuente
4

Puede obtener el nombre de la clase haciendo algo como:

class Person {}
String(describing: Person.self)
Ale Mohamad
fuente
3

Para obtener el nombre de la clase como String, declare su clase de la siguiente manera

@objc(YourClassName) class YourClassName{}

Y obtenga el nombre de la clase usando la siguiente sintaxis

NSStringFromClass(YourClassName)
Moin Uddin
fuente
3

Pruébelo reflect().summaryen Clase propia o instancia tipo dinámico. Desenvuelva los opcionales antes de obtener dynamicType; de ​​lo contrario, dynamicType es el contenedor opcional.

class SampleClass { class InnerClass{} }
let sampleClassName = reflect(SampleClass.self).summary;
let instance = SampleClass();
let instanceClassName = reflect(instance.dynamicType).summary;
let innerInstance = SampleClass.InnerClass();
let InnerInstanceClassName = reflect(innerInstance.dynamicType).summary.pathExtension;
let tupleArray = [(Int,[String:Int])]();
let tupleArrayTypeName = reflect(tupleArray.dynamicType).summary;

El resumen es una ruta de clase con tipos genéricos descritos. Para obtener un nombre de clase simple del resumen, pruebe este método.

func simpleClassName( complexClassName:String ) -> String {
    var result = complexClassName;
    var range = result.rangeOfString( "<" );
    if ( nil != range ) { result = result.substringToIndex( range!.startIndex ); }
    range = result.rangeOfString( "." );
    if ( nil != range ) { result = result.pathExtension; }
    return result;
}
dibujado hacia adelante
fuente
3

Las soluciones anteriores no funcionaron para mí. La mayoría de los problemas se mencionan en varios comentarios:

MyAppName.ClassName

o

MyFrameWorkName.ClassName

Estas soluciones funcionaron en XCode 9, Swift 3.0:

Lo nombré classNameCleanedpara que sea más fácil acceder y no entre en conflicto con futuros className()cambios:

extension NSObject {

static var classNameCleaned : String {

    let className = self.className()
    if className.contains(".") {
        let namesArray = className.components(separatedBy: ".")
        return namesArray.last ?? className
    } else {
        return self.className()
    }

}

}

Uso:

NSViewController.classNameCleaned
MyCustomClass.classNameCleaned
Darkwonder
fuente
2

Swift 3.0 (macOS 10.10 y posterior), puede obtenerlo de className

self.className.components(separatedBy: ".").last!
Thanh Vũ Trần
fuente
1
Por lo que puedo decir, no hay una propiedad incorporada className en las clases Swift. ¿Subclase de algo?
cuña
@shim className se declara en Foundation pero parece que solo está disponible en macOS 10.10 y versiones posteriores. Acabo de editar mi respuesta.
Thanh Vũ Trần
1
Hm, eso es raro. ¿Por qué no lo incluirían en iOS? También tenga en cuenta que es un método de instancia en NSObject developer.apple.com/reference/objectivec/nsobject/…
shim
2

Lo intenté type(of:...)en Playground con Swift 3. Este es mi resultado. Esta es la versión en formato de código.

print(String(describing: type(of: UIButton.self)))
print(String(describing: type(of: UIButton())))
UIButton.Type
UIButton
Lumialxk
fuente
Es una pérdida de tiempo crear una instancia solo para determinar el nombre de la clase.
Sethfri
@sethfri Utilizo esto para determinar el tipo dinámico.
Lumialxk
Todavía no entiendo por qué necesita crear una instancia solo para obtener la información de tipo de la clase
sethfri
2

Swift 5

 NSStringFromClass(CustomClass.self)
Adobels
fuente
2

Este tipo de ejemplo para la clase var. No incluya el nombre del paquete.

extension NSObject {
    class var className: String {
        return "\(self)"
    }
}
Ivan Tkachenko
fuente
1

Si no le gusta el nombre destrozado, puede dictar su propio nombre:

@objc(CalendarViewController) class CalendarViewController : UIViewController {
    // ...
}

Sin embargo, a la larga sería mejor aprender a analizar el nombre destrozado. El formato es estándar y significativo y no cambiará.

mate
fuente
No, no lo es, para mi clase regresa (ExistentialMetatype): confiar en el comportamiento de tiempo de ejecución indocumentado siempre es peligroso, especialmente para la rapidez, ya que todavía está en una etapa relativamente temprana.
superarts.org
1

A veces, las otras soluciones darán un nombre no útil según el objeto que intente mirar. En ese caso, puede obtener el nombre de la clase como una cadena utilizando lo siguiente.

String(cString: object_getClassName(Any!))

Haga clic en la función en xcode para ver algunos métodos relacionados que son bastante útiles. o marque aquí https://developer.apple.com/reference/objectivec/objective_c_functions

Beto
fuente
1

Swift 5 Puede obtener class_name como una cadena con String (que describe: Self.self)

print(String(describing: Self.self))
yasinkbas
fuente
0

Lo uso en Swift 2.2

guard let currentController = UIApplication.topViewController() else { return }
currentController.classForCoder.description().componentsSeparatedByString(".").last!
Alexander Khitev
fuente
0

Swift 5.1: -

También puede usar la función genérica para obtener el nombre de clase del objeto como cadena

struct GenericFunctions {
 static func className<T>(_ name: T) -> String {
        return "\(name)"
    }

}

Llame a esta función usando lo siguiente: -

let name = GenericFunctions.className(ViewController.self)

Happy Coding :)

dinesh sharma
fuente
0

Swift 5.2:

String(describing: type(of: self))
Abdelaziz Elrashed
fuente