NSClassFromString en langage Swift

83

Comment réaliser la réflexion en Swift Language?

Comment puis-je instancier une classe

[[NSClassFromString(@"Foo") alloc] init];
Selvin
la source
1
Essayez comme ceci, si laissez ImplementationClass: NSObject.Type = NSClassFromString (className) as? NSObject.Type {ImplementationClass.init ()}
Pushpa Raja

Réponses:

37

Solution moins piratée ici: https://stackoverflow.com/a/32265287/308315

Notez que les classes Swift sont désormais espacées de noms, donc au lieu de "MyViewController", ce serait "AppName.MyViewController"


Obsolète depuis XCode6-beta 6/7

Solution développée avec XCode6-beta 3

Grâce à la réponse d'Edwin Vermeer, j'ai pu créer quelque chose pour instancier des classes Swift dans une classe Obj-C en faisant ceci:

// swift file
// extend the NSObject class
extension NSObject {
    // create a static method to get a swift class for a string name
    class func swiftClassFromString(className: String) -> AnyClass! {
        // get the project name
        if  var appName: String? = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as String? {
            // generate the full name of your class (take a look into your "YourProject-swift.h" file)
            let classStringName = "_TtC\(appName!.utf16count)\(appName)\(countElements(className))\(className)"
            // return the class!
            return NSClassFromString(classStringName)
        }
        return nil;
    }
}

// obj-c file
#import "YourProject-Swift.h"

- (void)aMethod {
    Class class = NSClassFromString(key);
    if (!class)
        class = [NSObject swiftClassFromString:(key)];
    // do something with the class
}

ÉDITER

Vous pouvez également le faire en pur obj-c:

- (Class)swiftClassFromString:(NSString *)className {
    NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
    NSString *classStringName = [NSString stringWithFormat:@"_TtC%d%@%d%@", appName.length, appName, className.length, className];
    return NSClassFromString(classStringName);
}

J'espère que cela aidera quelqu'un!

Kevin Delord
la source
2
À partir de la version bêta 7, cela ne fonctionnera plus.NSStringFromClass retournera simplement le nom de votre bundle plus le nom de la classe séparés par un point. Vous pouvez donc utiliser un code comme: var appName: String = NSBundle.mainBundle (). ObjectForInfoDictionaryKey ("CFBundleName") comme chaîne? laissez classStringName: String = NSStringFromClass (theObject.dynamicType) renvoyer classStringName.stringByReplacingOccurrencesOfString (appName + ".", withString: "", options: NSStringCompareOptions.CaseInsensitiveSearch, intervalle: néant)
Edwin
@KevinDelord J'ai utilisé votre technique dans mon code. Mais la variable appName ne renvoie pas la valeur correcte lorsque l'application a un espace dans son nom. Toute idée de comment résoudre ce problème ? Ex "App Name" au lieu de "App_Name"
Loadex
Impossible de trouver la fonction countElements. Une aide à ce sujet?
C0D3
Utiliser plutôt ceci pour classStringName: laissez classStringName = "_TtC (appName! .Characters.count) (appName) (className.characters.count) (className)"
C0D3
@Loadex, cela ne fonctionne pas si le nom de votre exécutable contient un espace. Vous devez également remplacer les espaces par des traits de soulignement. Cela a été suggéré dans ce billet de blog (quelque peu déroutant): medium.com/@maximbilan/…. Le code dans le message fonctionnait, mais le message réel parlait d'utiliser le nom de l'application et le nom de la cible, ce qui était différent de ce que leur code faisait.
stuckj
57

Vous devez mettre @objc(SwiftClassName)au-dessus de votre classe rapide.
Comme:

