¿Cómo lograr la reflexión en Swift Language?
¿Cómo puedo crear una instancia de una clase?
[[NSClassFromString(@"Foo") alloc] init];
ios
objective-c
swift
Selvin
fuente
fuente
Respuestas:
Solución menos hacky aquí: https://stackoverflow.com/a/32265287/308315
Tenga en cuenta que las clases Swift ahora tienen un espacio de nombres, por lo que en lugar de "MyViewController" sería "AppName.MyViewController"
Gracias a la respuesta de Edwin Vermeer, pude construir algo para instanciar 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!
fuente
Debes poner por
@objc(SwiftClassName)
encima de tu clase veloz.Me gusta:
@objc(SubClass) class SubClass: SuperClass {...}
fuente
NSClassFromString()
la función necesita un nombre especificado por la@objc
atribución.@objc(SubClass)
funciona, pero@objc class SubClass
no.@objc class SubClass
forma, se implica que el nombre es el mismo que el nombre de la Subclase. Y en la@objc(SubClass) class SubClass
forma se especifica directamente. Supongo que el compilador simplemente no puede resolverlo por sí mismo en la primera forma por alguna razón.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()
fuente
ACTUALIZACIÓN: A partir de la versión beta 6, NSStringFromClass devolverá el nombre del paquete más el nombre de la clase separados por un punto. Entonces será algo como MyApp.MyClass
Las clases Swift tendrán un nombre interno construido que se compone de las siguientes partes:
Entonces su 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, puede crear una instancia de su clase Swift ejecutando:
var anyobjectype : AnyObject.Type = NSClassFromString(classString) var nsobjectype : NSObject.Type = anyobjectype as NSObject.Type var rec: AnyObject = nsobjectype()
fuente
Es casi lo mismo
func NSClassFromString(_ aClassName: String!) -> AnyClass!
Consulte este documento:
https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Misiverse/Foundation_Functions/#//apple_ref/c/func/NSClassFromString
fuente
NSClass
clases, no con clases Swift.NSClassFromString("String")
regresanil
, peroNSClassFromString("NSString")
no lo hace.var myVar:NSClassFromString("myClassName")
String
no es una clase; es una estructuraNSClassFromString
regresanil
para todas las clases de Swift.Pude crear una instancia de un objeto de forma dinámica
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
desde unString
(sin usar Obj-C). Creo que no quieren que hagas eso porque básicamente rompe el sistema de tipos.fuente
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 el 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) }
fuente
Esto le dará el nombre de la clase que desea instanciar. Luego, puede usar la respuesta de Edwin para crear una instancia de un nuevo objeto de su clase.
A partir de la beta 6, se
_stdlib_getTypeName
obtiene el nombre de tipo alterado de una variable. Pegue esto en un patio de recreo 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))")
La salida 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
representa elInt
tipo interno de Swift .fuente
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) }
fuente
xcode 7 beta 5:
class MyClass { required init() { print("Hi!") } } if let classObject = NSClassFromString("YOURAPPNAME.MyClass") as? MyClass.Type { let object = classObject.init() }
fuente
cadena de clase
let classString = NSStringFromClass(TestViewController.self)
o
let classString = NSStringFromClass(TestViewController.classForCoder())
inicie una clase UIViewController desde la cadena:
let vcClass = NSClassFromString(classString) as! UIViewController.Type let viewController = vcClass.init()
fuente
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>() -> 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 } }
El uso sería:
func getViewController(fromString: String) -> UIViewController? { guard let viewController: UIViewController.Type = "MyViewController".converToClass() else { return nil } return viewController.init() }
fuente
Creo que tengo razón al decir que no puedes, al menos no con la versión beta actual (2). Ojalá esto sea algo que cambiará en futuras versiones.
Puede usar
NSClassFromString
para obtener una variable de tipo,AnyClass
pero parece que no hay forma en Swift de instanciarla. Puede usar un puente a Objective C y hacerlo allí o, si funciona en su caso, volver a usar una declaración de cambio .fuente
Aparentemente, ya no es posible crear una instancia de 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 crear una instancia de 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()
fuente
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()
Seguro que el tipo que viene
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 de 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 su pregunta.Necesita este protocolo porque, por defecto, todas las clases de Swift no implementan un
init
método.fuente
Parece que el encantamiento correcto sería ...
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 }
... donde "p" significa "empaquetado" - un tema distinto.
Pero el elenco crítico de AnyClass a T actualmente causa un bloqueo del compilador, por lo que, mientras tanto, uno debe descomponer la inicialización de k en un cierre separado, que compila bien.
fuente
Utilizo diferentes objetivos 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); }
fuente
¿No es la solución tan simple como esta?
// Given the app/framework/module named 'MyApp' let className = String(reflecting: MyClass.self) // className = "MyApp.MyClass"
fuente
También en Swift 2.0 (¿posiblemente antes?) Puede acceder al tipo directamente con la
dynamicType
propiedades 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
fuente
Prueba esto.
let className: String = String(ControllerName.classForCoder()) print(className)
fuente
Lo he implementado así,
if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{ ImplementationClass.init() }
fuente
Swift 5 , fácil de usar, gracias a @Ondrej Rafaj's
Código fuente:
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 } }
Llame así:
guard let ctrl = "ViewCtrl".controller else { return } // ctrl do sth
fuente
Un ejemplo de salto de página que se muestra aquí, ¡la esperanza puede ayudarlo!
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) }
fuente
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) } }
fuente
He aquí un buen ejemplo:
class EPRocks { @require init() { } } class EPAwesome : EPRocks { func awesome() -> String { return "Yes"; } } var epawesome = EPAwesome.self(); print(epawesome.awesome);
fuente