Swift язык NSClassFromString

83

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

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

[[NSClassFromString(@"Foo") alloc] init];
Селвин
источник
1
Попробуйте вот так, если пусть ImplementationClass: NSObject.Type = NSClassFromString (className) as? NSObject.Type {ImplementationClass.init ()}
Пушпа Раджа

Ответы:

37

Здесь меньше хакерского решения: 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
}

РЕДАКТИРОВАТЬ

Вы также можете сделать это в чистом 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
Начиная с бета-версии 7 это больше не будет работать. NSStringFromClass теперь будет просто возвращать имя вашего пакета плюс имя класса, разделенные точкой. Значит, вы можете использовать такой код: var appName: String = NSBundle.mainBundle (). ObjectForInfoDictionaryKey ("CFBundleName") как String? пусть classStringName: String = NSStringFromClass (theObject.dynamicType) возвращение classStringName.stringByReplacingOccurrencesOfString (APPNAME +, withString: "", варианты: NSStringCompareOptions.CaseInsensitiveSearch, диапазон: "" ноль)
Эдвин Vermeer
@KevinDelord Я использовал вашу технику в своем коде. Но переменная appName не возвращает правильное значение, если в имени приложения есть пробел. Есть идеи, как это исправить? Пример: «Имя приложения» вместо «Имя_приложения»
Loadex,
Не удается найти функцию countElements. Любая помощь по этому поводу?
C0D3
Вместо этого используйте это для classStringName: let classStringName = "_TtC (appName! .Characters.count) (appName) (className.characters.count) (className)"
C0D3
@Loadex, это не работает, если в вашем имени исполняемого файла есть пробел. Вам также необходимо заменить пробелы подчеркиванием. Это было предложено в этом (несколько сбивающем с толку) сообщении блога: medium.com/@maximbilan/…. Код в сообщении работал, но в самом сообщении говорилось об использовании имени приложения и имени цели, которые отличались от того, что делал их код.
stuckj
57

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

@objc(SubClass)
class SubClass: SuperClass {...}
Клаус
источник
2
NSClassFromString()функции требуется имя, указанное в @objcатрибуции.
eonil 09
1
Удивительно, такая маленькая вещь оказала такое огромное влияние на мое психическое благополучие!
Adrian_H
1
Может ли кто-нибудь показать, как использовать NSClassFromString () после того, как они сделают @objc над именем своего класса. Я просто получаю AnyClass! вернулся
Райан Бобровски
Меня устраивает. Но у меня вопрос, почему @objc(SubClass)работает, а @objc class SubClassнет?
Pyanfield
@pyanfield из документа Swift: «Атрибут objc необязательно принимает один аргумент атрибута, который состоит из идентификатора. Идентификатор указывает имя, которое будет отображаться в Objective-C для сущности, к которой применяется атрибут objc». Итак, разница в том, как наш подкласс Swift получает имя, которое будет видно для Objective C. В @objc class SubClassформе подразумевается, что имя совпадает с именем подкласса . Причем в @objc(SubClass) class SubClassформе указано прямо. Думаю, компилятор по какой-то причине просто не может разобраться сам в первой форме.
voiger
56

Так я инициализирую производный 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()
Ригель Чен
источник
11
Если имя приложения содержит «-», его следует заменить на «_»
Цзитао Сюн
@RigelChen, хорошее решение, спасибо, но если нам просто нужен тип (TestViewController) и мы не хотим инициализировать его каждый раз, что нам делать?
ArgaPK 02
@RyanHeitner хорошее решение, спасибо, но если нам просто нужен тип (TestViewController) и мы не хотим инициализировать его каждый раз, что нам делать?
ArgaPK 02
@argap Создайте синглтон и получите к нему доступ: let viewController = aClass.sharedInstance ()
mahboudz
27

ОБНОВЛЕНИЕ: начиная с бета-версии 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()
Эдвин Вермеер
источник
Это будет работать только для классов NSObject, что касается классов Swift. По умолчанию у них нет метода инициализации, и приведение типов к протоколу кажется не работает, есть идея?
Стефан
Swift 1.2 / XCode 6.3.1 вылетает в моем случае с этой конструкцией
Стефан
Я создал класс отражения с поддержкой NSCoding, Printable, Hashable, Equatable и JSON github.com/evermeer/EVReflection
Эдвин Вермеер,
Упомянутая проблема исправлена ​​в Swift 2.0 ... см. Мой ответ, потому что вам нужно вызвать init .... просто nsobjectype () больше не работает.
Стефан
1
let classString = NSStringFromClass (type (of: self))
Абхишек Таплиял
11

Это почти то же самое

func NSClassFromString(_ aClassName: String!) -> AnyClass!

Проверьте этот документ:

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