@objc(SubClass)
class SubClass: SuperClass {...}
Klaus
la source
2
NSClassFromString()La fonction a besoin d'un nom spécifié par l' @objcattribution.
eonil le
1
Incroyable, une si petite chose avec un impact si énorme sur mon bien-être mental!
Adrian_H
quelqu'un peut-il montrer comment utiliser NSClassFromString () après avoir fait la chose @objc au-dessus de son nom de classe. Je viens de recevoir AnyClass! retourné
Ryan Bobrowski
Ça marche pour moi. Mais j'ai une question sur pourquoi @objc(SubClass)fonctionne, mais @objc class SubClasspas?
pyanfield
@pyanfield de la documentation Swift: "L'attribut objc accepte en option un seul argument d'attribut, qui consiste en un identifiant. L'identifiant spécifie le nom à exposer à Objective-C pour l'entité à laquelle l'attribut objc s'applique." Donc, la différence est de savoir comment notre sous-classe Swift obtient le nom, elle sera visible pour l'Objectif C. Sous la @objc class SubClassforme, le nom est implicite comme étant le même que le nom de la sous-classe. Et sous la @objc(SubClass) class SubClassforme, il est spécifié directement. Je suppose que le compilateur ne peut tout simplement pas le comprendre par lui-même sous la première forme pour une raison quelconque.
voiger
56

C'est ainsi que j'initialise UIViewController par nom de classe

var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass()

Plus d'informations ici

Dans iOS 9

var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass.init()
Rigel Chen
la source
11
Si le nom de l'application contient "-", il doit être remplacé par "_"
Zitao Xiong
@RigelChen belle solution merci mais si nous avons juste besoin de type (TestViewController) et que nous ne voulons pas l'initialiser à chaque fois, que devons-nous faire?
ArgaPK
@RyanHeitner belle solution merci, mais si nous avons juste besoin de type (TestViewController) et ne voulons pas l'initialiser à chaque fois, que devons-nous faire?
ArgaPK
@argap Créez un singleton et accédez-y: let viewController = aClass.sharedInstance ()
mahboudz
27

MISE À JOUR: À partir de la version bêta 6, NSStringFromClass renverra le nom de votre bundle plus le nom de la classe séparés par un point. Ce sera donc quelque chose comme MyApp.MyClass

Les classes Swift auront un nom interne construit composé des parties suivantes:

  • Il commencera par _TtC,
  • suivi d'un nombre correspondant à la longueur du nom de votre application,
  • suivi du nom de votre application,
  • suivi d'un nombre correspondant à la longueur du nom de votre classe,
  • suivi du nom de votre classe.

Donc, votre nom de classe sera quelque chose comme _TtC5MyApp7MyClass

Vous pouvez obtenir ce nom sous forme de chaîne en exécutant:

var classString = NSStringFromClass(self.dynamicType)

Mise à jour dans Swift 3, ceci est devenu:

var classString = NSStringFromClass(type(of: self))

En utilisant cette chaîne, vous pouvez créer une instance de votre classe Swift en exécutant:

var anyobjectype : AnyObject.Type = NSClassFromString(classString)
var nsobjectype : NSObject.Type = anyobjectype as NSObject.Type
var rec: AnyObject = nsobjectype()
Edwin Vermeer
la source
Cela ne fonctionnera que pour les classes NSObject, en ce qui concerne les classes Swift. Par défaut, ils n'ont aucune méthode d'initialisation et un transtypage en protocole ne semble pas fonctionner, aucune idée?
Stephan
Swift 1.2 / XCode 6.3.1crash dans mon cas avec cette construction
Stephan
J'ai créé une classe de réflexion prenant en charge NSCoding, Printable, Hashable, Equatable et JSON github.com/evermeer/EVReflection
Edwin Vermeer
Le problème mentionné est résolu dans Swift 2.0 ... voir ma réponse car vous devez appeler init .... juste nsobjectype () n'est plus correct.
Stephan
1
let classString = NSStringFromClass (type (de: self))
Abhishek Thapliyal
11

C'est presque pareil

func NSClassFromString(_ aClassName: String!) -> AnyClass!

Consultez ce document:

https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/#//apple_ref/c/func/NSClassFromString

gabuh
la source
5
Cette fonction ne fonctionne qu'avec les NSClassclasses, pas les classes Swift. NSClassFromString("String")revient nil, mais NSClassFromString("NSString")ne le fait pas.
Cezary Wojcik
Je ne suis pas devant l'ordinateur ... mais vous pouvez essayer avec: var myVar:NSClassFromString("myClassName")
gabuh
2
@CezaryWojcik: ps Stringn'est pas une classe; c'est une struct
newacct
2
@newacct D'oh, tu as raison, mon mauvais. Mais NSClassFromStringrevient nilde toute façon pour toutes les classes Swift.
Cezary Wojcik
Si une classe est annotée @objc ou hérite de NSObject, alors elle utilise le système d'objet Objective-C et participe à NSClassFromString, méthode swizzling, etc. Cependant, si ce n'est pas le cas, alors la classe est interne à votre code Swift et ne n'utilisez pas ce même système d'objets. Cela n'a pas été complètement décrit, mais le système d'objet de Swift semble être moins tardif que celui d'Objective-C.
Bill
9

