Obtenir le nom de classe de l'objet sous forme de chaîne dans Swift

320

Obtenir le nom de classe d'un objet en Stringutilisant:

object_getClassName(myViewController)

renvoie quelque chose comme ceci:

_TtC5AppName22CalendarViewController

Je cherche la pureté version: "CalendarViewController". Comment puis-je obtenir une chaîne nettoyée du nom de classe à la place?

J'ai trouvé quelques tentatives de questions à ce sujet mais pas une réponse réelle. N'est-ce pas possible du tout?

Bernd
la source
alternativement… à quoi ressemblerait une fonction d'analyseur efficace pour cela?
Bernd
Si vous souhaitez vérifier si un objet appartient à une certaine classe, consultez cette réponse .
signification est importante

Réponses:

530

Chaîne d'une instance :

String(describing: YourType.self)

Chaîne d'un type :

String(describing: self)

Exemple:

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"

Testé avec class, structet enum.

Rudolf Adamkovič
la source
4
Est-ce vraiment le cas? Selon les documents pour String pour cet initialiseur "un résultat non spécifié est fourni automatiquement par la bibliothèque standard Swift" lorsqu'il n'est pas conforme à Streamable, CustomStringConvertible ou CustomDebugStringConvertible. Ainsi, alors qu'il peut afficher la valeur souhaitée maintenant, continuera-t-il de le faire à l'avenir?
Tod Cunningham
3
L'utiliser dans Xcode 7.3.1 et Swift 2.2 produit ce qui suit, ce qui n'est pas idéal. '' let name = String (self) name String "<MyAppName.MyClassName: 0x ########>" ''
CodeBender
13
Dans Swift 3, la bonne réponse est d'appeler String(describing: MyViewController.self)comme indiqué dans quelques réponses ci-dessous.
AmitaiB
10
Mais il renvoie "MyViewController.TYPE"!
orkenstein
3
@ RudolfAdamkovič Oui, ce serait vrai. Mais je ne aime pas écrire le nom de la classe explicitement (si je renomme la classe Foopour Foo2mais créer une classe Fooailleurs, qui pourrait freiner le code). En cas de sous-classement, vous devez utiliser soit type(of:)ou Type.selfselon ce qui correspond à vos besoins. type(of:)Est probablement plus approprié pour obtenir le nom de la classe.
Marián Černý
182

MISE À JOUR DE SWIFT 5

Nous pouvons obtenir de jolies descriptions des noms de types en utilisant la variable d'instance via l' Stringinitialiseur et créer de nouveaux objets d'une certaine classe

Comme, par exemple print(String(describing: type(of: object))). Où objectpeut être une variable d'instance comme un tableau, un dictionnaire, un Int, un NSDate, etc.

Étant donné qu'il NSObjects'agit de la classe racine de la plupart des hiérarchies de classes Objective-C, vous pouvez essayer de créer une extension pour NSObjectobtenir le nom de classe de chaque sous-classe de NSObject. Comme ça:

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

Ou vous pouvez créer une fonction statique dont le paramètre est de type Any(le protocole auquel tous les types se conforment implicitement) et renvoie le nom de classe en tant que chaîne. Comme ça:

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

Vous pouvez maintenant faire quelque chose comme ceci:

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

    }
}

De plus, une fois que nous pouvons obtenir le nom de la classe en tant que chaîne, nous pouvons instancier de nouveaux objets de cette classe :

// 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)

Peut-être que cela aide quelqu'un là-bas !.

mauricioconde
la source
2
Ce devrait être la réponse acceptée. Merci, je cherchais un moyen d'imprimer le nom de la classe sans avoir à l'instancier, et j'ai trouvé la réponse ici. Merci impression (String (BaseAsyncTask))
Bruno Coelho
4
classNameAsStringpeut être simplifié className, car ce "nom" impliquait son type. theClassNameest similaire à l'histoire de Facebook, qui s'appelle à l'origine thefacebook.
DawnSong
"diccionary" ... la version italienne de la collection de dictionnaires de Swift:]
Andrew Kirna
Cette solution préfixe le nom du projet pour chaque classTag, c.-à-d ProjectTest.MyViewController. Comment puis-je me débarrasser de ce nom de projet? Je veux seulement un nom de classe.
Sazzad Hissain Khan
132

