web-dev-qa-db-fra.com

Langue rapide NSClassFromString

Comment réaliser réflexion en langue rapide?

Comment puis-je instancier une classe 

[[NSClassFromString(@"Foo") alloc] init];
79
Selvin

Moins de solution de hacky ici: https://stackoverflow.com/a/32265287/308315

Notez que les classes Swift sont des noms d'espacement maintenant, donc au lieu de "MyViewController", ce serait "AppName.MyViewController".


Déconseillé depuis XCode6-beta 6/7

Solution développée en utilisant XCode6-beta 3

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

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

MODIFIER

Vous pouvez aussi le faire en pure 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!

34
Kevin Delord

C’est ainsi que j’ai initié UIViewController par nom de classe

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

Plus d'informations est ici

Sous iOS 9

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

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

@objc(SubClass)
class SubClass: SuperClass {...}
49
klaus

UPDATE: à partir de la version 6, NSStringFromClass renverra le nom de votre bundle et le nom de la classe séparés par un point. Donc, ce sera quelque chose comme MyApp.MyClass

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

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

Donc, le nom de votre 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 a été remplacé par:

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()
25
Edwin Vermeer
10
gabuh

J'ai pu instancier un objet de manière dynamique

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 AnyClass à partir d'une String (sans utiliser Obj-C). Je pense qu'ils ne veulent pas que vous fassiez cela, car cela casse fondamentalement le système de typage.

9
Sulthan

Pour Swift2, j'ai créé une extension très simple pour le faire plus rapidementhttps://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)
}
7
Damien Romito

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

À partir de la version bêta 6, _stdlib_getTypeName obtient 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. _TtSi représente le type Int interne de Swift.

6
Klaas

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)
}
6
Go Let

xcode 7 beta 5:

class MyClass {
    required init() { print("Hi!") }
}
if let classObject = NSClassFromString("YOURAPPNAME.MyClass") as? MyClass.Type {
    let object = classObject.init()
}
5
rosstulloch

chaîne de la classe 

let classString = NSStringFromClass(TestViewController.self)

ou

let classString = NSStringFromClass(TestViewController.classForCoder())

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

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

On dirait que la bonne incantation 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 de AnyClass vers T provoque actuellement un plantage du compilateur. Par conséquent, il est nécessaire d’intervertir l’initialisation de k dans une fermeture distincte qui compile parfaitement.

2
rory

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

protocol Init {
  init()
}

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

Bien sûr, le type provenant de NSClassFromString doit implémenter ce protocole init.

Je suppose que c'est clair, className est une chaîne contenant le nom d'exécution de la classe Obj-C qui, par défaut, n'est PAS "Foo", mais cette discussion n'est pas le sujet principal de votre question.

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

2
Stephan

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

J'utilise différentes cibles, et dans ce cas, la classe Swift n'est pas trouvée. Vous devez remplacer CFBundleName par CFBundleExecutable. J'ai aussi 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);
}
2
Calin Drule

Je pense avoir raison de dire que vous ne pouvez pas, du moins pas avec la version bêta actuelle (2). Espérons que cela changera dans les versions futures.

Vous pouvez utiliser NSClassFromString pour obtenir une variable de type AnyClass, mais il semble n'y avoir aucun moyen de l'instancier dans Swift. Vous pouvez utiliser un bridge vers Objective C et le faire là-bas ou - si cela fonctionne dans votre cas - utilisez une instruction switch .

2
Stephen Darlington

Apparemment, il n'est pas (plus) possible d'instancier un objet dans Swift lorsque le nom de la classe n'est connu qu'à 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 comme un autre objet donné au moment de l'exécution sans encapsuleur Objective-C (avec xCode version 6.2 - 6C107a):

    class Test : NSObject {}
    var test1 = Test()
    var test2 = test1.dynamicType.alloc()
2
seb

J'ai mis en place comme ça,

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

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"
1
MarqueIV

Toujours dans Swift 2.0 (éventuellement avant?) Vous pouvez accéder au type directement avec la propriété dynamicType

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)

Sortie

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

Fonctionne pour mon cas d'utilisation

1
apocolipse

Essaye ça.

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

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

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