J'ai pu instancier un objet dynamiquement

var clazz: NSObject.Type = TestObject.self
var instance : NSObject = clazz()

if let testObject = instance as? TestObject {
    println("yes!")
}

Je n'ai pas trouvé de moyen de créer à AnyClasspartir d'un String(sans utiliser Obj-C). Je pense qu'ils ne veulent pas que vous fassiez cela parce que cela brise fondamentalement le système de types.

Sulthan
la source
1
Je pense que cette réponse, bien qu'elle ne concerne pas NSClassFromString, est la meilleure ici pour donner une façon centrée sur Swift de faire l'initialisation dynamique des objets. Merci d'avoir partagé!
Matt Long
Merci également à @Sulthan et Matt d'avoir souligné pourquoi cette réponse devrait être au top!
Ilias Karim
7

Pour swift2, j'ai créé une extension très simple pour le faire plus rapidement https://github.com/damienromito/NSObject-FromClassName

extension NSObject {
    class func fromClassName(className : String) -> NSObject {
        let className = NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String + "." + className
        let aClass = NSClassFromString(className) as! UIViewController.Type
        return aClass.init()
    }
}

Dans mon cas, je fais ceci pour charger le ViewController que je veux:

override func viewDidLoad() {
    super.viewDidLoad()
    let controllers = ["SettingsViewController", "ProfileViewController", "PlayerViewController"]
    self.presentController(controllers.firstObject as! String)

}

func presentController(controllerName : String){
    let nav = UINavigationController(rootViewController: NSObject.fromClassName(controllerName) as! UIViewController )
    nav.navigationBar.translucent = false
    self.navigationController?.presentViewController(nav, animated: true, completion: nil)
}
Damien Romito
la source
6

Cela vous donnera le nom de la classe que vous souhaitez instancier. Ensuite, vous pouvez utiliser Edwins answer pour instancier un nouvel objet de votre classe.

À partir de la version bêta 6, _stdlib_getTypeNameobtient le nom de type mutilé d'une variable. Collez ceci dans un terrain de jeu vide:

import Foundation

class PureSwiftClass {
}

var myvar0 = NSString() // Objective-C class
var myvar1 = PureSwiftClass()
var myvar2 = 42
var myvar3 = "Hans"

println( "TypeName0 = \(_stdlib_getTypeName(myvar0))")
println( "TypeName1 = \(_stdlib_getTypeName(myvar1))")
println( "TypeName2 = \(_stdlib_getTypeName(myvar2))")
println( "TypeName3 = \(_stdlib_getTypeName(myvar3))")

La sortie est:

TypeName0 = NSString
TypeName1 = _TtC13__lldb_expr_014PureSwiftClass
TypeName2 = _TtSi
TypeName3 = _TtSS

L'entrée de blog d'Ewan Swick aide à déchiffrer ces chaînes: http://www.eswick.com/2014/06/inside-swift/

par exemple _TtSireprésente le Inttype interne de Swift .

Klaas
la source
"Ensuite, vous pouvez utiliser Edwins answer pour instancier un nouvel objet de votre classe." Je ne pense pas que le ref. travail de réponse pour PureSwiftClass. par exemple, par défaut, cette classe n'a pas de méthode init.
Stephan
6

Dans Swift 2.0 (testé dans le Xcode 7.01) _20150930

let vcName =  "HomeTableViewController"
let ns = NSBundle.mainBundle().infoDictionary!["CFBundleExecutable"] as! String

// Convert string to class
let anyobjecType: AnyObject.Type = NSClassFromString(ns + "." + vcName)!
if anyobjecType is UIViewController.Type {
// vc is instance
    let vc = (anyobjecType as! UIViewController.Type).init()
    print(vc)
}
Allez laisser
la source
5

xcode 7 bêta 5:

class MyClass {
    required init() { print("Hi!") }
}
if let classObject = NSClassFromString("YOURAPPNAME.MyClass") as? MyClass.Type {
    let object = classObject.init()
}
décembre
la source
3

