Swift язык NSClassFromString


Как добиться отражение на языке Swift?

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

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

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!

регистрация этого документа:

https://developer.apple.com/library/prerelease/ios/documentation/Cocoa/Reference/Foundation/Miscellaneous/Foundation_Functions/#//apple_ref/c/func/NSClassFromString

мне удалось создать экземпляр объекта динамически

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 = ""
}

работает для моего использования

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

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"

пример перехода на страницу, показанный здесь, надежда может помочь вам!

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

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

class EPRocks { 
    @require init() { } 
}

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

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