Schnelle Sprache NSClassFromString

83

Wie kann man in Swift Language reflektieren ?

Wie kann ich eine Klasse instanziieren?

[[NSClassFromString(@"Foo") alloc] init];
Selvin
quelle
1
Versuchen Sie es wie folgt, wenn ImplementationClass: NSObject.Type = NSClassFromString (className) as? NSObject.Type {ImplementationClass.init ()}
Pushpa Raja

Antworten:

37

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!

Kevin Delord
quelle
2
Ab Beta 7 funktioniert dies nicht mehr. NSStringFromClass gibt jetzt nur noch Ihren Bundle-Namen und den durch einen Punkt getrennten Klassennamen zurück. Sie könnten also Code wie: var appName: String = NSBundle.mainBundle (). ObjectForInfoDictionaryKey ("CFBundleName") als String verwenden? lassen classStringName: String = NSStringFromClass (theObject.dynamicType) return classStringName.stringByReplacingOccurrencesOfString (appName +, withString: "", Möglichkeiten: NSStringCompareOptions.CaseInsensitiveSearch, Bereich: "" nil)
Edwin Vermeer
@ KevinDelord Ich habe deine Technik in meinem Code verwendet. Die Variable appName gibt jedoch nicht den richtigen Wert zurück, wenn die App ein Leerzeichen in ihrem Namen enthält. Irgendeine Idee, wie man das behebt? Ex "App Name" anstelle von "App_Name"
Loadex
Die countElements-Funktion kann nicht gefunden werden. Hilfe dazu?
C0D3
Verwenden Sie dies stattdessen für classStringName: Lassen Sie classStringName = "_TtC (appName! .Characters.count) (appName) (className.characters.count) (className)"
C0D3
@ Loadex, Dies funktioniert nicht, wenn Ihr ausführbarer Name ein Leerzeichen enthält. Sie müssen die Leerzeichen auch durch Unterstriche ersetzen. Es wurde in diesem (etwas verwirrenden) Blog-Beitrag vorgeschlagen: medium.com/@maximbilan/…. Der Code im Beitrag funktionierte, aber im eigentlichen Beitrag wurde über die Verwendung von App- und Zielnamen gesprochen, die sich von dem Code unterschieden.
Stuckj
56

Sie müssen @objc(SwiftClassName)über Ihre schnelle Klasse setzen.
Mögen:

@objc(SubClass)
class SubClass: SuperClass {...}
klaus
quelle
2
NSClassFromString()Die Funktion benötigt einen Namen, der durch die @objcZuordnung angegeben wird.
Eonil
1
Erstaunlich, so eine kleine Sache mit so großen Auswirkungen auf mein geistiges Wohlbefinden!
Adrian_H
kann jemand zeigen, wie man NSClassFromString () verwendet, nachdem er das @ objc-Ding über seinem Klassennamen ausgeführt hat. Ich bekomme gerade AnyClass! zurückgegeben
Ryan Bobrowski
Für mich geht das. Aber ich habe eine Frage, warum @objc(SubClass)funktioniert, aber @objc class SubClassnicht?
Pyanfield
@pyanfield aus dem Swift-Dokument: "Das objc-Attribut akzeptiert optional ein einzelnes Attributargument, das aus einem Bezeichner besteht. Der Bezeichner gibt den Namen an, der für die Entität, für die das objc-Attribut gilt, Objective-C zugänglich gemacht werden soll." Der Unterschied besteht also darin, wie unsere Swift-Unterklasse den Namen erhält, der für Ziel C sichtbar ist. In der @objc class SubClassForm wird impliziert, dass der Name mit dem Namen der Unterklasse identisch ist. Und in der @objc(SubClass) class SubClassForm ist es direkt angegeben. Ich denke, der Compiler kann es aus irgendeinem Grund in der ersten Form einfach nicht selbst herausfinden.
Voiger
56

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()
Rigel Chen
quelle
11
Wenn der App-Name "-" enthält, sollte er durch "_"
Zitao Xiong
@RigelChen nette Lösung, danke, aber wenn wir nur Typ (TestViewController) benötigen und ihn nicht jedes Mal initialisieren möchten, was sollen wir dann tun?
ArgaPK
@ RyanHeitner nette Lösung danke, aber wenn wir nur Typ (TestViewController) brauchen und ihn nicht jedes Mal initialisieren wollen, was sollen wir dann tun?
ArgaPK
@argap Erstellen Sie einen Singleton und greifen Sie darauf zu: let viewController = aClass.sharedInstance ()
Mahboudz
27

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:

  • Es wird mit _TtC beginnen,
  • gefolgt von einer Zahl, die der Länge Ihres Anwendungsnamens entspricht.
  • gefolgt von Ihrem Anwendungsnamen,
  • gefolgt von einer Zahl, die der Länge Ihres Klassennamens entspricht,
  • gefolgt von Ihrem Klassennamen.

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()
Edwin Vermeer
quelle
Dies funktioniert nur für NSObject-Klassen. Was ist mit Swift-Klassen? Standardmäßig haben sie keine Init-Methode und eine Typumwandlung zum Protokoll scheint nicht zu funktionieren, eine Idee?
Stephan
Swift 1.2 / XCode 6.3.1crash in meinem Fall mit diesem Konstrukt
Stephan
Ich habe eine Reflexionsklasse mit Unterstützung für NSCoding, druckbare, Hashable, gleichzusetzen und JSON erstellt github.com/evermeer/EVReflection
Edwin Vermeer
Das erwähnte Problem wurde in Swift 2.0 behoben ... siehe meine Antwort, weil Sie init aufrufen müssen ... nur nsobjectype () ist nicht mehr korrekt.
Stephan
1
let classString = NSStringFromClass (Typ (von: self))
Abhishek Thapliyal
11

