Comment connaître le type d'un objet (dans Swift)?

255

Lorsque vous essayez de comprendre un programme, ou dans certains cas, il est utile de pouvoir réellement savoir de quel type est un élément. Je sais que le débogueur peut vous montrer des informations de type, et vous pouvez généralement compter sur l'inférence de type pour éviter de spécifier le type dans ces situations, mais j'aimerais vraiment avoir quelque chose comme Pythontype()

dynamicType (voir cette question )

Mise à jour: cela a été changé dans une version récente de Swift, obj.dynamicTypevous donne maintenant une référence au type et non à l'instance du type dynamique.

Celui-ci semble le plus prometteur, mais jusqu'à présent, je n'ai pas pu trouver le type réel

class MyClass {
    var count = 0
}

let mc = MyClass()

# update: this now evaluates as true
mc.dynamicType === MyClass.self

J'ai également essayé d'utiliser une référence de classe pour instancier un nouvel objet, ce qui fonctionne, mais m'a étrangement donné une erreur disant que je dois ajouter un requiredinitialiseur:

travaux:

class MyClass {
    var count = 0
    required init() {
    }
}

let myClass2 = MyClass.self
let mc2 = MyClass2()

Mais ce n'est qu'un petit pas vers la découverte du type d'un objet donné

modifier : j'ai supprimé un nombre important de détails désormais non pertinents - consultez l'historique des modifications si vous êtes intéressé :)

Jiaaro
la source
1
Fait intéressant, print(mc)ou dump(mc)imprimera un résumé (que vous pouvez obtenir à partir de toString(mc)ou reflect(mc).summary), qui contiendra le nom de la classe quelque part. Mais il n'est pas clair comment obtenir vous-même le nom de la classe.
newacct
@David est similaire, mais toutes les variables ne sont pas des instances de classe. De plus, cette question était vraiment de vérifier si le type correspond à ce que le programmeur recherche, alors que j'espère simplement trouver le type en gros
Jiaaro

Réponses:

285

Version Swift 3:

type(of: yourObject)
Jérémy Lapointe
la source
8
Fait amusant. Cela ne fonctionne pas avec les options implicitement déballées! ie var myVar: SomeType!. Le compilateur renvoie l'erreur «Impossible de convertir la valeur de type« SomeType! .Type »(alias« ImplicitlyUnwrappedOptional <SomeType> .Type ») en type d'argument attendu« AnyClass »(alias« AnyObject.Type ») Le compilateur suggère d'ajouter as! AnyClassaprès le type mais ensuite programme se bloque avec certains "EXC_BAD_INSTRUCTION" et autres jiberrish que je ne peux pas déchiffrer.
LightningStryk
1
En effet, cela devrait être la réponse acceptée maintenant que Swift 3 existe. Merci Jeremy!
biomiker
1
Si vous recherchez le nom de type spécifique, lorsque le type est de type protocole, cela peut ne pas fonctionner pour vous.
Chris Prince
2
Si vous avez un Stringqui est passé en tant que type , Anypuis la type(of:)sortie de volonté Any, non String.
ScottyBlades
1
@ScottyBlades alors quelle sera la solution. Peux-tu fournir?
Mubin Mall
109

Swift 2.0:

La bonne façon de faire ce type d'introspection de type serait avec la structure Mirror ,

    let stringObject:String = "testing"
    let stringArrayObject:[String] = ["one", "two"]
    let viewObject = UIView()
    let anyObject:Any = "testing"

    let stringMirror = Mirror(reflecting: stringObject)
    let stringArrayMirror = Mirror(reflecting: stringArrayObject)
    let viewMirror = Mirror(reflecting: viewObject)
    let anyMirror = Mirror(reflecting: anyObject)

Ensuite, pour accéder au type lui-même à partir de la Mirrorstructure, vous utiliseriez la propriété subjectTypecomme suit:

    // Prints "String"
    print(stringMirror.subjectType)

    // Prints "Array<String>"
    print(stringArrayMirror.subjectType)

    // Prints "UIView"
    print(viewMirror.subjectType)

    // Prints "String"
    print(anyMirror.subjectType)

