Swift语言中的NSClassFromString

89

如何在Swift语言中实现反射(reflection)

如何实例化一个类?

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

1
尝试这样写: 如果让 ImplementationClass: NSObject.Type = NSClassFromString(className) as? NSObject.Type{ ImplementationClass.init() } - Pushpa Raja
25个回答

59
你必须在你的 Swift 类上方添加 @objc(SwiftClassName)
例如:
@objc(SubClass)
class SubClass: SuperClass {...}

2
NSClassFromString() 函数需要由 @objc 属性指定的名称。 - eonil
1
太神奇了,这么一件小事情却对我的心理健康产生了如此巨大的影响! - Adrian_H
1
有人能展示一下如何在类名上面加上@objc之后使用NSClassFromString()吗?我只是得到了返回的AnyClass!。 - Ryan Bobrowski
它对我有效。但是我有一个关于为什么@objc(SubClass)有效,而@objc class SubClass无效的问题? - pyanfield
1
来自 Swift 文档的 @pyanfield: "objc" 属性可选地接受单个属性参数,该参数由标识符组成。该标识符指定应为应用到 objc 属性的实体在 Objective-C 中公开的名称。因此,不同之处在于我们的 Swift 子类获取其可见于 Objective-C 的名称的方式。在 @objc class SubClass 形式中,名称被暗示为与 SubClass 名称相同。而在 @objc(SubClass) class SubClass 形式中,则直接指定。我想编译器无法自行解决第一种形式的问题。 - voiger

59

以下是我按照类名初始化派生的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

37

这里有一个更好的解决方案:https://dev59.com/AWAg5IYBdhLWcg3wDHU3#32265287

请注意,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”)as String? let classStringName:String = NSStringFromClass(theObject.dynamicType) return classStringName.stringByReplacingOccurrencesOfString(appName +“。”,withString:“”,options:NSStringCompareOptions.CaseInsensitiveSearch,range:nil) - Edwin Vermeer
@KevinDelord 我在我的代码中使用了你的技巧。但是当应用程序名称中有空格时,appName变量不会返回正确的值。有什么办法可以修复这个问题吗?例如,将"App Name"改为"App_Name"。 - Loadex
找不到countElements函数,这个怎么办? - C0D3
使用以下代码替换classStringName: let classStringName = "_TtC(appName!.characters.count)(appName)(className.characters.count)(className)" - C0D3
@Loadex,如果你的可执行文件名中带有空格,这种方法是行不通的。你还需要用下划线来替换空格。在这篇(有点令人困惑的)博客文章中提出了建议:medium.com/@maximbilan/…. 文章中的代码可以工作,但实际上,文章讨论的是使用应用程序名称和目标名称,与他们的代码所做的不同。 - stuckj
非常感谢您提醒Swift类是有命名空间的。因为您的提醒,我只花了15分钟而不是几个小时来解决问题,也没有让自己太过紧张 :D - Lord Zsolt

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方法,并且对协议的类型转换似乎不起作用,有什么想法吗? - Stephan
在我的情况下,使用这个结构体会导致Swift 1.2 / XCode 6.3.1崩溃。 - Stephan
我创建了一个反射类,支持NSCoding、Printable、Hashable、Equatable和JSON https://github.com/evermeer/EVReflection - Edwin Vermeer
提到的问题已在Swift 2.0中得到解决...请查看我的答案,因为您必须调用init...只有nsobjectype()不再正确。 - Stephan
1
让 classString = NSStringFromClass(type(of: self)) - Abhishek Thapliyal
显示剩余2条评论

11

5
该函数只适用于NSClass类,而不适用于Swift类。 NSClassFromString(“String”)返回nil,但NSClassFromString(“NSString”)不会返回nil - Cezary Wojcik
我现在不在电脑前...但你可以尝试使用以下代码:var myVar:NSClassFromString("myClassName") - gabuh
2
@CezaryWojcik:顺便说一下,String不是一个类,它是一个结构体。 - newacct
2
@newacct 对不起,我的错。但是,无论如何NSClassFromString也会对所有的Swift类返回nil - Cezary Wojcik
如果一个类被注释为@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!")
}

我还没有找到一种不使用Obj-C的方式来从一个字符串中创建任何类(AnyClass)。我认为他们不希望你这样做,因为这基本上会破坏类型系统。

1
我认为这个答案虽然与NSClassFromString无关,但是它是在这里提供一种以Swift为中心的动态对象初始化方式的最佳答案。感谢分享! - Matt Long
感谢 @Sulthan 和 Matt,他们强调这个回答为什么应该排在顶部! - Ilias Karim

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

6
这将为您获取要实例化的类名称。然后,您可以使用Edwin的答案来实例化一个新对象。
从beta 6开始,_stdlib_getTypeName获取变量的编码类型名称。将其粘贴到空白playground中:
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类型。


然后您可以使用Edwin的答案来实例化您的类的新对象。我认为参考答案对于PureSwiftClass不起作用,例如默认情况下此类没有init方法。 - Stephan

5

xcode 7 beta 5:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接