Es ist fast das gleiche

func NSClassFromString(_ aClassName: String!) -> AnyClass!

Überprüfen Sie dieses Dokument:

https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/#//apple_ref/c/func/NSClassFromString

gabuh
quelle
5
Diese Funktion funktioniert nur mit NSClassKlassen, nicht mit Swift-Klassen. NSClassFromString("String")kehrt zurück nil, NSClassFromString("NSString")tut es aber nicht.
Cezary Wojcik
Ich bin nicht vor dem Computer ... aber Sie könnten versuchen mit: var myVar:NSClassFromString("myClassName")
gabuh
2
@CezaryWojcik: ps Stringist keine Klasse; Es ist eine Struktur
Newacct
2
@newacct D'oh, du hast recht, mein schlechtes. Aber NSClassFromStringkehrt auch nilfür alle Swift-Klassen zurück.
Cezary Wojcik
Wenn eine Klasse mit @objc kommentiert ist oder von NSObject erbt, verwendet sie das Objective-C-Objektsystem und nimmt an NSClassFromString, Methoden-Swizzling usw. teil. Ist dies jedoch nicht der Fall, befindet sich die Klasse in Ihrem Swift-Code und ist dies nicht Verwenden Sie nicht dasselbe Objektsystem. Es wurde nicht vollständig beschrieben, aber Swifts Objektsystem scheint weniger spät gebunden zu sein als das von Objective-C.
Bill
9

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, AnyClassaus 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.

Sulthan
quelle
1
Ich denke, diese Antwort bezieht sich zwar nicht auf NSClassFromString, ist jedoch die beste, um eine Swift-zentrierte Methode für die dynamische Objektinitialisierung bereitzustellen. Danke für das Teilen!
Matt Long
Vielen Dank auch an @Sulthan und Matt, die hervorgehoben haben, warum diese Antwort ganz oben stehen sollte!
Ilias Karim
7

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)
}
Damien Romito
quelle
6

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_getTypeNamewird 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 _TtSisteht für Swifts internen IntTyp.

Klaas
quelle
"Dann können Sie Edwins Antwort verwenden, um ein neues Objekt Ihrer Klasse zu instanziieren." Ich glaube nicht, dass der Schiedsrichter. Beantworten Sie die Arbeit für PureSwiftClass. zB hat diese Klasse standardmäßig keine init-Methode.
Stephan
6

In Swift 2.0 (getestet im Xcode 7.01) _20150930

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)
}
Geh Lass
quelle
5

xcode 7 beta 5:

class MyClass {
    required init() { print("Hi!") }
}
if let classObject = NSClassFromString("YOURAPPNAME.MyClass") as? MyClass.Type {
    let object = classObject.init()
}
Dezember
quelle
3

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()
Binglin
quelle
3

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()
}
Ondrej Rafaj
quelle
2

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 NSClassFromStringeine Variable vom Typ AnyClassabrufen, 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 .

Stephen Darlington
quelle
Selbst mit Swift 1.2 / XCode 6.3.1 scheint dies nicht möglich zu sein, oder haben Sie eine Lösung gefunden?
Stephan
2

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()
seb
quelle
Dadurch wird keine Instanz aus dem Klassennamen erstellt.
Stephan
Aus Ihrer Antwort geht nicht hervor, dass es anscheinend nicht mit reinen Swift-Klassen funktioniert, richtig?
Stephan
2

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 NSClassFromStringdem er kommt, dieses Init-Protokoll implementieren.

Ich gehe davon aus, dass es klar ist, dass es sich um classNameeinen 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 initMethode implementieren .

Stephan
quelle
Wenn Sie reine
Stephan
2

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.

rory
quelle
2

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);
}
Calin Drule
quelle
2

Ist die Lösung nicht so einfach?

// Given the app/framework/module named 'MyApp'
let className = String(reflecting: MyClass.self)

// className = "MyApp.MyClass"
Mark A. Donohoe
quelle
3
Die ursprüngliche Frage war, Klasse von Zeichenfolge zu wollen, nicht Zeichenfolge von Klasse.
Ryan
1

Auch in Swift 2.0 (möglicherweise vorher?) Können Sie direkt mit der dynamicTypeEigenschaft 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

Apocolipse
quelle
1

Versuche dies.

let className: String = String(ControllerName.classForCoder())
print(className)
Himanshu
quelle
1

Ich habe so implementiert,

if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
   ImplementationClass.init()
}
Pushpa Raja
quelle
1

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
schwarze Perle
quelle
0

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)
}
Tim
quelle
0

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)
    }
}
SeRG1k
quelle
Vielen Dank für dieses Code-Snippet, das möglicherweise nur begrenzte und sofortige Hilfe bietet. Eine richtige Erklärung würde ihren langfristigen Wert erheblich verbessern, indem sie zeigt, warum dies eine gute Lösung für das Problem ist, und es für zukünftige Leser mit anderen, ähnlichen Fragen nützlicher machen. Bitte bearbeiten Sie Ihre Antwort, um eine Erklärung hinzuzufügen, einschließlich der von Ihnen getroffenen Annahmen.
iBug
-6

Hier ist ein gutes Beispiel:

class EPRocks { 
    @require init() { } 
}

class EPAwesome : EPRocks { 
    func awesome() -> String { return "Yes"; } 
}

var epawesome = EPAwesome.self(); 
print(epawesome.awesome);
EvilPenguin
quelle
1
Dies tut nicht das, wonach die Frage verlangt.
ThomasW