Wie erreicht man Reflektion in Swift Language?
Wie kann ich eine Klasse instanziieren?
[[NSClassFromString(@"Foo") alloc] init];
Weniger gehackte Lösung hier: https://stackoverflow.com/a/32265287/308315
Beachten Sie, dass Swift-Klassen jetzt einen Namespace haben, so dass anstelle von "MyViewController" "AppName.MyViewController" wäre
Veraltet seit XCode6-beta 6/7
Mit XCode6-beta 3 entwickelte Lösung
Dank der Antwort von Edwin Vermeer konnte ich etwas bauen, um Swift-Klassen in eine Obj-C-Klasse zu instantiieren:
// 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
}
EDIT
Sie können es auch in reinem obj-c tun:
- (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);
}
Ich hoffe das hilft jemandem!
Auf diese Weise habe ich UIViewController nach Klassennamen abgeleitet
var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass()
Weitere Informationen finden Sie hier
In iOS 9
var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass.init()
Sie müssen @objc(SwiftClassName)
über Ihre Swift-Klasse stellen.
Mögen:
@objc(SubClass)
class SubClass: SuperClass {...}
UPDATE: Ab Beta 6 wird NSStringFromClass Ihren Bundle-Namen plus Klassennamen durch einen Punkt getrennt anzeigen. Es wird also so etwas wie MyApp.MyClass sein
Swift-Klassen haben einen internen Namen, der sich aus den folgenden Teilen zusammensetzt:
Ihr Klassenname wird also etwa _TtC5MyApp7MyClass sein
Sie können diesen Namen als Zeichenfolge erhalten, indem Sie Folgendes ausführen:
var classString = NSStringFromClass(self.dynamicType)
Update In Swift 3 wurde dies folgendermaßen geändert:
var classString = NSStringFromClass(type(of: self))
Mit dieser Zeichenfolge können Sie eine Instanz Ihrer Swift-Klasse erstellen, indem Sie Folgendes ausführen:
var anyobjectype : AnyObject.Type = NSClassFromString(classString)
var nsobjectype : NSObject.Type = anyobjectype as NSObject.Type
var rec: AnyObject = nsobjectype()
Es ist fast das gleiche
func NSClassFromString(_ aClassName: String!) -> AnyClass!
Überprüfen Sie dieses Dokument:
Ich konnte ein Objekt dynamisch instanziieren
var clazz: NSObject.Type = TestObject.self
var instance : NSObject = clazz()
if let testObject = instance as? TestObject {
println("yes!")
}
Ich habe keine Möglichkeit gefunden, AnyClass
aus einer String
zu erstellen (ohne Obj-C). Ich denke, sie wollen nicht, dass Sie das tun, weil sie das Typsystem im Grunde bricht.
Für Swift2 habe ich eine sehr einfache Erweiterung erstellt, um dies schneller zu erledigenhttps://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()
}
}
In meinem Fall lade ich den gewünschten ViewController:
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)
}
Dadurch erhalten Sie den Namen der Klasse, die Sie instanziieren möchten. Dann können Sie Edwins answer verwenden, um ein neues Objekt Ihrer Klasse zu instanziieren.
Ab Beta 6 erhält _stdlib_getTypeName
den unzulässigen Typnamen einer Variablen. Fügen Sie dies in einen leeren Spielplatz ein:
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))")
Die Ausgabe ist:
TypeName0 = NSString
TypeName1 = _TtC13__lldb_expr_014PureSwiftClass
TypeName2 = _TtSi
TypeName3 = _TtSS
Ewan Swicks Blogeintrag hilft beim Entschlüsseln dieser Zeichenfolgen: http://www.eswick.com/2014/06/inside-Swift/
z.B. _TtSi
steht für den internen Int
-Typ von 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()
}
string aus der Klasse
let classString = NSStringFromClass(TestViewController.self)
oder
let classString = NSStringFromClass(TestViewController.classForCoder())
init eine UIViewController-Klasse aus Zeichenfolge:
let vcClass = NSClassFromString(classString) as! UIViewController.Type
let viewController = vcClass.init()
Sieht aus wie die richtige Beschwörung wäre ...
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
}
... wo "p" für "verpackt" steht - ein eindeutiges Problem.
Die kritische Besetzung von AnyClass nach T führt jedoch zu einem Absturz des Compilers. In der Zwischenzeit muss man die Initialisierung von k in einen separaten Closure aufbrechen, der sich gut kompilieren lässt.
In Swift 2.0 (getestet in der Beta2 von Xcode 7) funktioniert es folgendermaßen:
protocol Init {
init()
}
var type = NSClassFromString(className) as? Init.Type
let obj = type!.init()
Natürlich muss der Typ, der von NSClassFromString
kommt, dieses Init-Protokoll implementieren.
Ich denke, es ist klar, dass className
ein String ist, der den Obj-C-Laufzeitnamen der Klasse enthält, der standardmäßig NICHT nur "Foo" ist, aber diese Diskussion ist IMHO nicht das Hauptthema Ihrer Frage.
Sie benötigen dieses Protokoll, da standardmäßig alle Swift-Klassen keine init
-Methode implementieren.
Ich verwende diese Kategorie für 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
}
}
Die Verwendung wäre:
func getViewController(fromString: String) -> UIViewController? {
guard let viewController: UIViewController.Type = "MyViewController".converToClass() else {
return nil
}
return viewController.init()
}
Ich verwende verschiedene Ziele und in diesem Fall wird die Swift-Klasse nicht gefunden. Sie sollten CFBundleName durch CFBundleExecutable ersetzen. Ich habe auch die Warnungen behoben:
- (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);
}
Ich glaube, ich kann zu Recht sagen, dass Sie dies nicht tun können, zumindest nicht mit der aktuellen Beta (2). Hoffentlich ändert sich dies in zukünftigen Versionen.
Sie können NSClassFromString
verwenden, um eine Variable vom Typ AnyClass
abzurufen, aber es scheint keine Möglichkeit zu geben, sie in Swift zu instanziieren. Sie können eine bridge für Objective C verwenden und es dort tun oder - wenn es in Ihrem Fall funktioniert - auf eine switch-Anweisung zurückgreifen .
Offensichtlich ist es nicht mehr möglich, ein Objekt in Swift zu instanziieren, wenn der Name der Klasse nur zur Laufzeit bekannt ist. Ein Objective-C-Wrapper ist für Unterklassen von NSObject möglich.
Sie können zumindest ein Objekt derselben Klasse als ein anderes Objekt zur Laufzeit ohne Objective-C-Wrapper instanziieren (mit xCode Version 6.2 - 6C107a):
class Test : NSObject {}
var test1 = Test()
var test2 = test1.dynamicType.alloc()
Ich habe so umgesetzt,
if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
ImplementationClass.init()
}
Ist die Lösung nicht so einfach?
// Given the app/framework/module named 'MyApp'
let className = String(reflecting: MyClass.self)
// className = "MyApp.MyClass"
Auch in Swift 2.0 (möglicherweise vorher?) Können Sie mit der Eigenschaft dynamicType
direkt auf den Typ zugreifen
d.h.
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)
Ausgabe
aUser: User = {
name = "Tom"
}
bUser: User = {
name = ""
}
Funktioniert für meinen Anwendungsfall
Versuche dies.
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)
}
}
Ein hier gezeigtes Seitensprungbeispiel, das Ihnen helfen kann!
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)
}