Swift 5

Voici l'extension pour obtenir le en typeNametant que variable (travailler avec le type de valeur ou le type de référence).

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)
    }
}

Comment utiliser:

// 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
la source
12
Une idée pourquoi j'en tire MyAppName.MyClassName? Je ne m'intéresse qu'à MyClassName
Andrew
@Andrew pourriez-vous donner plus de détails dans votre cas? J'utilise cette extension dans de nombreux projets et tous répondent comme prévu.
nahung89
1
Eh bien, si je l'appelle à partir de la classe elle-même, elle renvoie MyClassName, mais si j'enveloppe cela dans une méthode qui renvoie la chaîne de nom de classe et l'appelle à partir d'une autre classe, elle renvoie MyAppName.MyClassName. Lors de la recherche, j'ai vu que toutes les classes étaient implicitement espacées, donc si vous l'appeliez en dehors de votre classe, vous obtenez MyAppName.MyClassName au lieu de MyClassName. Ensuite, vous devez vous fier à la manipulation des chaînes pour obtenir le nom de la classe.
Andrew
1
extension très utile
Hattori Hanzō
@Andrew, pas sûr du fonctionnement actuel, mais je me souviens, String (décrivant: type (of: self)) utilisé pour renvoyer ModuleName.ClassName à un moment donné. J'avais divisé en fonction de. et récupéré le dernier objet.
BangOperator
31

Swift 3.0

String(describing: MyViewController.self)

svelte
la source
2
En fait, type(of: )c'est une surpuissance là-bas, String(describing: MyViewController.self)suffira
Alexander Vasenin
2
Cela crée une chaîne telle que <App_Name.ClassName: 0x79e14450> pour moi, ce qui n'est pas idéal
Doug Amos
N'est-ce pas String.init(describing: MyViewController.self)?
Iulian Onofrei
2
@IulianOnofrei, vous pouvez le faire, mais ce initn'est pas nécessaire.
shim
Débarrassez-vous totalement du préfixe AppName! Merci
yuchen
28

Je propose une telle approche ( très 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)"
}

Il n'utilise ni introspection ni démêlage manuel ( pas de magie! ).


Voici une démo:

// 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
plongeur
la source
Ils l'ont changé dans Swift 3.0. Maintenant, vous pouvez obtenir une chaîne detype(of: self)
Asad Ali
Mise à jour vers Swift 3.
werediver
Je pense que c'est la meilleure façon d'obtenir le nom du type: n'importe quelle instance de classe, basée sur NSObject, enum, typealiase, fonctions, propriétés var et tous types, même constants. Pour les types, vous devez utiliser .self. Par exemple, tapezName (Int.self), typeName (true.self). Vous n'avez pas non plus à vous soucier du nom du projet en tant que partie (comme AppProj. [Typename]) Génial!
David.Chu.ca
23

Si vous avez du type Foo, le code suivant vous donnera "Foo"dans Swift 3 et Swift 4:

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

Le problème avec la plupart des réponses ici est qu'elles vous donnent "Foo.Type" la chaîne résultante lorsque vous n'avez aucune instance du type, lorsque ce que vous voulez vraiment est juste "Foo". Ce qui suit vous donne "Foo.Type", comme mentionné dans un tas d'autres réponses.

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

La type(of:)partie est inutile si vous le souhaitez "Foo".

sethfri
la source
21

Dans Swift 4.1 et maintenant 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 vous n'avez pas d'instance autour:

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 vous avez une instance autour:

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
la source
14

Pour obtenir le nom d'une classe Swift à partir d'un objet, par exemple pour un objet var: SomeClass (), utilisez

String(describing: type(of: object))

Pour obtenir le nom d'une classe Swift à partir d'un type de classe, par exemple SomeClass, utilisez:

String(describing: SomeClass.self)

Production:

"SomeClass"

Gurjit Singh
la source
13

