Swift language NSClassFromString

¿Cómo lograr la reflexión en Swift Language?

¿Cómo puedo instanciar una clase?

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

Solución menos hacky aquí: https://stackoverflow.com/a/32265287/308315

Tenga en cuenta que las clases Swift están ahora espaciadas, por lo que en lugar de “MyViewController” sería “AppName.MyViewController”.


Desaprobado desde XCode6-beta 6/7

Solución desarrollada usando XCode6-beta 3

Gracias a la respuesta de Edwin Vermeer, pude crear algo para crear instancias de clases Swift en una clase Obj-C haciendo esto:

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

EDITAR

También puedes hacerlo en obj-c puro:

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

¡Espero que esto ayude a alguien!

Esta es la forma en que inicié UIViewController por nombre de clase

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

Más información está aquí

En iOS 9

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

Debes poner @objc(SwiftClassName) arriba de tu clase rápida.
Me gusta:

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

ACTUALIZACIÓN: Comenzando con beta 6 NSStringFromClass devolverá el nombre de su paquete más el nombre de clase separado por un punto. Entonces será algo así como MyApp.MyClass

Las clases de Swift tendrán un nombre interno construido que consiste en la acumulación de las siguientes partes:

  • Comenzará con _TtC,
  • seguido por un número que es la longitud del nombre de su aplicación,
  • seguido de su nombre de aplicación,
  • seguido por un número que es la longitud de su nombre de clase,
  • seguido de tu nombre de clase.

Entonces, tu nombre de clase será algo así como _TtC5MyApp7MyClass

Puede obtener este nombre como una cadena ejecutando:

 var classString = NSStringFromClass(self.dynamicType) 

Actualización En Swift 3 esto ha cambiado a:

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

Usando esa cadena, puedes crear una instancia de tu clase Swift ejecutando:

 var anyobjectype : AnyObject.Type = NSClassFromString(classString) var nsobjectype : NSObject.Type = anyobjectype as NSObject.Type var rec: AnyObject = nsobjectype() 

Pude crear una instancia de un objeto dinámicamente

 var clazz: NSObject.Type = TestObject.self var instance : NSObject = clazz() if let testObject = instance as? TestObject { println("yes!") } 

No he encontrado una manera de crear AnyClass partir de una String (sin usar Obj-C). Creo que no quieren que hagas eso porque básicamente rompe el sistema de tipos.

Para swift2, creé una extensión muy simple para hacer esto más rápidamente 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() } } 

En mi caso, hago esto para cargar ViewController que quiero:

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

Esto te dará el nombre de la clase a la que quieres crear una instancia. Luego puede usar la respuesta de Edwin para instanciar un nuevo objeto de su clase.

A partir de la versión beta 6, _stdlib_getTypeName obtiene el nombre de tipo destrozado de una variable. Pega esto en un patio vacío:

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

El resultado es:

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

La entrada del blog de Ewan Swick ayuda a descifrar estas cadenas: http://www.eswick.com/2014/06/inside-swift/

por ejemplo _TtSi significa tipo interno de Swift.

En Swift 2.0 (probado en 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) } 

xcode 7 beta 5:

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

cadena de clase

 let classString = NSStringFromClass(TestViewController.self) 

o

 let classString = NSStringFromClass(TestViewController.classForCoder()) 

iniciar una clase UIViewController desde la cadena:

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

Creo que tengo razón al decir que no se puede, al menos no con la beta actual (2). Esperemos que esto sea algo que cambie en futuras versiones.

Puede usar NSClassFromString para obtener una variable de tipo AnyClass pero parece que no hay manera en Swift de crear una instancia. Puede usar un puente para Objective C y hacerlo allí o, si funciona en su caso, recurrir a una instrucción switch .

Aparentemente, no es posible (más) instanciar un objeto en Swift cuando el nombre de la clase solo se conoce en tiempo de ejecución. Un contenedor Objective-C es posible para subclases de NSObject.

Al menos puede instanciar un objeto de la misma clase que otro objeto dado en tiempo de ejecución sin un contenedor Objective-C (usando xCode Versión 6.2 – 6C107a):

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

En Swift 2.0 (probado en la beta2 de Xcode 7) funciona así:

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

Por supuesto, el tipo procedente de NSClassFromString tiene que implementar este protocolo de inicio.

Espero que esté claro, className es una cadena que contiene el nombre de tiempo de ejecución Obj-C de la clase que, por defecto, no es solo “Foo”, pero esta discusión no es en mi humilde opinión el tema principal de tu pregunta.

Necesitas este protocolo porque por defecto todas las clases Swift no implementan un método init .

Parece que el conjuro correcto sería …

 func newForName(p:String) -> T? { var result:T? = nil if let k:AnyClass = NSClassFromString(p) { result = (k as! T).dynamicType.init() } return result } 

… donde “p” significa “empaquetado” – un problema distinto.

Pero el lanzamiento crítico de AnyClass a T causa actualmente un locking del comstackdor, por lo tanto, mientras tanto, uno debe detener la inicialización de k en un cierre separado, que comstack bien.

Utilizo objectives diferentes, y en este caso no se encuentra la clase rápida. Debe reemplazar CFBundleName con CFBundleExecutable. También arreglé las advertencias:

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

También en Swift 2.0 (¿posiblemente antes?) Puede acceder al tipo directamente con la propiedad dynamicType

es decir

 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) 

Salida

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

Funciona para mi caso de uso

Prueba esto.

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

Lo he implementado así,

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

Estoy usando esta categoría para 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.Type? { return StringClassConverter.convert(string: self) } } class StringClassConverter { 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 } } 

El uso sería:

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

¿No es la solución tan simple como esto?

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

Un ejemplo de salto de página que se muestra aquí, ¡la esperanza puede ayudarte!

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

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

Aquí hay un buen ejemplo:

 class EPRocks { @require init() { } } class EPAwesome : EPRocks { func awesome() -> String { return "Yes"; } } var epawesome = EPAwesome.self(); print(epawesome.awesome);