Swift język NSClassFromString


83

Jak osiągnąć refleksję w Swift Language?

Jak mogę utworzyć instancję klasy

[[NSClassFromString(@"Foo") alloc] init];

1
Spróbuj w ten sposób, jeśli pozwól ImplementationClass: NSObject.Type = NSClassFromString (className) as? NSObject.Type {ImplementationClass.init ()}
Pushpa Raja

Odpowiedzi:


37

Mniej hakerskie rozwiązanie tutaj: https://stackoverflow.com/a/32265287/308315

Zauważ, że klasy Swift mają teraz przestrzeń nazw, więc zamiast „MyViewController” będzie to „AppName.MyViewController”


Przestarzałe od XCode6-beta 6/7

Rozwiązanie opracowane przy użyciu XCode6-beta 3

Dzięki odpowiedzi Edwina Vermeera udało mi się zbudować coś, co utworzy instancję klas Swift w klasie Obj-C, wykonując następujące czynności:

// 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
}

EDYTOWAĆ

Możesz to również zrobić w czystym 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);
}

Mam nadzieję, że to komuś pomoże!


2
Począwszy od wersji beta 7 to już nie będzie działać. NSStringFromClass zwróci teraz tylko nazwę pakietu i nazwę klasy oddzielone kropką. Możesz więc użyć kodu takiego jak: var appName: String = NSBundle.mainBundle (). ObjectForInfoDictionaryKey ("CFBundleName") jako String? pozwolić classStringName String = NSStringFromClass (theObject.dynamicType) classStringName.stringByReplacingOccurrencesOfString zwrotny (appName + withString: "", opcje: NSStringCompareOptions.CaseInsensitiveSearch, zakres "" zero)
Edwin Vermeer

@KevinDelord Użyłem twojej techniki w moim kodzie. Ale zmienna appName nie zwraca prawidłowej wartości, gdy aplikacja ma spację w swojej nazwie. Masz jakiś pomysł, jak to naprawić? Na przykład „Nazwa aplikacji” zamiast „Nazwa_aplikacji”
Loadex

Nie można znaleźć funkcji countElements. Jakaś pomoc w tej sprawie?
C0D3

Zamiast tego użyj tego dla classStringName: let classStringName = "_TtC (appName! .Characters.count) (appName) (className.characters.count) (className)"
C0D3

@Loadex, to nie działa, jeśli nazwa pliku wykonywalnego zawiera spację. Musisz również zastąpić spacje podkreśleniami. Zasugerowano to w tym (nieco mylącym) poście na blogu: medium.com/@maximbilan/…. Kod w poście działał, ale rzeczywisty post mówił o używaniu nazwy aplikacji i nazwy docelowej, które różniły się od tego, co robi ich kod.
stuckj

57

Musisz postawić @objc(SwiftClassName)ponad swoją szybką klasą.
Lubić:

@objc(SubClass)
class SubClass: SuperClass {...}

2
NSClassFromString()funkcja wymaga nazwy określonej przez @objcatrybucję.
eonil

1
Niesamowite, taka mała rzecz, która ma tak ogromny wpływ na moje samopoczucie psychiczne!
Adrian_H

1
czy ktoś może pokazać, jak używać NSClassFromString () po wykonaniu czynności @objc nad nazwą klasy. Właśnie dostałem AnyClass! powrócił
Ryan Bobrowski

Mi to pasuje. Ale mam pytanie, dlaczego @objc(SubClass)działa, ale @objc class SubClassnie?
pyanfield

@pyanfield z dokumentu Swift: „Atrybut objc opcjonalnie akceptuje pojedynczy argument atrybutu, który składa się z identyfikatora. Identyfikator określa nazwę, która ma być ujawniona Objective-C dla jednostki, której dotyczy atrybut objc.” Tak więc różnica polega na tym, w jaki sposób nasza podklasa Swift otrzymuje nazwę, która będzie widoczna dla celu C. W @objc class SubClasspostaci zakłada się, że nazwa jest taka sama jak nazwa podklasy. I w @objc(SubClass) class SubClassformie jest określony bezpośrednio. Wydaje mi się, że kompilator z jakiegoś powodu nie może sam tego rozgryźć w pierwszej formie.
voiger,