Vous pouvez essayer de cette façon:

self.classForCoder.description()
陈方涛
la source
Cela fonctionne très bien car il inclut également "l'espace de noms" complet (le préfixe d'appartenance cible approprié).
BonanzaDriver
BTW, j'ai également trouvé en utilisant "String (self)" où self est une instance de MyViewController fonctionne aussi ... il fournit les mêmes informations ... Xcode 7.1.
BonanzaDriver
12

Vous pouvez utiliser la fonction de bibliothèque standard Swift appelée _stdlib_getDemangledTypeNamecomme ceci:

let name = _stdlib_getDemangledTypeName(myViewController)
Jernej Strasner
la source
@NeilJaff que voulez-vous dire par "juste une chaîne optionnelle"? Il renvoie une chaîne et ce n'est pas facultatif.
Jernej Strasner
3
Cette approche ne renvoie pas les noms des classes dans les API privées FYI. Il renverra le nom du premier nom de classe de base publique.
Tylerc230
Je reçois le message: Utilisation de l'identifiant non résolu '_stdlib_getDemangledTypeName'
Adobels
12

Pour obtenir le nom du type sous forme de chaîne dans Swift 4 (je n'ai pas vérifié les versions antérieures), utilisez simplement l'interpolation de chaîne:

"\(type(of: myViewController))"

Vous pouvez utiliser .selfsur un type lui-même et la type(of:_)fonction sur une instance:

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

On peut également utiliser des miroirs:

let vc = UIViewController()

String(Mirror(reflecting: vc).subjectType)

NB: Cette méthode peut également être utilisée pour les structures et les énumérations. Il y a un displayStyle qui donne une indication de quel type de structure:

Mirror(reflecting: vc).displayStyle

Le retour est une énumération pour que vous puissiez:

Mirror(reflecting: vc).displayStyle == .Class
Paul Ardeleanu
la source
Merci. C'est ce qui a fonctionné pour moi. La seule chose (au moins sur iOS 9) est que le subjectType se terminait par ".Type", j'ai donc dû supprimer cette partie de la chaîne.
Nikolay Suvandzhiev
8

Swift 5.1

Vous pouvez cependant obtenir les noms de classe, de structure, d'énumération, de protocole et de NSObject Self.self.

print("\(Self.self)")
Victor Kushnerov
la source
Notez que cela peut ajouter le nom du module (différemment de String(describing: type(of: self)))
Ixx
7

Swift 3.0: Vous pouvez créer une extension comme celle-ci. Elle donne le nom de la classe sans le nom du projet

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

    public class var className: String {
        return NSStringFromClass(self).components(separatedBy: ".").last ?? ""
    }
}
Exception
la source
Lorsque j'essaie d'utiliser cela, j'obtiens l'erreur: Impossible de convertir la valeur de type «ProjectName.MyViewController» (0x1020409e8) en «Swift.AnyObject.Type» (0x7fb96fc0dec0).
tylerSF
6

Vous pouvez étendre NSObjectProtocoldans Swift 4 comme ceci:

import Foundation

extension NSObjectProtocol {

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

Cela rendra la variable calculée classNamedisponible pour TOUTES les classes. L' utilisation de ce à l' intérieur d' un print()dans CalendarViewControllerimprimera "CalendarViewController"dans la console.

Siddharth Bhatt
la source
4

Je cherche cette réponse de temps en temps. J'utilise GKStateMachine et j'aime observer les changements d'état et je voulais un moyen facile de voir juste le nom de la classe. Je ne sais pas si c'est juste iOS 10 ou Swift 2.3, mais dans cet environnement, ce qui suit fait exactement ce que je veux:

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

// Output:    
// Class Name: GKState
Troy
la source
4

Vous pouvez obtenir le nom de la classe en faisant quelque chose comme:

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

Pour obtenir le nom de classe en tant que chaîne, déclarez votre classe comme suit

@objc(YourClassName) class YourClassName{}

Et obtenez le nom de la classe en utilisant la syntaxe suivante

NSStringFromClass(YourClassName)
Moin Uddin
la source
3

Essayez reflect().summaryClass auto ou instance dynamicType. Déballez les options avant d'obtenir dynamicType, sinon le dynamicType est l'encapsuleur facultatif.

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;

Le résumé est un chemin de classe avec des types génériques décrits. Pour obtenir un nom de classe simple à partir du résumé, essayez cette méthode.

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;
}
dessiné
la source
3

Les solutions ci-dessus n'ont pas fonctionné pour moi. Le produit principalement les problèmes mentionnés dans plusieurs commentaires:

MyAppName.ClassName

ou

MyFrameWorkName.ClassName

Ces solutions ont fonctionné sur XCode 9, Swift 3.0:

Je l'ai nommé classNameCleanedpour qu'il soit plus facile d'accès et n'entre pas en conflit avec les className()changements futurs :

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()
    }

}

}

