Как достичь reflection в языке Swift?
Как я могу создать экземпляр класса
[[NSClassFromString(@"Foo") alloc] init];
Как достичь reflection в языке Swift?
Как я могу создать экземпляр класса
[[NSClassFromString(@"Foo") alloc] init];
Меньше хакерских решений здесь: fooobar.com/questions/87858/...
Обратите внимание, что классы 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
}
ИЗМЕНИТЬ
Вы также можете сделать это в чистом 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);
}
Надеюсь, это поможет кому-то!
Вы должны положить @objc(SwiftClassName)
над своим быстрым классом.
Например:
@objc(SubClass)
class SubClass: SuperClass {...}
Так я инициализирую вывод 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()
UPDATE: Начиная с бета-версии 6 NSStringFromClass вернет имя вашего пула плюс имя класса, разделенное точкой. Итак, это будет что-то вроде MyApp.MyClass
У классов Swift будет построенное внутреннее имя, которое состоит из следующих частей:
Итак, ваше имя класса будет чем-то вроде _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)
}
Это даст вам имя класса, который вы хотите создать. Затем вы можете использовать ответ Edwins, чтобы создать новый объект своего класса.
Начиная с бета-версии 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
Запись блога Ewan Swick помогает расшифровать эти строки: http://www.eswick.com/2014/06/inside-swift/
например. _TtSi
обозначает внутренний тип Swift Int
.
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()
}
Я думаю, что я прав, говоря, что вы не можете, по крайней мере, не с текущей бета-версией (2). Надеюсь, это то, что изменится в будущих версиях.
Вы можете использовать NSClassFromString
для получения переменной типа AnyClass
, но, похоже, в Swift нет возможности ее создания. Вы можете использовать мост для Objective C и сделать его там или - если он работает в вашем случае - вернуться к использованию оператора switch.
По-видимому, невозможно (уже) создавать экземпляр объекта в Swift, когда имя класса известно только во время выполнения. Оболочка Objective-C возможна для подклассов NSObject.
По крайней мере, вы можете создать экземпляр объекта того же класса, что и другой объект, заданный во время выполнения без оболочки Objective-C (используя xCode Version 6.2 - 6C107a):
class Test : NSObject {}
var test1 = Test()
var test2 = test1.dynamicType.alloc()
В Swift 2.0 (тестируется в бета2 Xcode 7) он работает следующим образом:
protocol Init {
init()
}
var type = NSClassFromString(className) as? Init.Type
let obj = type!.init()
Конечно, тип, исходящий от NSClassFromString
, должен реализовать этот init-протокол.
Я ожидаю, что это ясно, 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);
}
строка из класса
let classString = NSStringFromClass(TestViewController.self)
или
let classString = NSStringFromClass(TestViewController.classForCoder())
инициализировать класс UIViewController из строки:
let vcClass = NSClassFromString(classString) as! UIViewController.Type
let viewController = vcClass.init()
Также в 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 = ""
}
Работает в моем случае использования
Попробуйте это.
let className: String = String(ControllerName.classForCoder())
print(className)
Я реализовал вот так,
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"
Вот хороший пример:
class EPRocks {
@require init() { }
}
class EPAwesome : EPRocks {
func awesome() -> String { return "Yes"; }
}
var epawesome = EPAwesome.self();
print(epawesome.awesome);