如何在Swift中打印变量的类型或类?

365

有没有一种方法可以在Swift中打印变量的运行时类型?例如:

var now = NSDate()
var soon = now.dateByAddingTimeInterval(5.0)

println("\(now.dynamicType)") 
// Prints "(Metatype)"

println("\(now.dynamicType.description()")
// Prints "__NSDate" since objective-c Class objects have a "description" selector

println("\(soon.dynamicType.description()")
// Compile-time error since ImplicitlyUnwrappedOptional<NSDate> has no "description" method
在上面的例子中,我正在寻找一种显示变量“soon”是类型ImplicitlyUnwrappedOptional<NSDate>,或至少是NSDate!的方法。

48
@JasonMArcher告诉我这个问题怎么是重复的,如果你链接的那个问题是在这个问题之后4天才被问到的? - akashivskyy
有关测试Swift对象类型或读取Swift对象类型的问题有很多。我们正在寻找最佳的问题作为该主题的“主问题”。建议的重复问题有更详细的答案。这并不是说你做错了什么,只是我们试图减少混乱。 - JasonMArcher
4
建议的重复问题并没有回答相同的问题;Type.self不能被打印到控制台用于调试目的,它应该被用于传递给其他将类型作为对象使用的函数。 - Matt Bridges
2
OT:非常奇怪,Swift没有提供这个功能,而且人们需要在这样的低级C库中摆弄。值得提交错误报告吗? - qwerty_so
各位,我已经提供了我的答案。请看一下并让我知道是否符合预期。 - DILIP KOSURI
34个回答

4

我尝试了这里的其他答案,但是根据底层对象不同,结果似乎有所不同。

然而,我找到了一种方法,可以通过以下方式获取对象的Object-C类名:

now?.superclass as AnyObject! //replace now with the object you are trying to get the class name for

以下是如何使用它的示例:
let now = NSDate()
println("what is this = \(now?.superclass as AnyObject!)")

在这种情况下,它将在控制台中打印NSDate。

4

Swift 4:

// "TypeName"
func stringType(of some: Any) -> String {
    let string = (some is Any.Type) ? String(describing: some) : String(describing: type(of: some))
    return string
}

// "ModuleName.TypeName"
func fullStringType(of some: Any) -> String {
    let string = (some is Any.Type) ? String(reflecting: some) : String(reflecting: type(of: some))
    return string
}

使用方法:

print(stringType(of: SomeClass()))     // "SomeClass"
print(stringType(of: SomeClass.self))  // "SomeClass"
print(stringType(of: String()))        // "String"
print(fullStringType(of: String()))    // "Swift.String"

3

根据Klass和Kevin Ballard上面给出的答案和评论,我会选择:

println(_stdlib_getDemangledTypeName(now).componentsSeparatedByString(".").last!)
println(_stdlib_getDemangledTypeName(soon).componentsSeparatedByString(".").last!)
println(_stdlib_getDemangledTypeName(soon?).componentsSeparatedByString(".").last!)
println(_stdlib_getDemangledTypeName(soon!).componentsSeparatedByString(".").last!)

println(_stdlib_getDemangledTypeName(myvar0).componentsSeparatedByString(".").last!)
println(_stdlib_getDemangledTypeName(myvar1).componentsSeparatedByString(".").last!)
println(_stdlib_getDemangledTypeName(myvar2).componentsSeparatedByString(".").last!)
println(_stdlib_getDemangledTypeName(myvar3).componentsSeparatedByString(".").last!)

这将会输出:

"NSDate"
"ImplicitlyUnwrappedOptional"
"Optional"
"NSDate"

"NSString"
"PureSwiftClass"
"Int"
"Double"

我认为这是更好的答案。 - maschall
我最终在Swift 1.2中使用了以下代码:toString(self.dynamicType).componentsSeparatedByString(".").last!,显然我处于对象上下文并能够引用self - Joe

