¿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:
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()
Es casi lo mismo
func NSClassFromString(_ aClassName: String!) -> AnyClass!
Verifique este documento:
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.
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);