web-dev-qa-db-fra.com

Obtenir le nom (chaîne) d'un type générique dans Swift

J'ai une classe générique de type T et j'aimerais obtenir le nom du type qui est passé dans la classe lors de son instanciation. Voici un exemple.

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

Je regarde depuis des heures et je n'arrive pas à trouver un moyen de le faire. Est-ce que quelqu'un a déjà essayé?

Toute aide est grandement appréciée.

Merci

19
Rob

Un moyen purement Swift pour y parvenir n'est pas possible.

Une solution de contournement possible est la suivante:

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

Les limitations reposent sur le fait que cela fonctionne uniquement avec des classes.

S'il s'agit du type générique:

class TestClass {}

NSStringFromClass() renvoie le nom complet (y compris l'espace de noms):

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

C'est pourquoi la fonction cherche la dernière occurrence du caractère ..

Testé comme suit:

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

MISE À JOUR 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
}
8
Antonio

Vous pouvez renvoyer n'importe quel nom de type en utilisant une interpolation de chaîne:

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

Vous pouvez l'essayer sur un terrain de jeu et cela fonctionne comme prévu:

var someClass = MyClass<String>()
someClass.genericName() // Returns "Swift.String"
18
Bradley Hilton

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

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

Dans le type générique, obtenez le nom du type T en convertissant T.self ou type(of: T.self) en String. J'ai trouvé que type(of:) n'était pas nécessaire, mais il vaut la peine de le savoir, car dans d'autres cas, il supprime d'autres détails sur le Type.

L'exemple suivant montre comment obtenir le nom du type générique T au sein d'une structure et d'une classe. Il comprend un code pour obtenir le nom du type contenant.

Exemple incluant class et 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)

Sortie de la console

/*
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'
*/
9
Mobile Dan

Une autre solution possible qui pourrait aider quelqu'un:

Cour de récréation

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()
1
brduca

C'est possible si votre paramètre de type implémente un protocole de nommage commun. 

Dans l'exemple ci-dessous, le protocole Named garantit que le type générique implémente la propriété de classe name

Notez que cela fonctionne à la fois avec les classes et les types de valeur car ces derniers peuvent également être étendus pour se conformer aux protocoles, comme illustré par la variable Int ci-dessous.

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
1
augustzf