Jak osiągnąć refleksję w Swift Language?
Jak mogę utworzyć instancję klasy
[[NSClassFromString(@"Foo") alloc] init];
ios
objective-c
swift
Selvin
źródło
źródło
Odpowiedzi:
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”
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!
źródło
Musisz postawić
@objc(SwiftClassName)
ponad swoją szybką klasą.Lubić:
@objc(SubClass) class SubClass: SuperClass {...}
źródło
NSClassFromString()
funkcja wymaga nazwy określonej przez@objc
atrybucję.@objc(SubClass)
działa, ale@objc class SubClass
nie?@objc class SubClass
postaci zakłada się, że nazwa jest taka sama jak nazwa podklasy. I w@objc(SubClass) class SubClass
formie jest określony bezpośrednio. Wydaje mi się, że kompilator z jakiegoś powodu nie może sam tego rozgryźć w pierwszej formie.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()
źródło
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:
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()
źródło
To prawie to samo
func NSClassFromString(_ aClassName: String!) -> AnyClass!
Sprawdź ten dokument:
https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/#//apple_ref/c/func/NSClassFromString
źródło
NSClass
klasami, a nie z klasami Swift.NSClassFromString("String")
wracanil
, aleNSClassFromString("NSString")
nie.var myVar:NSClassFromString("myClassName")
String
nie jest klasą; to jest structNSClassFromString
wracanil
dla wszystkich klas Swift.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
AnyClass
z plikuString
(bez użycia Obj-C). Myślę, że nie chcą, żebyś to robił, ponieważ zasadniczo łamie to system typów.źródło
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) }
źródło
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_getTypeName
pobiera 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.
_TtSi
oznacza wewnętrznyInt
typ Swift .źródło
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) }
źródło
xcode 7 beta 5:
class MyClass { required init() { print("Hi!") } } if let classObject = NSClassFromString("YOURAPPNAME.MyClass") as? MyClass.Type { let object = classObject.init() }
źródło
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()
źródło
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() }
źródło
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ć,
NSClassFromString
aby uzyskać zmienną typu,AnyClass
ale 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 .źródło
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()
źródło
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
NSClassFromString
musi zaimplementować ten protokół inicjujący.Spodziewam się, że jest jasne, że
className
jest 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ą
init
metody.źródło
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.
źródło
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); }
źródło
Czy rozwiązanie nie jest tak proste?
// Given the app/framework/module named 'MyApp' let className = String(reflecting: MyClass.self) // className = "MyApp.MyClass"
źródło
Również w Swift 2.0 (prawdopodobnie wcześniej?) Możesz uzyskać dostęp do typu bezpośrednio z
dynamicType
wł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
źródło
Spróbuj tego.
let className: String = String(ControllerName.classForCoder()) print(className)
źródło
Wdrożyłem w ten sposób,
if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{ ImplementationClass.init() }
źródło
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
źródło
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) }
źródło
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) } }
źródło
Oto dobry przykład:
class EPRocks { @require init() { } } class EPAwesome : EPRocks { func awesome() -> String { return "Yes"; } } var epawesome = EPAwesome.self(); print(epawesome.awesome);
źródło