56

W ten sposób inicjalizuję wyprowadzenie UIViewController na podstawie nazwy klasy

var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass()

Więcej informacji tutaj

W iOS 9

var className = "YourAppName.TestViewController"
let aClass = NSClassFromString(className) as! UIViewController.Type
let viewController = aClass.init()

11
Jeśli nazwa aplikacji zawiera „-”, należy ją zastąpić
znakiem

@RigelChen fajne rozwiązanie dzięki ale jeśli potrzebujemy tylko typu (TestViewController) i nie chcemy go inicjalizować za każdym razem to co powinniśmy zrobić?
ArgaPK

@RyanHeitner fajne rozwiązanie dzięki, ale jeśli potrzebujemy tylko typu (TestViewController) i nie chcemy go inicjalizować za każdym razem, to co powinniśmy zrobić?
ArgaPK

@argap Utwórz singleton i uzyskaj do niego dostęp: let viewController = aClass.sharedInstance ()
mahboudz

27

AKTUALIZACJA: Począwszy od wersji beta 6 NSStringFromClass zwróci nazwę twojego pakietu oraz nazwę klasy oddzielone kropką. Będzie to więc coś w rodzaju MyApp.MyClass

Klasy Swift będą miały skonstruowaną nazwę wewnętrzną, która składa się z następujących części:

  • Zacznie się od _TtC,
  • po którym następuje liczba będąca długością nazwy aplikacji,
  • po którym następuje nazwa aplikacji,
  • a po nim liczba będąca długością nazwy Twojej klasy,
  • po którym następuje nazwa klasy.

Więc nazwa klasy będzie miała postać _TtC5MyApp7MyClass

Możesz uzyskać tę nazwę jako ciąg, wykonując:

var classString = NSStringFromClass(self.dynamicType)

Aktualizacja w Swift 3 zmieniła się na:

var classString = NSStringFromClass(type(of: self))

Używając tego ciągu, możesz utworzyć instancję swojej klasy Swift, wykonując:

var anyobjectype : AnyObject.Type = NSClassFromString(classString)
var nsobjectype : NSObject.Type = anyobjectype as NSObject.Type
var rec: AnyObject = nsobjectype()

To zadziała tylko dla klas NSObject, o co chodzi w klasach Swift. Domyślnie nie mają żadnej metody init, a rzutowanie typu do protokołu wydaje się nie działać, jakiś pomysł?
Stephan

Awaria Swift 1.2 / XCode 6.3.1 w moim przypadku z tą konstrukcją
Stephan

Stworzyłem klasę refleksji z obsługą NSCoding, Printable, Hashable, Equatable i JSON github.com/evermeer/EVReflection
Edwin Vermeer

Wspomniany problem został rozwiązany w Swift 2.0 ... zobacz moją odpowiedź, ponieważ musisz wywołać init ... po prostu nsobjectype () nie jest już poprawne.
Stephan

1
let classString = NSStringFromClass (type (of: self))
Abhishek Thapliyal

11

5
Ta funkcja działa tylko z NSClassklasami, a nie z klasami Swift. NSClassFromString("String")wraca nil, ale NSClassFromString("NSString")nie.
Cezary Wojcik

Nie jestem przed komputerem ... ale możesz spróbować z: var myVar:NSClassFromString("myClassName")
gabuh

2
@CezaryWojcik: ps Stringnie jest klasą; to jest struct
newacct

2
@newacct D'oh, masz rację, mój błąd. Ale i tak NSClassFromStringwraca nildla wszystkich klas Swift.
Cezary Wojcik