Габух
источник
5
Эта функция работает только с NSClassклассами, а не с классами Swift. NSClassFromString("String")возвращается nil, но NSClassFromString("NSString")не возвращается .
Чезари Войчик
Я не стою перед компьютером ... но вы можете попробовать: var myVar:NSClassFromString("myClassName")
gabuh
2
@CezaryWojcik: ps String- это не класс; это структура
newacct 05
2
@newacct Да, ты прав, моя беда. Но все равно NSClassFromStringвозвращается nilдля всех классов Swift.
Чезари Войчик
Если класс аннотирован @objc или наследуется от NSObject, тогда он использует объектную систему Objective-C и участвует в NSClassFromString, замене методов и т. Д. Однако, если это не так, то класс является внутренним для вашего кода Swift и не Я не использую ту же объектную систему. Он не был полностью описан, но объектная система Swift, похоже, имеет менее позднюю привязку, чем Objective-C.
Bill
9

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

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

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

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

Султан
источник
1
Я думаю, что этот ответ, хотя и не о NSClassFromString, является лучшим здесь для предоставления Swift-ориентированного способа инициализации динамического объекта. Спасибо, что поделился!
Мэтт Лонг,
Спасибо @Sulthan и Мэтту за то, что они подчеркнули, почему этот ответ должен быть на высоте!
Илиас Карим
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)
}
Дэмиен Ромито
источник
6

Это даст вам имя класса, который вы хотите создать. Затем вы можете использовать ответ Эдвинса для создания экземпляра нового объекта вашего класса.

Начиная с бета 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 .

Клаас
источник
«Затем вы можете использовать ответ Эдвинса для создания экземпляра нового объекта вашего класса». Не думаю, что исх. ответьте на работу для PureSwiftClass. например, по умолчанию этот класс не имеет метода инициализации.
Стефан
6

В 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)
}
Иди отпусти
источник
5

xcode 7 бета 5:

class MyClass {
    required init() { print("Hi!") }
}
if let classObject = NSClassFromString("YOURAPPNAME.MyClass") as? MyClass.Type {
    let object = classObject.init()
}
Декабрь
источник
3

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

let classString = NSStringFromClass(TestViewController.self)

или же

let classString = NSStringFromClass(TestViewController.classForCoder())

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

let vcClass = NSClassFromString(classString) as! UIViewController.Type
let viewController = vcClass.init()
Бинглин
источник
3

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

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

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

Стивен Дарлингтон
источник
Даже с Swift 1.2 / XCode 6.3.1 это кажется невозможным, или вы нашли решение?
Стефан
2

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

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

    class Test : NSObject {}
    var test1 = Test()
    var test2 = test1.dynamicType.alloc()
себ
источник
Это не создает экземпляр из имени класса.
Стефан
Из вашего ответа неясно, похоже, он не работает с чистыми классами Swift, верно?
Стефан
2

В Swift 2.0 (протестирован в бета-версии 2 Xcode 7) это работает так:

protocol Init {
  init()
}

var type = NSClassFromString(className) as? Init.Type
let obj = type!.init()

Конечно, тип NSClassFromStringдолжен реализовывать этот протокол инициализации.

Я ожидаю, что это ясно, classNameэто строка, содержащая имя среды выполнения Obj-C класса, которое по умолчанию НЕ просто "Foo", но это обсуждение, ИМХО, не является основной темой вашего вопроса.

Вам нужен этот протокол, потому что по умолчанию все классы Swift не реализуют initметод.

Стефан
источник
Если у вас есть классы Pur Swift, см. Другие мои вопросы: stackoverflow.com/questions/30111659
Стефан
2

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

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

Рори
источник
2

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

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

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

// className = "MyApp.MyClass"
Марк А. Донохью
источник
3
исходный вопрос заключался в том, чтобы класс из строки, а не из строки из класса.
Райан
1

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

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

апоколипс
источник
1

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

let className: String = String(ControllerName.classForCoder())
print(className)
Химаншу
источник
1

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

if let ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{
   ImplementationClass.init()
}
Пушпа Раджа
источник
1

Swift 5 , простой в использовании, благодаря @Ondrej Rafaj's

  • Исходный код:

    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
    
       }
    
    }
    
  • Звоните так:

    guard let ctrl = "ViewCtrl".controller else {
        return
    }
    //  ctrl do sth
    
черная жемчужина
источник
0

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

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)
}
Тим
источник
0

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)
    }
}
SeRG1k
источник
Спасибо за этот фрагмент кода, который может оказать некоторую немедленную помощь. Надлежащее объяснение было бы значительно улучшить свою долгосрочную ценность , показывая , почему это хорошее решение проблемы, и сделает его более полезным для читателей будущих с другими подобными вопросами. Пожалуйста , измените свой ответ , чтобы добавить некоторые объяснения, в том числе допущений , которые вы сделали.
iBug
-6

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

class EPRocks { 
    @require init() { } 
}

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

var epawesome = EPAwesome.self(); 
print(epawesome.awesome);
ЗлоПингвин
источник
1
Это не то, о чем спрашивает.
ThomasW 02