Comment réaliser réflexion en langue rapide?
Comment puis-je instancier une classe
[[NSClassFromString(@"Foo") alloc] init];
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!
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()
Vous devez placer @objc(SwiftClassName)
au-dessus de votre classe Swift.
Comme:
@objc(SubClass)
class SubClass: SuperClass {...}
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:
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()
C'est presque pareil
func NSClassFromString(_ aClassName: String!) -> AnyClass!
Vérifiez ce doc:
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.
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)
}
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.
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()
}
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()
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.
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
.
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()
}
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);
}
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 .
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()
J'ai mis en place comme ça,
if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
ImplementationClass.init()
}
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"
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
Essaye ça.
let className: String = String(ControllerName.classForCoder())
print(className)
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)
}
}
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)
}