chaîne de classe

let classString = NSStringFromClass(TestViewController.self)

ou

let classString = NSStringFromClass(TestViewController.classForCoder())

init une classe UIViewController à partir de la chaîne:

let vcClass = NSClassFromString(classString) as! UIViewController.Type
let viewController = vcClass.init()
Binglin
la source
3

J'utilise cette catégorie pour Swift 3:

//
//  String+AnyClass.swift
//  Adminer
//
//  Created by Ondrej Rafaj on 14/07/2017.
//  Copyright © 2017 manGoweb UK Ltd. All rights reserved.
//

import Foundation


extension String {

    func convertToClass<T>() -> T.Type? {
        return StringClassConverter<T>.convert(string: self)
    }

}

class StringClassConverter<T> {

    static func convert(string className: String) -> T.Type? {
        guard let nameSpace = Bundle.main.infoDictionary?["CFBundleExecutable"] as? String else {
            return nil
        }
        guard let aClass: T.Type = NSClassFromString("\(nameSpace).\(className)") as? T.Type else {
            return nil
        }
        return aClass

    }

}

L'utilisation serait:

func getViewController(fromString: String) -> UIViewController? {
    guard let viewController: UIViewController.Type = "MyViewController".converToClass() else {
        return nil
    }
    return viewController.init()
}
Ondrej Rafaj
la source
2

Je pense que j'ai raison de dire que vous ne pouvez pas, du moins pas avec la version bêta actuelle (2). Espérons que c'est quelque chose qui changera dans les versions futures.

Vous pouvez utiliser NSClassFromStringpour obtenir une variable de type AnyClassmais il ne semble y avoir aucun moyen dans Swift de l'instancier. Vous pouvez utiliser un pont vers l'Objectif C et le faire là-bas ou - si cela fonctionne dans votre cas - utiliser une instruction switch .

Stephen Darlington
la source
Même avec Swift 1.2 / XCode 6.3.1, cela ne semble pas possible, ou avez-vous trouvé une solution?
Stephan
2

Apparemment, il n'est plus possible (plus) d'instancier un objet dans Swift lorsque le nom de la classe n'est connu qu'au moment de l'exécution. Un wrapper Objective-C est possible pour les sous-classes de NSObject.

Au moins, vous pouvez instancier un objet de la même classe qu'un autre objet donné lors de l'exécution sans wrapper Objective-C (en utilisant xCode Version 6.2 - 6C107a):

    class Test : NSObject {}
    var test1 = Test()
    var test2 = test1.dynamicType.alloc()
Seb
la source
Cela ne crée pas d'instance à partir du nom de la classe.
Stephan
Ce n'est pas clair d'après votre réponse, cela ne semble pas fonctionner sur des classes Swift pures, n'est-ce pas?
Stephan
2

Dans Swift 2.0 (testé dans la version bêta2 de Xcode 7), cela fonctionne comme ceci:

protocol Init {
  init()
}

var type = NSClassFromString(className) as? Init.Type
let obj = type!.init()

Bien sûr, le type provenant de NSClassFromStringdoit implémenter ce protocole d'initialisation.

Je m'attends à ce que ce soit clair, classNameest une chaîne contenant le nom d'exécution Obj-C de la classe qui n'est par défaut PAS juste "Foo", mais cette discussion n'est pas à mon humble avis le sujet principal de votre question.

Vous avez besoin de ce protocole car par défaut, toutes les classes Swift n'implémentent pas de initméthode.

Stéphan
la source
Si vous avez des cours pur Swift, consultez mes autres questions: stackoverflow.com/questions/30111659
Stephan
2

On dirait que l'incantation correcte serait ...

func newForName<T:NSObject>(p:String) -> T? {
   var result:T? = nil

   if let k:AnyClass = NSClassFromString(p) {
      result = (k as! T).dynamicType.init()
   }

   return result
}

... où "p" signifie "emballé" - un problème distinct.

Mais la conversion critique d'AnyClass vers T provoque actuellement un plantage du compilateur, donc en attendant, il faut casser l'initialisation de k dans une fermeture séparée, ce qui se compile correctement.

rory
la source
2