Usage:

NSViewController.classNameCleaned
MyCustomClass.classNameCleaned
Darkwonder
la source
2

Swift 3.0 (macOS 10.10 et versions ultérieures), vous pouvez l'obtenir à partir de className

self.className.components(separatedBy: ".").last!
Thanh Vũ Trần
la source
1
Pour autant que je sache, il n'y a pas de propriété className intégrée dans les classes Swift. Avez-vous sous-classe quelque chose?
shim
@shim className est déclaré dans Foundation, mais il semble qu'il ne soit disponible que sur macOS 10.10 et versions ultérieures. Je viens de modifier ma réponse.
Thanh Vũ Trần
1
Hm, c'est bizarre. Pourquoi ne l'incluraient-ils pas dans iOS? Notez également qu'il s'agit d'une méthode d'instance sur NSObject developer.apple.com/reference/objectivec/nsobject/…
shim
2

J'ai essayé type(of:...)dans Playground avec Swift 3. C'est mon résultat. Ceci est la version au format de code.

print(String(describing: type(of: UIButton.self)))
print(String(describing: type(of: UIButton())))
UIButton.Type
UIButton
Lumialxk
la source
Il est inutile d'instancier une instance juste pour déterminer le nom de la classe.
sethfri
@sethfri J'utilise ceci pour déterminer le type dynamique.
Lumialxk
Je ne comprends toujours pas pourquoi vous devez créer une instance juste pour obtenir les informations de type de la classe
sethfri
2

Swift 5

 NSStringFromClass(CustomClass.self)
Adobels
la source
2

Ce genre d'exemple pour la classe var. N'incluez pas le nom de l'ensemble.

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

Si vous n'aimez pas le nom mutilé, vous pouvez dicter votre propre nom:

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

Cependant, il serait préférable à long terme d'apprendre à analyser le nom mutilé. Le format est standard et significatif et ne changera pas.

mat
la source
Non, ce n'est pas le cas, pour ma classe, il renvoie (ExistentialMetatype) - compter sur un comportement d'exécution non documenté est toujours dangereux, en particulier pour swift car il est encore à un stade relativement précoce.
superarts.org
1

Parfois, les autres solutions donneront un nom non utile en fonction de l'objet que vous essayez de regarder. Dans ce cas, vous pouvez obtenir le nom de classe sous forme de chaîne en utilisant ce qui suit.

String(cString: object_getClassName(Any!))

⌘ cliquez sur la fonction dans xcode pour voir quelques méthodes connexes qui sont assez utiles. ou consultez ici https://developer.apple.com/reference/objectivec/objective_c_functions

bob
la source
1

Swift 5 Vous pouvez obtenir nom_classe sous forme de chaîne avec String (décrivant: Self.self)

print(String(describing: Self.self))
yasinkbas
la source
1

Swift 5.2:

String(describing: type(of: self))
Abdelaziz Elrashed
la source
0

Je l'utilise dans Swift 2.2

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

Swift 5.1: -

Vous pouvez également utiliser une fonction générique pour obtenir le nom de classe de l'objet sous forme de chaîne

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

}

Appelez cette fonction en utilisant ce qui suit: -

let name = GenericFunctions.className(ViewController.self)

Happy Coding :)

dinesh sharma
la source