在Swift中如何通过字符串创建类的实例

46

我尝试过多种方法使用字符串创建一个类的实例,但在Swift 3中都无法正常工作。

以下是我尝试过且在Swift 3之前版本中不起作用的解决方案:

- 将类设置为Objective-C类

@objc(customClass)
class customClass {
    ...
}

//Error here: cannot convert value of type 'AnyClass?' to expected argument type 'customClass'
let c: customClass = NSClassFromString("customClass")

- 使用NSString值指定类(使用和不使用@objc属性)

@objc(customClass)
class customClass {
    ...
}

//Error here: cannot convert value of type 'String' to expected argument type 'AnyClass' (aka 'AnyObject.Type')
var className = NSStringFromClass("customClass")

let c: customClass = NSClassFromString(className)

我做的事情不对,但在网上找不到任何解决方案。

如何在Swift 3中使用字符串创建类的实例?


我的问题和你链接的那个问题的区别在于,我正在询问如何在Swift3中实现这一点,因为那个问题的答案在我的Swift3代码中不起作用。 - Danoram
好的,我已经撤回了我的关闭投票。 - par
4个回答

47

你可以尝试这个:

func classFromString(_ className: String) -> AnyClass! {

    /// get namespace
    let namespace = Bundle.main.infoDictionary!["CFBundleExecutable"] as! String

    /// get 'anyClass' with classname and namespace 
    let cls: AnyClass = NSClassFromString("\(namespace).\(className)")!

    // return AnyClass!
    return cls
}

像这样使用函数:

class customClass: UITableView {}   

let myclass = classFromString("customClass") as! UITableView.Type
let instance = myclass.init()

应该是 let instance = myclass.init() 吗? - Danoram
我喜欢这个解决方案!不过我想知道是否有类似于Swift之前版本的解决方案 - 类似于NSClassFromString - Danoram
那非常有趣! - Danoram
1
如果您正在尝试实例化自定义类,则需要在该类内部的初始化器中添加一个required关键字,否则编译器会抛出错误。 - SmileBot
2
注意:如果您的可执行文件名称中有空格,则此方法可能无效。我们没有使用CFBundleExecutable,而是使用了CFBundleName,但是除非我们还将空格替换为下划线,否则它不起作用。在这篇(有些令人困惑的)博客文章中建议这样做:https://medium.com/@maximbilan/ios-objective-c-project-nsclassfromstring-method-for-swift-classes-dbadb721723。文章中的代码有效,但实际上讨论的是使用应用程序名称和目标名称,这与他们的代码不同。 - stuckj
显示剩余2条评论

39

1
太好了!感谢您提供这些信息。 - Danoram
4
别忘了在"MyClassName"的前面加上"MyApp."作为前缀,其中MyApp是你的包名称。否则可能会得到nil值。 - Tulleb
@Tulleb,你能给我一个格式化的例子吗?我们应该像这样添加它“MyAppMyClassName”吗?非常感谢。 - Difeng Chen
Bundle.main.classNamed("MyApp.MyClassName") - Tulleb

9
如果您的项目包含空格,应将空格替换为“_”。 类似如下:
let namespace = (Bundle.main.infoDictionary!["CFBundleExecutable"] as! String).replacingOccurrences(of: " ", with: "_")
let cls = NSClassFromString("\(namespace).\(className)")! as! AnyClass

1
我给这个点赞,因为用下划线替换空格的建议正是我所需要的。虽然“CFBundleExecutable”的位置也可以使用“CFBundleName”。 - Adetunji Mohammed
如果您的目标名称中有“-”,请执行相同的替换 - WalterF

2

类型扩展

您可以从字符串中创建一个具有类型的类,例如:

let loadedClass = try! Bundle.main.class(ofType: MyClass.self)

使用这个简单的扩展:
extension Bundle {
    func `class`<T: AnyObject>(ofType type: T.Type, named name: String? = nil) throws -> T.Type {
        let name = name ?? String(reflecting: type.self)

        guard name.components(separatedBy: ".").count > 1 else { throw ClassLoadError.moduleNotFound }
        guard let loadedClass = Bundle.main.classNamed(name) else { throw ClassLoadError.classNotFound }
        guard let castedClass = loadedClass as? T.Type else { throw ClassLoadError.invalidClassType(loaded: name, expected: String(describing: type)) }

        return castedClass
    }
}

extension Bundle {
    enum ClassLoadError: Error {
        case moduleNotFound
        case classNotFound
        case invalidClassType(loaded: String, expected: String)
    }
}

当然可以使用try catch来捕获错误 ;)


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