Vous pouvez ensuite utiliser quelque chose comme ceci:

    if anyMirror.subjectType == String.self {
        print("anyObject is a string!")
    } else {
        print("anyObject is not a string!")
    }
Gudbergur
la source
7
C'est bien. Sachez que si l'objet mis en miroir est de type facultatif, sa comparaison avec un type non facultatif échouera. Stringet Optional(String)ne sont pas les mêmes.
Thomas Verbeek
1
Exactement ce que je cherchais, je voulais savoir quel est le type de l'objet
Joseph
Existe-t-il un type de comparaison dans ce contexte qui n'échouera pas lors de la comparaison de types facultatifs et non facultatifs?
Chris Prince
Voilà ce que je cherchais. Merci @Gudbergur.
Mubin Mall
60

Le dynamicType.printClassNamecode provient d'un exemple du livre Swift. Je ne connais aucun moyen de récupérer directement un nom de classe personnalisé, mais vous pouvez vérifier un type d'instance en utilisant le ismot - clé comme indiqué ci-dessous. Cet exemple montre également comment implémenter une fonction className personnalisée, si vous voulez vraiment que le nom de classe soit une chaîne.

class Shape {
    class func className() -> String {
        return "Shape"
    }
}

class Square: Shape {
    override class func className() -> String {
        return "Square"
    }
}

class Circle: Shape {
    override class func className() -> String {
        return "Circle"
    }
}

func getShape() -> Shape {
    return Square() // hardcoded for example
}

let newShape: Shape = getShape()
newShape is Square // true
newShape is Circle // false
newShape.dynamicType.className() // "Square"
newShape.dynamicType.className() == Square.className() // true

Remarque:
ces sous-classes NSObjectimplémentent déjà leur propre fonction className. Si vous travaillez avec Cocoa, vous pouvez simplement utiliser cette propriété.

class MyObj: NSObject {
    init() {
        super.init()
        println("My class is \(self.className)")
    }
}
MyObj()
Tiret
la source
2
Hé, je ne sais pas quand cela a changé, mais comme l'a souligné Alex Pretzlav, le comportement a changé.
Jiaaro
1
Oui. À partir de Swift 3.0, subjectTypen'est plus disponible et dynamicTypeprovoque un message de dépréciation du compilateur.
Raphael
41