Jeśli klasa jest opatrzona adnotacją @objc lub dziedziczy po NSObject, to używa systemu obiektowego Objective-C i uczestniczy w NSClassFromString, zamianie metod itp. Jeśli jednak tak nie jest, klasa jest wewnętrzna dla kodu Swift i nie Nie używam tego samego systemu obiektów. Nie zostało to w pełni opisane, ale system obiektów Swifta wydaje się być mniej spóźniony niż system Objective-C.
Bill

9

Mogłem dynamicznie utworzyć instancję obiektu

var clazz: NSObject.Type = TestObject.self
var instance : NSObject = clazz()

if let testObject = instance as? TestObject {
    println("yes!")
}

Nie znalazłem sposobu na tworzenie AnyClassz pliku String(bez użycia Obj-C). Myślę, że nie chcą, żebyś to robił, ponieważ zasadniczo łamie to system typów.


1
Myślę, że ta odpowiedź, choć nie dotyczy NSClassFromString, jest najlepszą tutaj, jeśli chodzi o zapewnienie skoncentrowanego na Swift sposobu inicjowania dynamicznego obiektu. Dzięki za udostępnienie!
Matt Long

Dziękuję również @Sulthan i Mattowi za podkreślenie, dlaczego ta odpowiedź powinna być najlepsza!
Ilias Karim

7

Dla swift2 stworzyłem bardzo proste rozszerzenie, aby zrobić to szybciej 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()
    }
}

W moim przypadku robię to, aby załadować ViewController, który chcę:

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)
}

6

W ten sposób otrzymasz nazwę klasy, której chcesz utworzyć instancję. Następnie możesz użyć odpowiedzi Edwinsa, aby utworzyć wystąpienie nowego obiektu swojej klasy.

Od wersji beta 6 _stdlib_getTypeNamepobiera zniekształconą nazwę typu zmiennej. Wklej to do pustego placu zabaw:

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))")

Wynik to:

TypeName0 = NSString
TypeName1 = _TtC13__lldb_expr_014PureSwiftClass
TypeName2 = _TtSi
TypeName3 = _TtSS

Wpis na blogu Ewana Swicka pomaga rozszyfrować te ciągi: http://www.eswick.com/2014/06/inside-swift/

np. _TtSioznacza wewnętrzny Inttyp Swift .


„Następnie możesz użyć odpowiedzi Edwinsa, aby utworzyć instancję nowego obiektu swojej klasy”. Nie sądzę, żeby ref. odpowiedz praca dla PureSwiftClass. np. domyślnie ta klasa nie ma metody init.
Stephan

6

W Swift 2.0 (testowany w 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)
}

5

xcode 7 beta 5:

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

3

ciąg z klasy

let classString = NSStringFromClass(TestViewController.self)

lub

let classString = NSStringFromClass(TestViewController.classForCoder())

init a UIViewController class from string:

let vcClass = NSClassFromString(classString) as! UIViewController.Type
let viewController = vcClass.init()

3

Używam tej kategorii dla 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

    }

}

Zastosowanie byłoby następujące:

func getViewController(fromString: String) -> UIViewController? {
    guard let viewController: UIViewController.Type = "MyViewController".converToClass() else {
        return nil
    }
    return viewController.init()
}

2

Myślę, że mam rację, mówiąc, że nie możesz, przynajmniej nie przy obecnej wersji beta (2). Miejmy nadzieję, że to się zmieni w przyszłych wersjach.

Możesz użyć, NSClassFromStringaby uzyskać zmienną typu, AnyClassale wydaje się, że w języku Swift nie ma możliwości jej utworzenia. Możesz użyć pomostu do celu C i zrobić to tam lub - jeśli zadziała w twoim przypadku - wrócić do używania instrukcji switch .


Nawet ze Swift 1.2 / XCode 6.3.1 wydaje się to niemożliwe, czy też znalazłeś rozwiązanie?
Stephan

2

