Wie kann man in Swift Language reflektieren ?
Wie kann ich eine Klasse instanziieren?
[[NSClassFromString(@"Foo") alloc] init];
Wie kann man in Swift Language reflektieren ?
Wie kann ich eine Klasse instanziieren?
[[NSClassFromString(@"Foo") alloc] init];
Antworten:
Weniger hackige Lösung hier: https://stackoverflow.com/a/32265287/308315
Beachten Sie, dass Swift-Klassen jetzt einen Namespace haben. Anstelle von "MyViewController" wäre dies "AppName.MyViewController".
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 instanziieren:
// 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
}
BEARBEITEN
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!
Sie müssen @objc(SwiftClassName)
über Ihre schnelle Klasse setzen.
Mögen:
@objc(SubClass)
class SubClass: SuperClass {...}
NSClassFromString()
Die Funktion benötigt einen Namen, der durch die @objc
Zuordnung angegeben wird.
@objc(SubClass)
funktioniert, aber @objc class SubClass
nicht?
@objc class SubClass
Form wird impliziert, dass der Name mit dem Namen der Unterklasse identisch ist. Und in der @objc(SubClass) class SubClass
Form ist es direkt angegeben. Ich denke, der Compiler kann es aus irgendeinem Grund in der ersten Form einfach nicht selbst herausfinden.
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()
UPDATE: Ab Beta 6 gibt NSStringFromClass Ihren Bundle-Namen plus den durch einen Punkt getrennten Klassennamen zurück. Es wird also so etwas wie MyApp.MyClass sein
Swift-Klassen haben einen konstruierten internen Namen, der aus den folgenden Teilen besteht:
Ihr Klassenname lautet also ungefähr _TtC5MyApp7MyClass
Sie können diesen Namen als Zeichenfolge erhalten, indem Sie Folgendes ausführen:
var classString = NSStringFromClass(self.dynamicType)
Update In Swift 3 wurde Folgendes 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:
NSClass
Klassen, nicht mit Swift-Klassen. NSClassFromString("String")
kehrt zurück nil
, NSClassFromString("NSString")
tut es aber nicht.
var myVar:NSClassFromString("myClassName")
String
ist keine Klasse; Es ist eine Struktur
NSClassFromString
kehrt auch nil
für alle Swift-Klassen zurück.
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 keinen Weg gefunden, AnyClass
aus einem zu erstellen String
(ohne Obj-C zu verwenden). Ich denke, sie wollen nicht, dass Sie das tun, weil es im Grunde das Typensystem bricht.
Für swift2 habe ich eine sehr einfache Erweiterung erstellt, um dies schneller zu tun. 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()
}
}
In meinem Fall mache ich das, um den gewünschten ViewController zu laden:
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 Antwort verwenden , um ein neues Objekt Ihrer Klasse zu instanziieren.
Ab Beta 6 _stdlib_getTypeName
wird der verstümmelte Typname einer Variablen abgerufen. 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
Der Blogeintrag von Ewan Swick hilft beim Entschlüsseln dieser Zeichenfolgen: http://www.eswick.com/2014/06/inside-swift/
zB _TtSi
steht für Swifts internen Int
Typ.
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)
}
Zeichenfolge aus der Klasse
let classString = NSStringFromClass(TestViewController.self)
oder
let classString = NSStringFromClass(TestViewController.classForCoder())
Initiieren Sie eine UIViewController-Klasse aus einer Zeichenfolge:
let vcClass = NSClassFromString(classString) as! UIViewController.Type
let viewController = vcClass.init()
Ich benutze 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 glaube, ich habe Recht, wenn ich sage, dass Sie das nicht können, zumindest nicht mit der aktuellen Beta (2). Hoffentlich wird sich dies in zukünftigen Versionen ändern.
Sie können NSClassFromString
eine Variable vom Typ AnyClass
abrufen, aber in Swift scheint es keine Möglichkeit zu geben, sie zu instanziieren. Sie können eine Brücke zu Ziel C verwenden und dort ausführen oder - falls dies in Ihrem Fall funktioniert - auf die Verwendung einer switch-Anweisung zurückgreifen .
Anscheinend 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.
Zumindest können Sie ein Objekt derselben Klasse wie ein anderes zur Laufzeit angegebenes Objekt ohne einen Objective-C-Wrapper instanziieren (mit xCode Version 6.2 - 6C107a):
class Test : NSObject {}
var test1 = Test()
var test2 = test1.dynamicType.alloc()
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, von NSClassFromString
dem er kommt, dieses Init-Protokoll implementieren.
Ich gehe davon aus, dass es klar ist, dass es sich um className
einen String handelt, der den Obj-C-Laufzeitnamen der Klasse enthält, der standardmäßig NICHT nur "Foo" ist, aber diese Diskussion ist meiner Meinung nach nicht das Hauptthema Ihrer Frage.
Sie benötigen dieses Protokoll, da standardmäßig alle Swift-Klassen keine init
Methode implementieren .
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
}
... wobei "p" für "verpackt" steht - ein besonderes Problem.
Die kritische Umwandlung von AnyClass in T führt derzeit jedoch zu einem Absturz des Compilers. In der Zwischenzeit muss die Initialisierung von k in einen separaten Abschluss verschoben werden, der sich gut kompilieren lässt.
Ich benutze verschiedene Ziele, und in diesem Fall wird die schnelle 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);
}
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 direkt mit der dynamicType
Eigenschaft auf den Typ zugreifen
dh
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
Ich habe so implementiert,
if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
ImplementationClass.init()
}
Swift 5 , einfach zu bedienen, dank @Ondrej Rafaj's
Quellcode:
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
}
}
Rufen Sie so an:
guard let ctrl = "ViewCtrl".controller else {
return
}
// ctrl do sth
Ein hier gezeigtes Beispiel für einen Seitensprung, die Hoffnung kann Ihnen helfen!
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)
}
}
Hier ist ein gutes Beispiel:
class EPRocks {
@require init() { }
}
class EPAwesome : EPRocks {
func awesome() -> String { return "Yes"; }
}
var epawesome = EPAwesome.self();
print(epawesome.awesome);