Depuis Xcode 6.0.1 (au moins, je ne sais pas quand ils l'ont ajouté), votre exemple d'origine fonctionne maintenant:

class MyClass {
    var count = 0
}

let mc = MyClass()
mc.dynamicType === MyClass.self // returns `true`

Mettre à jour:

Pour répondre à la question d'origine, vous pouvez réellement utiliser le runtime Objective-C avec des objets Swift simples avec succès.

Essayez ce qui suit:

import Foundation
class MyClass { }
class SubClass: MyClass { }

let mc = MyClass()
let m2 = SubClass()

// Both of these return .Some("__lldb_expr_35.SubClass"), which is the fully mangled class name from the playground
String.fromCString(class_getName(m2.dynamicType))
String.fromCString(object_getClassName(m2))
// Returns .Some("__lldb_expr_42.MyClass")
String.fromCString(object_getClassName(mc))
Alex Pretzlav
la source
On dirait qu'ils l'ont changé pour vous donner le type au lieu d'une instance.
Jiaaro
@Jiaaro, j'ai mis à jour ma réponse avec ce que je pense que vous cherchiez dans votre question d'origine
Alex Pretzlav
36

Si vous avez simplement besoin de vérifier si la variable est de type X ou si elle est conforme à un protocole, vous pouvez utiliser is, ou as?comme dans ce qui suit:

var unknownTypeVariable =if unknownTypeVariable is <ClassName> {
    //the variable is of type <ClassName>
} else {
    //variable is not of type <ClassName>
}

C'est l'équivalent d' isKindOfClassObj-C.

Et cela équivaut à conformsToProtocol, ouisMemberOfClass

var unknownTypeVariable =if let myClass = unknownTypeVariable as? <ClassName or ProtocolName> {
    //unknownTypeVarible is of type <ClassName or ProtocolName>
} else {
    //unknownTypeVariable is not of type <ClassName or ProtocolName>
}
Valerii Lider
la source
La deuxième partie de votre réponse est fausse. L'instruction «if let» avec as?conversion conditionnelle fait de même isKindOfClasségalement, fournit également le résultat de la conversion si elle réussit.
awolf
L'équivalent de isMemberOfClassest la condition object.dynamicType == ClassName.self.
awolf
18

Swift 3:

if unknownType is MyClass {
   //unknownType is of class type MyClass
}
Peter
la source
Je pense qu'il isexiste avant Swift 3 ...?
Nicolas Miari
10

Pour Swift 3.0

String(describing: <Class-Name>.self)

Pour Swift 2.0 - 2.3

String(<Class-Name>)
techloverr
la source
1
La chose importante à propos de cette réponse étant correcte pour moi, c'est que la chaîne résultante correspond exactement au nom de la classe - je peux donc l'utiliser pour obtenir un nom d'entité Core Data à partir d'une sous-classe NSManagedObject. J'ai utilisé la version Swift3.
Kendall Helmstetter Gelner
8

Voici 2 façons dont je recommande de le faire:

if let thisShape = aShape as? Square 

Ou:

aShape.isKindOfClass(Square)

Voici un exemple détaillé:

class Shape { }
class Square: Shape { } 
class Circle: Shape { }

var aShape = Shape()
aShape = Square()

if let thisShape = aShape as? Square {
    println("Its a square")
} else {
    println("Its not a square")
}

if aShape.isKindOfClass(Square) {
    println("Its a square")
} else {
    println("Its not a square")
}
Esqarrouth
la source
2
print( aShape is Square ), l' isopérateur est plus préférable.
DawnSong
Bonne solution pour moi pour obtenir le type d'objets.
nihasmata
1

Dépend du cas d'utilisation. Mais supposons que vous vouliez faire quelque chose d'utile avec vos types "variables". L' switchinstruction Swift est très puissante et peut vous aider à obtenir les résultats que vous recherchez ...

    let dd2 = ["x" : 9, "y" : "home9"]
    let dds = dd2.filter {
        let eIndex = "x"
        let eValue:Any = 9
        var r = false

        switch eValue {
        case let testString as String:
            r = $1 == testString
        case let testUInt as UInt:
            r = $1 == testUInt
        case let testInt as Int:
            r = $1 == testInt
        default:
            r = false
        }

        return r && $0 == eIndex
    }

Dans ce cas, ayez un dictionnaire simple qui contient des paires clé / valeur qui peuvent être UInt, Int ou String. Dans la .filter()méthode du dictionnaire, je dois m'assurer de tester correctement les valeurs et de ne tester une chaîne que lorsqu'elle est une chaîne, etc. L'instruction switch rend cela simple et sûr! En affectant 9 à la variable de type Any, il fait exécuter le commutateur Int. Essayez de le changer en:

   let eValue:Any = "home9"

..et réessayez. Cette fois, il exécute l' as Stringaffaire.

CPD
la source
1

Si vous obtenez un avertissement "toujours vrai / échoue", vous devrez peut-être effectuer un cast vers Any avant d'utiliser is

(foo as Any) is SomeClass
josef
la source
0
//: Playground - noun: a place where people can play

import UIKit

class A {
    class func a() {
        print("yeah")
    }

    func getInnerValue() {
        self.dynamicType.a()
    }
}

class B: A {
    override class func a() {
        print("yeah yeah")
    }
}

B.a() // yeah yeah
A.a() // yeah
B().getInnerValue() // yeah yeah
A().getInnerValue() // yeah
Avi Cohen
la source