Swift язык NSClassFromString
Как добиться отражение на языке Swift?
Как я могу создать экземпляр класса
[[NSClassFromString(@"Foo") alloc] init];
24 ответа:
менее хаки решение здесь:https://stackoverflow.com/a/32265287/308315
обратите внимание, что классы Swift теперь находятся в пространстве имен, поэтому вместо "MyViewController" это будет "AppName.MyViewController"
устарел с XCode6-beta 6/7
решение разработано с использованием XCode6-beta 3
благодаря ответу Эдвина Вермеера мне удалось построить что-то для создания экземпляров Swift-классов в класс Obj-C делая это:
// 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 }
EDIT
вы также можете сделать это в чистом 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); }
Я надеюсь, что это поможет кому-то !
Так я инициализирую производный UIViewController по имени класса
var className = "YourAppName.TestViewController" let aClass = NSClassFromString(className) as! UIViewController.Type let viewController = aClass()
дополнительная информация здесь
в iOS 9
var className = "YourAppName.TestViewController" let aClass = NSClassFromString(className) as! UIViewController.Type let viewController = aClass.init()
вы должны поставить
@objc(SwiftClassName)
выше вашего класса swift.
Например:@objc(SubClass) class SubClass: SuperClass {...}
обновление: начиная с бета-версии 6 NSStringFromClass вернет ваше имя пакета плюс имя класса, разделенное точкой. Так что это будет что-то вроде MyApp.MyClass
Swift классы будут иметь построенное внутреннее имя, которое состоит из следующих частей:
- он начнется с _TtC,
- затем число, которое является длиной вашего имени заявления,
- затем следует имя вашего приложения,
- folowed рядом это длина вашего имени класса,
- затем следует имя класса.
Так что ваше имя класса будет что-то вроде _TtC5MyApp7MyClass
вы можете получить это имя в виде строки, выполнив:
var classString = NSStringFromClass(self.dynamicType)
обновление в Swift 3 это изменилось на:
var classString = NSStringFromClass(type(of: self))
используя эту строку, вы можете создать экземпляр вашего класса Swift, выполнив:
var anyobjectype : AnyObject.Type = NSClassFromString(classString) var nsobjectype : NSObject.Type = anyobjectype as NSObject.Type var rec: AnyObject = nsobjectype()
это почти то же самое
func NSClassFromString(_ aClassName: String!) -> AnyClass!
регистрация этого документа:
мне удалось создать экземпляр объекта динамически
var clazz: NSObject.Type = TestObject.self var instance : NSObject = clazz() if let testObject = instance as? TestObject { println("yes!") }
Я не нашел способа создать
AnyClass
СString
(без использования Obj-C). Я думаю, что они не хотят, чтобы вы это делали, потому что это в основном нарушает систему типов.
для swift2 я создал очень простое расширение, чтобы сделать это быстрее 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() } }
в моем случае, я делаю это, чтобы загрузить ViewController я хочу:
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
_stdlib_getTypeName
получает искаженное имя типа переменной. Вставьте это в пустую игровую площадку: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))")
выход:
TypeName0 = NSString TypeName1 = _TtC13__lldb_expr_014PureSwiftClass TypeName2 = _TtSi TypeName3 = _TtSS
запись в блоге Эвана Свика помогает расшифровать эти строки:http://www.eswick.com/2014/06/inside-swift/
например,
_TtSi
означает внутренний СвифтInt
тип.
в Swift 2.0 (тестируется в 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) }
Xcode 7 beta 5:
class MyClass { required init() { print("Hi!") } } if let classObject = NSClassFromString("YOURAPPNAME.MyClass") as? MyClass.Type { let object = classObject.init() }
строка из класса
let classString = NSStringFromClass(TestViewController.self)
или
let classString = NSStringFromClass(TestViewController.classForCoder())
init класс UIViewController из строки:
let vcClass = NSClassFromString(classString) as! UIViewController.Type let viewController = vcClass.init()
Я думаю, что я прав, говоря, что вы не можете, по крайней мере, не с текущей бета (2). Надеюсь, это то, что изменится в будущих версиях.
можно использовать
NSClassFromString
чтобы получить переменную типаAnyClass
но, похоже, в Swift нет способа создать его экземпляр. Вы можете использовать мост к объекту C и сделать это там или-если это работает в вашем случае -- вернитесь к использованию оператора switch.
по-видимому, невозможно (больше) создать экземпляр объекта в Swift, когда имя класса известно только во время выполнения. Цель-с оболочкой можно на подклассы NSObject.
по крайней мере, вы можете создать экземпляр объекта того же класса, что и другой объект, заданный во время выполнения без Objective-C wrapper (используя xCode версии 6.2 - 6C107a):
class Test : NSObject {} var test1 = Test() var test2 = test1.dynamicType.alloc()
в Swift 2.0 (тестируется в beta2 Xcode 7) он работает следующим образом:
protocol Init { init() } var type = NSClassFromString(className) as? Init.Type let obj = type!.init()
наверняка тип исходит от
NSClassFromString
должны реализовать этот протокол инициализации.Я думаю, что это ясно,
className
- это строка, содержащая имя времени выполнения Obj-C класса, который по умолчанию не просто "Foo", но это обсуждение IMHO не является основной темой вашего вопроса.вам нужен этот протокол, потому что по умолчанию все классы Swift не реализуют
init
метод.
похоже, правильное заклинание будет...
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 }
...где "p "означает" упакованный " – отдельный вопрос.
но критическое приведение от AnyClass к T в настоящее время вызывает сбой компилятора, поэтому в то же время необходимо перебить инициализацию k в отдельное закрытие, которое компилируется нормально.
Я использую разные цели, и в этом случае swift класс не найден. Вы должны заменить CFBundleName на CFBundleExecutable. Я также исправил предупреждения:
- (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); }
также в Swift 2.0 (возможно раньше?) Вы можете получить доступ к типу непосредственно с
dynamicType
свойстват. е.
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)
выход
aUser: User = { name = "Tom" } bUser: User = { name = "" }
работает для моего использования
я реализовал вот так,
if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{ ImplementationClass.init() }
Я использую эту категорию для 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 } }
польза будет:
func getViewController(fromString: String) -> UIViewController? { guard let viewController: UIViewController.Type = "MyViewController".converToClass() else { return nil } return viewController.init() }
разве решение не так просто, как это?
// Given the app/framework/module named 'MyApp' let className = String(reflecting: MyClass.self) // className = "MyApp.MyClass"
пример перехода на страницу, показанный здесь, надежда может помочь вам!
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) }