Najwyraźniej nie jest już (już) możliwe utworzenie wystąpienia obiektu w Swift, gdy nazwa klasy jest znana tylko w czasie wykonywania. Opakowanie Objective-C jest możliwe dla podklas NSObject.

Przynajmniej możesz utworzyć instancję obiektu tej samej klasy, co inny obiekt podany w czasie wykonywania bez opakowania Objective-C (używając xCode w wersji 6.2 - 6C107a):

    class Test : NSObject {}
    var test1 = Test()
    var test2 = test1.dynamicType.alloc()

Nie tworzy to instancji na podstawie nazwy klasy.
Stephan

Z Twojej odpowiedzi nie wynika jasno, wygląda na to, że nie działa na czystych klasach Swift, prawda?
Stephan

2

W Swift 2.0 (testowany w wersji beta2 Xcode 7) działa to tak:

protocol Init {
  init()
}

var type = NSClassFromString(className) as? Init.Type
let obj = type!.init()

Z pewnością typ pochodzący z NSClassFromStringmusi zaimplementować ten protokół inicjujący.

Spodziewam się, że jest jasne, że classNamejest to ciąg znaków zawierający nazwę środowiska wykonawczego Obj-C klasy, która domyślnie NIE jest tylko „Foo”, ale ta dyskusja nie jest głównym tematem twojego pytania.

Potrzebujesz tego protokołu, ponieważ domyślnie wszystkie klasy Swift nie implementują initmetody.


Jeśli masz zajęcia z Pur Swift, zobacz moje inne pytania: stackoverflow.com/questions/30111659
Stephan

2

Wygląda na to, że poprawna inkantacja byłaby ...

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
}

... gdzie „p” oznacza „zapakowany” - odrębny problem.

Ale krytyczne rzutowanie z AnyClass na T obecnie powoduje awarię kompilatora, więc w międzyczasie należy przełączyć inicjalizację k do osobnego zamknięcia, które kompiluje się dobrze.


2

Używam różnych celów iw tym przypadku nie znaleziono klasy swift. Należy zamienić CFBundleName na CFBundleExecutable. Naprawiłem też ostrzeżenia:

- (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);
}

2

Czy rozwiązanie nie jest tak proste?

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

// className = "MyApp.MyClass"

3
pierwotnie chodziło o to, aby klasa była z łańcucha, a nie z klasy.
Ryan

1

Również w Swift 2.0 (prawdopodobnie wcześniej?) Możesz uzyskać dostęp do typu bezpośrednio z dynamicTypewłaściwością

to znaczy

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)

Wynik

aUser: User = {
  name = "Tom"
}
bUser: User = {
  name = ""
}

Działa w moim przypadku użycia


1

Spróbuj tego.

let className: String = String(ControllerName.classForCoder())
print(className)

1

Wdrożyłem w ten sposób,

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

1

Swift 5 , łatwy w użyciu dzięki @Ondrej Rafaj's

  • Kod źródłowy:

    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
    
       }
    
    }
    
  • Zadzwoń tak:

    guard let ctrl = "ViewCtrl".controller else {
        return
    }
    //  ctrl do sth
    

0

Przykład skoku strony pokazany tutaj, nadzieja może ci pomóc!

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)
}

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)
    }
}

Dziękujemy za ten fragment kodu, który może zapewnić ograniczoną, natychmiastową pomoc. Właściwe wyjaśnienie byłoby znacznie poprawić swoją długoterminową wartość pokazując dlaczego jest to dobre rozwiązanie problemu, a byłoby bardziej użyteczne dla czytelników przyszłości z innymi, podobnymi pytaniami. Proszę edytować swoją odpowiedź dodać kilka wyjaśnień, w tym założeń już wykonanych.
iBug

-6

Oto dobry przykład:

class EPRocks { 
    @require init() { } 
}

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

var epawesome = EPAwesome.self(); 
print(epawesome.awesome);

1
To nie robi tego, o co chodzi w pytaniu.
Thomas W.
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.