如何在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个回答

407

2016年9月更新:

Swift 3.0:使用type(of:),例如type(of: someThing)(因为已删除dynamicType关键字)。

2015年10月更新:

我已将下面的示例更新为新的Swift 2.0语法(例如,println被替换为printtoString()现在是String())。

Xcode 6.3发布说明:

@nschum在评论中指出Xcode 6.3发布说明还展示了另一种方法:

当与println或字符串插值一起使用时,类型值现在打印为完整的解码类型名称。

import Foundation

class PureSwiftClass { }

var myvar0 = NSString() // Objective-C class
var myvar1 = PureSwiftClass()
var myvar2 = 42
var myvar3 = "Hans"

print( "String(myvar0.dynamicType) -> \(myvar0.dynamicType)")
print( "String(myvar1.dynamicType) -> \(myvar1.dynamicType)")
print( "String(myvar2.dynamicType) -> \(myvar2.dynamicType)")
print( "String(myvar3.dynamicType) -> \(myvar3.dynamicType)")

print( "String(Int.self)           -> \(Int.self)")
print( "String((Int?).self         -> \((Int?).self)")
print( "String(NSString.self)      -> \(NSString.self)")
print( "String(Array<String>.self) -> \(Array<String>.self)")

输出结果为:

String(myvar0.dynamicType) -> __NSCFConstantString
String(myvar1.dynamicType) -> PureSwiftClass
String(myvar2.dynamicType) -> Int
String(myvar3.dynamicType) -> String
String(Int.self)           -> Int
String((Int?).self         -> Optional<Int>
String(NSString.self)      -> NSString
String(Array<String>.self) -> Array<String>

Xcode 6.3更新:

你可以使用_stdlib_getDemangledTypeName()函数:

print( "TypeName0 = \(_stdlib_getDemangledTypeName(myvar0))")
print( "TypeName1 = \(_stdlib_getDemangledTypeName(myvar1))")
print( "TypeName2 = \(_stdlib_getDemangledTypeName(myvar2))")
print( "TypeName3 = \(_stdlib_getDemangledTypeName(myvar3))")

并将此作为输出:

TypeName0 = NSString
TypeName1 = __lldb_expr_26.PureSwiftClass
TypeName2 = Swift.Int
TypeName3 = Swift.String

原始答案:

在 Xcode 6.3 之前,_stdlib_getTypeName 获取变量的编码类型名称。Ewan Swick 的博客文章 帮助解密这些字符串:

例如,_TtSi 表示 Swift 内部的 Int 类型。

Mike Ash 的博客文章涵盖了相同的主题,内容非常棒


2
你是怎么找到 _stdlib_getTypeName() 的?集成的 REPL 不再存在,swift-ide-test 也不存在了,而 Xcode 打印 Swift 模块时省略了以 _ 开头的值。 - Lily Ballard
10
lldb 的 repl 中,似乎可以轻松地搜索符号。还有 _stdlib_getDemangledTypeName()_stdlib_demangleName() - Lily Ballard
1
哦,真不错,我没意识到imagetarget modules的快捷方式。我最终使用了target modules lookup -r -n _stdlib_,当然,这可以更好地表达为image lookup -r -n _stdlib_ libswiftCore.dylib - Lily Ballard
1
FYI - 看起来 toString(...) 已被替换为 String(...)。 - Nick
7
Swift 3.0: dynamicType 关键字已从 Swift 中删除。取而代之,语言中添加了一个新的原始函数 type(of:):https://github.com/apple/swift/blob/master/CHANGELOG.md - Szu
显示剩余8条评论

46

编辑:在Swift 1.2(Xcode 6.3)中引入了一个新的toString函数。

现在,您可以使用.self打印任何类型的解缠后的类型,并使用.dynamicType打印任何实例:

struct Box<T> {}

toString("foo".dynamicType)            // Swift.String
toString([1, 23, 456].dynamicType)     // Swift.Array<Swift.Int>
toString((7 as NSNumber).dynamicType)  // __NSCFNumber

toString((Bool?).self)                 // Swift.Optional<Swift.Bool>
toString(Box<SinkOf<Character>>.self)  // __lldb_expr_1.Box<Swift.SinkOf<Swift.Character>>
toString(NSStream.self)                // NSStream
试着调用YourClass.selfyourObject.dynamicType
参考:https://devforums.apple.com/thread/227425.

2
我已经回复了那个帖子,但我会在这里添加我的评论。那些值的类型是“Metatype”,但似乎没有一种简单的方法可以确定Metatype对象表示的类型,这就是我正在寻找的东西。 - Matt Bridges
它们是Metatype类型,也是类。它们代表一个类。不确定你对此有什么问题。someInstance.dynamicType.printClassName()将打印类的名称。 - Analog File
18
printClassName() 方法只是文档中示例方法的一个示例,它不是您可以访问的 API 调用。 - Dave Wood
4
FYI - 看起来toString(...)已被替换为String(...)。 - Nick

40

Swift 3.0

let string = "Hello"
let stringArray = ["one", "two"]
let dictionary = ["key": 2]

print(type(of: string)) // "String"

// Get type name as a string
String(describing: type(of: string)) // "String"
String(describing: type(of: stringArray)) // "Array<String>"
String(describing: type(of: dictionary)) // "Dictionary<String, Int>"

// Get full type as a string
String(reflecting: type(of: string)) // "Swift.String"
String(reflecting: type(of: stringArray)) // "Swift.Array<Swift.String>"
String(reflecting: type(of: dictionary)) // "Swift.Dictionary<Swift.String, Swift.Int>"

1
Swift 2的实现特别棒,特别是枚举案例。有人知道这种行为是否被苹果文档化/保证了吗?标准库仅仅包含一条注释,说明这适用于调试... - Milos

30

这是您正在寻找的吗?

println("\(object_getClassName(now))");

它会打印“__NSDate”

更新:请注意,自Beta05以来,此方法似乎不再适用。


2
不适用于Swift类。返回“UnsafePointer(0x7FBB18D06DE0)” - aryaxt
4
在ObjC类中也不起作用(至少在Beta5中),对于NSManagedObject,我只会得到“Builtin.RawPointer = address”。 - Kendall Helmstetter Gelner

23

我目前使用的Xcode版本是6.0(6A280e)。

import Foundation

class Person { var name: String; init(name: String) { self.name = name }}
class Patient: Person {}
class Doctor: Person {}

var variables:[Any] = [
    5,
    7.5,
    true,
    "maple",
    Person(name:"Sarah"),
    Patient(name:"Pat"),
    Doctor(name:"Sandy")
]

for variable in variables {
    let typeLongName = _stdlib_getDemangledTypeName(variable)
    let tokens = split(typeLongName, { $0 == "." })
    if let typeName = tokens.last {
        println("Variable \(variable) is of Type \(typeName).")
    }
}

输出:

Variable 5 is of Type Int.
Variable 7.5 is of Type Double.
Variable true is of Type Bool.
Variable maple is of Type String.
Variable Swift001.Person is of Type Person.
Variable Swift001.Patient is of Type Patient.
Variable Swift001.Doctor is of Type Doctor.

1
不错,但问题还包括Optionals,Optionals的输出设置为Optional(...),因此您的代码只会返回类型为Optional。它还应该显示var optionalTestVar:Person?= Person(name:“Sarah”)作为Optional(Person),并且带有Person!的变量为ImplicitlyUnwrappedOptional(Person)。 - Binarian

18

从Xcode 6.3与Swift 1.2开始,您可以将类型值直接转换为完整的解析后的 String

toString(Int)                   // "Swift.Int"
toString(Int.Type)              // "Swift.Int.Type"
toString((10).dynamicType)      // "Swift.Int"
println(Bool.self)              // "Swift.Bool"
println([UTF8].self)            // "Swift.Array<Swift.UTF8>"
println((Int, String).self)     // "(Swift.Int, Swift.String)"
println((String?()).dynamicType)// "Swift.Optional<Swift.String>"
println(NSDate)                 // "NSDate"
println(NSDate.Type)            // "NSDate.Type"
println(WKWebView)              // "WKWebView"
toString(MyClass)               // "[Module Name].MyClass"
toString(MyClass().dynamicType) // "[Module Name].MyClass"

1.2版本中是否可以实现另一种方向的操作?即根据类名初始化一个实例? - user965972
据我所知,我认为没有任何。 - kiding

17

你仍然可以通过className(返回一个String)来访问类。

实际上有几种方法可以获取类,例如classForArchiverclassForCoderclassForKeyedArchiver(所有这些都返回AnyClass!)。

你无法获取原始类型的类型(原始类型不是一个类)。

示例:

var ivar = [:]
ivar.className // __NSDictionaryI

var i = 1
i.className // error: 'Int' does not have a member named 'className'

如果您想获取原始数据类型的类型,您需要使用bridgeToObjectiveC()方法。例如:

var i = 1
i.bridgeToObjectiveC().className // __NSCFNumber

我敢打赌这是一个原始类型。不幸的是,你无法获取原始类型的类型。 - Leandros
这些示例似乎在游乐场中无法运行。我无论是对于基元类型还是非基元类型都会出现错误。 - Matt Bridges
没有这样的模块'Cocoa' - Matt Bridges
9
在为Mac OS X开发时,className似乎仅适用于NSObjects;但在iOS SDK中,无论是NSObjects还是“普通”对象都无法使用。 - Matt Bridges
3
className 是 Cocoa 的 AppleScript 支持的一部分,绝对不应该用于通用的内省。显然它在 iOS 上不可用。 - Nikolai Ruhe
显示剩余2条评论

16

您可以使用反射(reflect)获取对象信息。
例如,获取对象的类名:

var classname = reflect(now).summary

在Beta 6中,这个给我返回了应用程序名称+类名,虽然有点混乱,但是这是一个开始。对于reflect(x).summary给予加分,谢谢! - Olie
在Swift 1.2 Xcode 6.4和iOs 8.4中无法工作,会打印一个空行。 - Juan Boero

15

Xcode 8 Swift 3.0 使用 type(of:)

let className = "\(type(of: instance))"

13

我运气好,得到了:

let className = NSStringFromClass(obj.dynamicType)

@dude,什么都没用,但classForCoder起作用了。 - Satheesh

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