Jak osiągnąć refleksję w Swift Language?
Jak mogę utworzyć instancję klasy
[[NSClassFromString(@"Foo") alloc] init];
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”
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!
Musisz postawić @objc(SwiftClassName)ponad swoją szybką klasą.
Lubić:
@objc(SubClass)
class SubClass: SuperClass {...}
NSClassFromString()funkcja wymaga nazwy określonej przez @objcatrybucję.
@objc(SubClass)działa, ale @objc class SubClassnie?
@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.
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()
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()
To prawie to samo
func NSClassFromString(_ aClassName: String!) -> AnyClass!
Sprawdź ten dokument:
NSClassklasami, a nie z klasami Swift. NSClassFromString("String")wraca nil, ale NSClassFromString("NSString")nie.
var myVar:NSClassFromString("myClassName")
Stringnie jest klasą; to jest struct
NSClassFromStringwraca nildla 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 AnyClassz pliku String(bez użycia Obj-C). Myślę, że nie chcą, żebyś to robił, ponieważ zasadniczo łamie to system typów.
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)
}
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 .
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)
}
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()
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()
}
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 .
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()
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.
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.
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);
}
Czy rozwiązanie nie jest tak proste?
// Given the app/framework/module named 'MyApp'
let className = String(reflecting: MyClass.self)
// className = "MyApp.MyClass"
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
Wdrożyłem w ten sposób,
if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
ImplementationClass.init()
}
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
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)
}
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)
}
}
Oto dobry przykład:
class EPRocks {
@require init() { }
}
class EPAwesome : EPRocks {
func awesome() -> String { return "Yes"; }
}
var epawesome = EPAwesome.self();
print(epawesome.awesome);