J'utilise des cibles différentes, et dans ce cas, la classe swift n'est pas trouvée. Vous devez remplacer CFBundleName par CFBundleExecutable. J'ai également corrigé les avertissements:

- (Class)swiftClassFromString:(NSString *)className {
    NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleExecutable"];
    NSString *classStringName = [NSString stringWithFormat:@"_TtC%lu%@%lu%@", (unsigned long)appName.length, appName, (unsigned long)className.length, className];
    return NSClassFromString(classStringName);
}
Calin Drule
la source
2

La solution n'est-elle pas aussi simple que cela?

// Given the app/framework/module named 'MyApp'
let className = String(reflecting: MyClass.self)

// className = "MyApp.MyClass"
Mark A. Donohoe
la source
3
la question originale voulait la classe de la chaîne, pas la chaîne de la classe.
Ryan
1

Aussi dans Swift 2.0 (peut-être avant?) Vous pouvez accéder au type directement avec la dynamicTypepropriété

c'est à dire

class User {
    required init() { // class must have an explicit required init()
    }
    var name: String = ""
}
let aUser = User()
aUser.name = "Tom"
print(aUser)
let bUser = aUser.dynamicType.init()
print(bUser)

Production

aUser: User = {
  name = "Tom"
}
bUser: User = {
  name = ""
}

Fonctionne pour mon cas d'utilisation

apocolipse
la source
1

Essaye ça.

let className: String = String(ControllerName.classForCoder())
print(className)
Himanshu
la source
1

J'ai implémenté comme ça,

if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
   ImplementationClass.init()
}
Pushpa Raja
la source
1

Swift 5 , facile à utiliser, grâce à @Ondrej Rafaj's

  • Code source:

    extension String {
         fileprivate
         func convertToClass<T>() -> T.Type? {
              return StringClassConverter<T>.convert(string: self)
         }
    
    
        var controller: UIViewController?{
              guard let viewController: UIViewController.Type = convertToClass() else {
                return nil
            }
            return viewController.init()
        }
    }
    
    class StringClassConverter<T> {
         fileprivate
         static func convert(string className: String) -> T.Type? {
             guard let nameSpace = Bundle.main.infoDictionary?["CFBundleExecutable"] as? String, let aClass = NSClassFromString("\(nameSpace).\(className)") as? T.Type else {
                 return nil
            }
             return aClass
    
       }
    
    }
    
  • Appelez comme ceci:

    guard let ctrl = "ViewCtrl".controller else {
        return
    }
    //  ctrl do sth
    
perle noire
la source
0

Un exemple de saut de page montré ici, l'espoir peut vous aider!

let vc:UIViewController = (NSClassFromString("SwiftAutoCellHeight."+type) as! UIViewController.Type).init()
self.navigationController?.pushViewController(vc, animated: true)

// Click the Table response
tableView.deselectRow(at: indexPath, animated: true)
let sectionModel = models[(indexPath as NSIndexPath).section]
var className = sectionModel.rowsTargetControlerNames[(indexPath as NSIndexPath).row]
className = "GTMRefreshDemo.\(className)"
if let cls = NSClassFromString(className) as? UIViewController.Type {
   let dvc = cls.init()
   self.navigationController?.pushViewController(dvc, animated: true)
}
Tim
la source
0

Swift3 +

extension String {

    var `class`: AnyClass? {

        guard
            let dict = Bundle.main.infoDictionary,
            var appName = dict["CFBundleName"] as? String
            else { return nil }

        appName.replacingOccurrences(of: " ", with: "_")
        let className = appName + "." + self
        return NSClassFromString(className)
    }
}
SeRG1k
la source
Merci pour cet extrait de code, qui pourrait fournir une aide limitée et immédiate. Une explication appropriée améliorerait considérablement sa valeur à long terme en montrant pourquoi c'est une bonne solution au problème, et la rendrait plus utile aux futurs lecteurs avec d'autres questions similaires. Veuillez modifier votre réponse pour ajouter des explications, y compris les hypothèses que vous avez formulées.
iBug
-6

Voici un bon exemple:

class EPRocks { 
    @require init() { } 
}

class EPAwesome : EPRocks { 
    func awesome() -> String { return "Yes"; } 
}

var epawesome = EPAwesome.self(); 
print(epawesome.awesome);
MalPingouin
la source
1
Cela ne fait pas ce que demande la question.
ThomasW