Swift语言NSClassFromString


83

如何在Swift语言中实现反射

我如何实例化课程

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

1
尝试这样,如果让ImplementationClass:NSObject.Type = NSClassFromString(className)设为?NSObject.Type {ImplementationClass.init()}
Pushpa Raja

Answers:


37

这里的黑客解决方案更少:https : //stackoverflow.com/a/32265287/308315

请注意,Swift类现在已命名为名称空间,因此它不是“ MyViewController”,而是“ AppName.MyViewController”


从XCode6-beta 6/7开始不推荐使用

使用XCode6-beta 3开发的解决方案

多亏了Edwin Vermeer的回答,我能够通过执行以下操作来构建一些将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
从beta 7开始,它将不再起作用.NSStringFromClass现在将仅返回您的包名称和类名称,并用点分隔。因此,您可以使用像这样的代码:var appName:String = NSBundle.mainBundle()。objectForInfoDictionaryKey(“ CFBundleName”)作为String?让classStringName:字符串= NSStringFromClass(theObject.dynamicType)返回classStringName.stringByReplacingOccurrencesOfString(appName +“。”,withString:“”,选项:NSStringCompareOptions.CaseInsensitiveSearch,范围:无)
Edwin Vermeer

@KevinDelord我在代码中使用了您的技术。但是,当应用程序名称中包含空格时,appName变量将不会返回正确的值。知道如何解决吗?例如,用“应用名称”代替“应用名称”
Loadex 2015年

找不到countElements函数。有什么帮助吗?
C0D3

改用它作为classStringName:let classStringName =“ _TtC(appName!.characters.count)(appName)(className.characters.count)(className)”
C0D3 2016年

@Loadex,如果您的可执行文件名称中包含空格,则此功能无效。您还需要用下划线替换空格。在这个(有些令人困惑的)博客文章中提出了建议:medium.com/@maximbilan/…。帖子中的代码有效,但是实际的帖子中谈到了使用appname和目标名称,这与他们的代码不同。
stickj

57

您必须@objc(SwiftClassName)超越快速班级。
喜欢:

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

2
NSClassFromString()函数需要由@objc属性指定的名称。
尼尔2014年

1
太神奇了,这件小事对我的心理健康造成了巨大影响!
Adrian_H 2015年

有人在类名称上方执行@objc操作后,可以显示出如何使用NSClassFromString()吗?我刚得到AnyClass!返回
Ryan Bobrowski

这个对我有用。但是我有一个关于为什么@objc(SubClass)有效的问题,但@objc class SubClass不是吗?
pyanfield

来自Swift文档的@pyanfield:“ objc属性可以选择接受单个属性参数,该参数由标识符组成。该标识符指定了objc属性适用于的实体要公开给Objective-C的名称。” 因此,不同之处在于我们的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 '18年

@RyanHeitner一个不错的解决方案,谢谢,但是如果我们只需要类型(TestViewController)并且不想每次都初始化它,那我们该怎么办?
ArgaPK '18年

@argap创建一个单例并访问它:let viewController = aClass.sharedInstance()
mahboudz

27

更新:从beta 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在我的情况下崩溃
Stephan

我创建了一个反射类,它支持NSCoding,Printable,Hashable,Equatable和JSON github.com/evermeer/EVReflection
Edwin Vermeer

提到的问题已在Swift 2.0中解决。。。请参阅我的回答,因为您必须调用init...。只是nsobjectype()不再正确。
Stephan

1
let classString = NSStringFromClass(type(of:self))
Abhishek Thapliyal

11

5
该函数仅适用于NSClass类,而不适用于Swift类。NSClassFromString("String")返回nil,但NSClassFromString("NSString")不返回。
Cezary Wojcik 2014年

我不在电脑前...但是您可以尝试: var myVar:NSClassFromString("myClassName")
gabuh 2014年

2
@CezaryWojcik:psString不是一个类;这是一个结构
newacct 2014年

2
@newacct D'哦,你是对的,我不好。但是无论如何,所有Swift类都NSClassFromString将返回nil
Cezary Wojcik 2014年

如果一个类带有@objc注释或从NSObject继承,则它使用Objective-C对象系统并参与NSClassFromString,方法转换等。但是,如果不是,则该类在Swift代码内部,并且不会不要使用相同的对象系统。尚未对其进行充分描述,但是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为中心的动态对象初始化方法的最佳答案。感谢分享!
马特·朗

感谢@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

这将为您提供要实例化的类的名称。然后,您可以使用Edwins答案实例化类的新对象。

从beta 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类型。


“然后,您可以使用Edwins答案实例化类的一个新对象。” 我不认为裁判。回答PureSwiftClass的工作。例如,默认情况下,此类没有init方法。
斯蒂芬

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

显然,当仅在运行时知道类的名称时,不可能(再)在Swift中实例化对象。NSObject的子类可以使用Objective-C包装器。

至少您可以实例化与运行时给定的另一个对象相同类的对象,而无需使用Objective-C包装器(使用xCode版本6.2-6C107a):

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

这不会从类名称创建实例。
斯蒂芬

从您的答案尚不清楚,似乎无法在纯Swift类上使用,对吗?
斯蒂芬

2

在Swift 2.0(在Xcode 7的beta2中测试)中,它的工作方式如下:

protocol Init {
  init()
}

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

确保来自的类型NSClassFromString必须实现此init协议。

我希望这很清楚 className是一个包含该类的Obj-C运行时名称的String,默认情况下它不仅是“ Foo”,而且此讨论不是恕我直言,不是您问题的主要主题。

您需要此协议,因为默认情况下,所有Swift类都不实现init方法。


如果您有普通的Swift类,请参阅我的其他问题:stackoverflow.com/questions/30111659
Stephan

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

迅捷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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.