有没有办法在Swift中获得_stable_泛型类名?

时间:2015-01-30 09:07:55

标签: swift

在Swift中获取类的名称非常简单:

import Foundation

class Gen<T> {
    init() { }
}

func printName(obj: AnyObject) {
    dump(NSStringFromClass(obj.dynamicType))
}

let a: Gen<String> = Gen()
let b: Gen<Int> = Gen()
printName(a)
printName(b)

但是,如果运行上述代码两次,则不会产生相同的结果。

那么有没有办法以稳定的方式实现printName(获取专门的通用类的名称)?那就是代码的多次运行为同一个类打印相同的字符串?

我正在使用最新的Xcode测试版。

一些额外要求:

  • 将传递给printName的班级数量很高。
  • 可能有多个通用类,但不是很多。

5 个答案:

答案 0 :(得分:7)

很抱歉让人失望,但这似乎不太可能,除非您积极编写新的编译器。

检查此source。作者很好地描述了它:Note that the isa pointer of the two objects is not identical, even though both are an instance of WrapperClass. Evidently, a specialization of a generic class is a separate class at runtime.

我还在玩@objc,但这只是改变了类的命名空间,但显然对类名没有影响。

答案 1 :(得分:6)

从Xcode 6.3 beta 1开始的新功能

好消息!从6.3开始你可以做到:

toString(Gen<Int>())

然后打印:Gen<Swift.Int>

OLD答案:

如果范围可以缩减为T:AnyObject,那么您可以按如下方式实现它:

class Gen<T: AnyObject> {
    var className: String {
        return "Gen<\(NSStringFromClass(T.self))>"
    }
}

答案 2 :(得分:2)

这将打印Gen,并且就我所知的任何课程都适用:

class Gen<T> {
    init() { }
}

func printName(obj: AnyObject) {
    let desc = reflect(obj).summary

    let scanner = NSScanner(string: desc)

    var name: NSString?

    scanner.scanUpToString(".", intoString: nil)
    scanner.scanString(".", intoString: nil)
    scanner.scanUpToString("", intoString: &name)

    if let n = name {
        println(n)
    }
}

但是,它不会为您提供专门的类型 - 例如Gen<String>Gen<Int> - 我现在只在在线REPL上测试它,因此结果可能会在项目或游乐场。

您是否需要它来提供专门的类型,或者类名是否足够?

编辑:我将@ relejo的答案和我的答案结合起来,使用协议和他建议的计算属性给你专门的类型:

protocol GenericClassName {
    var className: String { get }
}

class Gen<T: AnyObject>: GenericClassName {
    init() { }
    var className: String {
        return NSStringFromClass(T.self)
    }
}

func printName<U: GenericClassName>(obj: U) {
    let desc = reflect(obj).summary

    let scanner = NSScanner(string: desc)

    var name: NSString?

    scanner.scanUpToString(".", intoString: nil)
    scanner.scanString(".", intoString: nil)
    scanner.scanUpToString("", intoString: &name)

    if let n = name {
        println(n + "<\(obj.className)>")
    }
}

let gen1 = Gen<NSString>()
let gen2 = Gen<NSNumber>()

printName(gen1) // Outputs: Gen<NSString>
printName(gen2) // Outputs: Gen<NSNumber>

再次编辑:第二个想法,如果你要为每个泛型类添加一个计算属性(你说你只有3-4个,对吧?)那么就让它看起来像这样:

class Gen<T: AnyObject> {
    init() { }
    var className: String {
        return (reflect(self).summary.componentsSeparatedByString(".").last ?? "") + "<\(NSStringFromClass(T.self))>"
    }
}

现在您可以将类名称作为字符串获取:

let gen1 = Gen<NSString>()
println(gen1.className)    // Output: Gen<NSString>

这样你就不会搞乱协议或全局函数或NSScanner(这可能很慢......)。

答案 3 :(得分:0)

建立最佳答案,如果你只想要实际的类名,不包括&#34;模块&#34;它只是被#34;分开。&#34;并获取该数组的最后一个元素。我需要获取通用名称以编写通用核心数据函数,以便能够调用 NSManagedObject.insertObjectIntoContext ,这样我就不会调用 NSEntityDescription.insertNewObject。 .. 到处都是。

代码:

func insertNewObjectIntoContext<E>(moc: NSManagedObjectContext) -> E {
    return NSEntityDescription.insertNewObjectForEntityForName(toString(E).componentsSeparatedByString(".").last!, inManagedObjectContext: moc) as! E
}

这个问题的关注部分是:

toString(E).componentsSeparatedByString(".").last!  // returns E - rather than ModuleName.E

答案 4 :(得分:-1)

也许这可能暗示您正在寻找的解决方案:

class Gen<T> {
    init() { }
}

enum ClassID: String {
    case GenInt = "Gen<Int>"
    case GenString = "Gen<String>"

    func classFromID(stringID: String) -> AnyClass? {
        if let classID = ClassID(rawValue: stringID) {
            switch classID {
            case .GenInt:
                return Gen<Int>().dynamicType
            case .GenString:
                return Gen<String>().dynamicType
            }
        }
        return nil
    }
}

简而言之,构建自己的类ID,以及相关函数来恢复它们。您保留安全性,因为如果某个类不是已知的类,则不会使用它。