Быстрый язык NSClassFromString

Как достичь reflection в языке Swift?

Как я могу создать экземпляр класса

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

Ответ 1

Меньше хакерских решений здесь: 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);
}

Надеюсь, это поможет кому-то!

Ответ 2

Вы должны положить @objc(SwiftClassName) над своим быстрым классом.
Например:

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

Ответ 3

Так я инициализирую вывод 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()

Ответ 4

UPDATE: Начиная с бета-версии 6 NSStringFromClass вернет имя вашего пула плюс имя класса, разделенное точкой. Итак, это будет что-то вроде MyApp.MyClass

У классов Swift будет построенное внутреннее имя, которое состоит из следующих частей:

  • Он начнется с _TtC,
  • а затем число, которое является длиной имени вашего приложения,
  • а затем ваше имя приложения,
  • содержащее число, которое является длиной имени вашего класса,
  • а затем ваше имя класса.

Итак, ваше имя класса будет чем-то вроде _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()

Ответ 6

Я смог динамически создавать объект

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

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

Я не нашел способ создать AnyClass из String (без использования Obj-C). Я думаю, они не хотят, чтобы вы это делали, потому что это в основном разрушает систему типов.

Ответ 7

Для 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)
}

Ответ 8

Это даст вам имя класса, который вы хотите создать. Затем вы можете использовать ответ 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.

Ответ 9

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

Ответ 10

xcode 7 beta 5:

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

Ответ 11

Я думаю, что я прав, говоря, что вы не можете, по крайней мере, не с текущей бета-версией (2). Надеюсь, это то, что изменится в будущих версиях.

Вы можете использовать NSClassFromString для получения переменной типа AnyClass, но, похоже, в Swift нет возможности ее создания. Вы можете использовать мост для Objective C и сделать его там или - если он работает в вашем случае - вернуться к использованию оператора switch.

Ответ 12

По-видимому, невозможно (уже) создавать экземпляр объекта в Swift, когда имя класса известно только во время выполнения. Оболочка Objective-C возможна для подклассов NSObject.

По крайней мере, вы можете создать экземпляр объекта того же класса, что и другой объект, заданный во время выполнения без оболочки Objective-C (используя xCode Version 6.2 - 6C107a):

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

Ответ 13

В 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.

Ответ 14

Похоже, правильное заклинание будет...

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 в отдельное закрытие, которое компилируется отлично.

Ответ 15

Я использую разные цели, и в этом случае класс 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);
}

Ответ 16

строка из класса

let classString = NSStringFromClass(TestViewController.self)

или

let classString = NSStringFromClass(TestViewController.classForCoder())

инициализировать класс UIViewController из строки:

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

Ответ 17

Также в 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 = ""
}

Работает в моем случае использования

Ответ 18

Попробуйте это.

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

Ответ 19

Я реализовал вот так,

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

Ответ 20

Я использую эту категорию для 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()
}

Ответ 21

Разве это не так просто?

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

// className = "MyApp.MyClass"

Ответ 22

Вот хороший пример:

class EPRocks { 
    @require init() { } 
}

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

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