3
let i: Int = 20


  func getTypeName(v: Any) -> String {
    let fullName = _stdlib_demangleName(_stdlib_getTypeName(i))
    if let range = fullName.rangeOfString(".") {
        return fullName.substringFromIndex(range.endIndex)
    }
    return fullName
}

println("Var type is \(getTypeName(i)) = \(i)")

3

在lldb的beta 5版本中,您可以使用以下命令查看对象的类:

fr v -d r shipDate

输出结果可能类似于:

(DBSalesOrderShipDate_DBSalesOrderShipDate_ *) shipDate = 0x7f859940

命令“expanded out”的意思类似于: Frame Variable(打印帧变量)-d run_target(展开动态类型)
需要知道的一件有用的事情是,使用“Frame Variable”输出变量值可以保证不执行任何代码。

3
我发现了一个自主开发类的解决方案(或者你可以访问此类)。在你的对象类定义中添加以下计算属性:
var className: String? {
    return __FILE__.lastPathComponent.stringByDeletingPathExtension
}

现在你可以像这样简单地在你的对象上调用类名:
myObject.className

请注意,这只有在类定义所在的文件名完全相同于你想要名称的类时才有效。
通常情况下,上述答案对大多数情况都适用。但在一些特殊情况下,您可能需要找到不同的解决方案。
如果您需要在类(文件)内部获取类名,可以使用以下代码行:
let className = __FILE__.lastPathComponent.stringByDeletingPathExtension

也许这种方法能帮助一些人解决问题。

2

目前似乎没有一种通用的方法来打印任意值类型的类型名称。正如其他人所指出的,对于实例,您可以打印value.className,但对于原始值,似乎在运行时,类型信息已经丢失。

例如,似乎没有一种方法可以键入:1.something() 并获取任何值的 somethingInt。 (正如另一个答案建议的那样,您可以使用i.bridgeToObjectiveC().className 来给你一个提示,但是__NSCFNumber 实际上不是i的类型--只有当它穿过Objective-C函数调用的边界时才会转换成这个类型。)

我很高兴被证明是错误的,但看起来类型检查都是在编译时完成的,就像C ++(禁用RTTI)一样,在运行时大部分类型信息已经丢失。


你能举个例子,使用className打印“NSDate”变量“now”的示例,在Xcode6的playground中工作吗?据我所知,className在Swift对象上未定义,即使是NSObject的子类也是如此。 - Matt Bridges
看起来“className”对于从NSObject继承的对象是可用的,但仅在开发Mac时可用,而不适用于iOS。似乎在iOS上没有任何解决方法。 - Matt Bridges
1
是的,在一般情况下,Swift 中的类型信息在运行时被删除,这似乎是真正的要点。 - ipmcc
我很好奇REPL是如何打印值的类型的。 - Matt Bridges
REPL 几乎肯定是解释执行而非编译执行,这很容易解释它的运行方式。 - ipmcc

2

以下是如何获取一个类型字符串的对象Type,它是一致的并考虑到对象定义所属的模块或嵌套在其中。适用于Swift 4.x。

@inline(__always) func typeString(for _type: Any.Type) -> String {
    return String(reflecting: type(of: _type))
}

@inline(__always) func typeString(for object: Any) -> String {
    return String(reflecting: type(of: type(of: object)))
}

struct Lol {
    struct Kek {}
}

// if you run this in playground the results will be something like
typeString(for: Lol.self)    //    __lldb_expr_74.Lol.Type
typeString(for: Lol())       //    __lldb_expr_74.Lol.Type
typeString(for: Lol.Kek.self)//    __lldb_expr_74.Lol.Kek.Type
typeString(for: Lol.Kek())   //    __lldb_expr_74.Lol.Kek.Type

1

Xcode 7.3.1,Swift 2.2:

String(instanceToPrint.self).componentsSeparatedByString(".").last


1

虽然不完全符合你的需求,但你也可以这样检查变量的类型是否与Swift类型相匹配:

let object: AnyObject = 1

if object is Int {
}
else if object is String {
}

例如。

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