Швидка мова NSClassFromString


83

Як досягти роздумів на швидкій мові?

Як я можу створити екземпляр класу

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

1
Спробуйте так, якщо нехай ImplementationClass: NSObject.Type = NSClassFromString (className) як? 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

1
Дивовижно, така дрібниця з таким величезним впливом на моє психічне благополуччя!
Adrian_H

може хтось показати, як використовувати NSClassFromString () після того, як вони виконують річ @objc над назвою класу. Я просто отримую AnyClass! повернувся
Райан Бобровський

Це працює для мене. Але у мене питання про те, чому @objc(SubClass)працює, а @objc class SubClassні?
pyanfield

@pyanfield із документа Swift: "Атрибут objc необов'язково приймає один аргумент атрибута, який складається з ідентифікатора. Ідентифікатор вказує ім'я, яке буде піддано Objective-C для сутності, до якої застосовується атрибут objc." Отже, різниця полягає в тому, як наш підклас Swift отримує ім’я, яке буде видно для Цілі C. У @objc class SubClassформі ім’я передбачається таким самим, як ім’я 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
Якщо назва програми містить "-", її слід замінити на "_"
Zitao Xiong

@RigelChen хороше рішення, дякую, але якщо нам просто потрібен тип (TestViewController) і не хочемо його ініціалізувати щоразу, то що нам робити?
ArgaPK

@RyanHeitner приємне рішення, дякую, але якщо нам просто потрібен тип (TestViewController) і не хочемо щоразу його ініціалізувати, що нам робити?
ArgaPK

@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. За замовчуванням вони не мають жодного методу init, і видача протоколу, здається, не працює, будь-яка ідея?
Стефан

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")не повертається .
Cezary Wojcik

Я не перед комп’ютером ... але ви можете спробувати: var myVar:NSClassFromString("myClassName")
gabuh

2
@CezaryWojcik: ps String- це не клас; це структура
newacct

2
@newacct D'oh, ти маєш рацію, мій поганий. Але NSClassFromStringповернення nilдля всіх класів Swift також у будь-якому випадку.
Cezary Wojcik

Якщо клас анотується @objc або успадковується від NSObject, тоді він використовує систему об'єктів Objective-C і бере участь у NSClassFromString, методі зміни розміру тощо. Однак, якщо цього немає, то клас є внутрішнім до вашого коду Swift і не не використовую ту саму систему об'єктів. Це не було повністю описано, але система об'єктів Свіфта, здається, менш пізньо обмежена, ніж Objective-C.
Білл

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-орієнтованого способу здійснення динамічної ініціалізації об'єкта. Дякую, що поділились!
Matt Long

Дякуємо також @Sulthan та Matt, які підкреслили, чому ця відповідь повинна бути на висоті!
Іліас Карім

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тип Свіфта .


"Тоді ви можете використовувати відповідь Edwins для створення нового об’єкта вашого класу." Я не думаю, що реф. робота відповідей для PureSwiftClass. наприклад, за замовчуванням цей клас не має методу init.
Stephan

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 beta 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 немає способу створити її екземпляр. Ви можете використовувати міст до Цілі 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 (протестовано в бета-версії Xcode 7) це працює так:

protocol Init {
  init()
}

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

Точно тип, що походить NSClassFromString , повинен реалізувати цей протокол ініціювання.

Я сподіваюся, це зрозуміло, className це рядок, що містить ім'я середовища виконання класу Obj-C, яке за замовчуванням НЕ просто "Foo", але це обговорення IMHO не є основною темою вашого запитання.

Вам потрібен цей протокол, оскільки за замовчуванням усі класи Swift не реалізують initметод.


Якщо у вас є курси 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

Я використовую різні цілі, і в цьому випадку швидкий клас не знайдений. Вам слід замінити 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

Я реалізував так,

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

Свіфт3 +

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

Дякуємо за цей фрагмент коду, який може надати обмежену негайну допомогу. Належне пояснення було б значно поліпшити свою довгострокову цінність , показуючи , чому це хороше рішення проблеми, і зробить його більш корисним для читачів майбутніх з іншими подібними питаннями. Будь ласка, відредагуйте свою відповідь, щоб додати пояснення, включаючи припущення, які ви зробили.
iBug

-6

Ось хороший приклад:

class EPRocks { 
    @require init() { } 
}

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

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

1
Це не робить того, про що запитує запитання.
ThomasW
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.