¿Cómo lograr la reflexión en Swift Language?
¿Cómo puedo crear una instancia de una clase?
[[NSClassFromString(@"Foo") alloc] init];
¿Cómo lograr la reflexión en Swift Language?
¿Cómo puedo crear una instancia de una clase?
[[NSClassFromString(@"Foo") alloc] init];
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"
En desuso desde XCode6-beta 6/7
Solución desarrollada con XCode6-beta 3
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!
Debes poner por @objc(SwiftClassName)
encima de tu clase veloz.
Me gusta:
@objc(SubClass)
class SubClass: SuperClass {...}
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()
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()
Es casi lo mismo
func NSClassFromString(_ aClassName: String!) -> AnyClass!
Consulte este documento:
NSClass
clases, no con clases Swift. NSClassFromString("String")
regresa nil
, pero NSClassFromString("NSString")
no lo hace.
var myVar:NSClassFromString("myClassName")
String
no es una clase; es una estructura
NSClassFromString
regresa nil
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 un 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 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)
}
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 el Int
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)
}
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()
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()
}
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 .
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()
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.
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.
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);
}
¿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"
También en Swift 2.0 (¿posiblemente antes?) Puede acceder al tipo directamente con la dynamicType
propiedad
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
Lo he implementado así,
if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
ImplementationClass.init()
}
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
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)
}
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)
}
}
He aquí un buen ejemplo:
class EPRocks {
@require init() { }
}
class EPAwesome : EPRocks {
func awesome() -> String { return "Yes"; }
}
var epawesome = EPAwesome.self();
print(epawesome.awesome);