Comment réaliser la réflexion en Swift Language?
Comment puis-je instancier une classe
[[NSClassFromString(@"Foo") alloc] init];
Comment réaliser la réflexion en Swift Language?
Comment puis-je instancier une classe
[[NSClassFromString(@"Foo") alloc] init];
Réponses:
Solution moins piratée ici: https://stackoverflow.com/a/32265287/308315
Notez que les classes Swift sont désormais espacées de noms, donc au lieu de "MyViewController", ce serait "AppName.MyViewController"
Obsolète depuis XCode6-beta 6/7
Solution développée avec XCode6-beta 3
Grâce à la réponse d'Edwin Vermeer, j'ai pu créer quelque chose pour instancier des classes Swift dans une classe Obj-C en faisant ceci:
// 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
}
ÉDITER
Vous pouvez également le faire en pur 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!
Vous devez mettre @objc(SwiftClassName)
au-dessus de votre classe rapide.
Comme:
@objc(SubClass)
class SubClass: SuperClass {...}
NSClassFromString()
La fonction a besoin d'un nom spécifié par l' @objc
attribution.
@objc(SubClass)
fonctionne, mais @objc class SubClass
pas?
@objc class SubClass
forme, le nom est implicite comme étant le même que le nom de la sous-classe. Et sous la @objc(SubClass) class SubClass
forme, il est spécifié directement. Je suppose que le compilateur ne peut tout simplement pas le comprendre par lui-même sous la première forme pour une raison quelconque.
C'est ainsi que j'initialise UIViewController par nom de classe
var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass()
Plus d'informations ici
Dans iOS 9
var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass.init()
MISE À JOUR: À partir de la version bêta 6, NSStringFromClass renverra le nom de votre bundle plus le nom de la classe séparés par un point. Ce sera donc quelque chose comme MyApp.MyClass
Les classes Swift auront un nom interne construit composé des parties suivantes:
Donc, votre nom de 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 est devenu:
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!
Consultez ce document:
NSClass
classes, pas les classes Swift. NSClassFromString("String")
revient nil
, mais NSClassFromString("NSString")
ne le fait pas.
var myVar:NSClassFromString("myClassName")
String
n'est pas une classe; c'est une struct
NSClassFromString
revient nil
de toute façon pour toutes les classes Swift.
J'ai pu instancier un objet dynamiquement
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'un String
(sans utiliser Obj-C). Je pense qu'ils ne veulent pas que vous fassiez cela parce que cela brise fondamentalement le système de types.
Pour swift2, j'ai créé une extension très simple pour le faire plus rapidement 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()
}
}
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 souhaitez 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 Int
type 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)
}
chaîne de classe
let classString = NSStringFromClass(TestViewController.self)
ou
let classString = NSStringFromClass(TestViewController.classForCoder())
init une classe UIViewController à partir de la chaîne:
let vcClass = NSClassFromString(classString) as! UIViewController.Type
let viewController = vcClass.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()
}
Je pense que j'ai raison de dire que vous ne pouvez pas, du moins pas avec la version bêta actuelle (2). Espérons que c'est quelque chose qui changera dans les versions futures.
Vous pouvez utiliser NSClassFromString
pour obtenir une variable de type AnyClass
mais il ne semble y avoir aucun moyen dans Swift de l'instancier. Vous pouvez utiliser un pont vers l'Objectif C et le faire là-bas ou - si cela fonctionne dans votre cas - utiliser une instruction switch .
Apparemment, il n'est plus possible (plus) d'instancier un objet dans Swift lorsque le nom de la classe n'est connu qu'au moment de 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 qu'un autre objet donné lors de l'exécution sans wrapper Objective-C (en utilisant xCode Version 6.2 - 6C107a):
class Test : NSObject {}
var test1 = Test()
var test2 = test1.dynamicType.alloc()
Dans Swift 2.0 (testé dans la version bêta2 de Xcode 7), cela fonctionne comme ceci:
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 d'initialisation.
Je m'attends à ce que ce soit clair, className
est une chaîne contenant le nom d'exécution Obj-C de la classe qui n'est par défaut PAS juste "Foo", mais cette discussion n'est pas à mon humble avis le sujet principal de votre question.
Vous avez besoin de ce protocole car par défaut, toutes les classes Swift n'implémentent pas de init
méthode.
On dirait que l'incantation correcte 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 d'AnyClass vers T provoque actuellement un plantage du compilateur, donc en attendant, il faut casser l'initialisation de k dans une fermeture séparée, ce qui se compile correctement.
J'utilise des cibles différentes, et dans ce cas, la classe swift n'est pas trouvée. Vous devez remplacer CFBundleName par CFBundleExecutable. J'ai également 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);
}
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"
Aussi dans Swift 2.0 (peut-être avant?) Vous pouvez accéder au type directement avec la dynamicType
propriété
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)
Production
aUser: User = {
name = "Tom"
}
bUser: User = {
name = ""
}
Fonctionne pour mon cas d'utilisation
J'ai implémenté comme ça,
if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
ImplementationClass.init()
}
Swift 5 , facile à utiliser, grâce à @Ondrej Rafaj's
Code source:
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
}
}
Appelez comme ceci:
guard let ctrl = "ViewCtrl".controller else {
return
}
// ctrl do sth
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)
}
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)
}
}
Voici un bon exemple:
class EPRocks {
@require init() { }
}
class EPAwesome : EPRocks {
func awesome() -> String { return "Yes"; }
}
var epawesome = EPAwesome.self();
print(epawesome.awesome);