如何在Swift中查找对象的类型?

301

在尝试理解一个程序或某些特殊情况时,找出某个东西的类型是很有用的。我知道调试器可以显示一些类型信息,并且在这些情况下通常可以依靠类型推断来避免指定类型,但是仍然会想要像Python的type()那样的东西。

dynamicType(参见此问题

更新:在Swift的最新版本中已更改此内容,obj.dynamicType现在会给您一个对类型的引用而不是动态类型的实例。

这个看起来最有前途,但到目前为止我还没有找到实际类型。

class MyClass {
    var count = 0
}

let mc = MyClass()

# update: this now evaluates as true
mc.dynamicType === MyClass.self

我也尝试使用类引用来实例化新对象,这确实有效,但奇怪的是给了一个错误,说我必须添加一个required初始化器:

有效:

class MyClass {
    var count = 0
    required init() {
    }
}

let myClass2 = MyClass.self
let mc2 = MyClass2()

虽然这仅是实际发现任何给定对象类型的一小步,但是。

编辑: 我已删除大量不再相关的细节 - 如果您感兴趣,请查看编辑历史记录 :)


1
可能是重复的问题,如何在Swift中获取变量的类?(https://dev59.com/EmAf5IYBdhLWcg3w9mts) - David Berry
1
有趣的是,print(mc)dump(mc)将打印一个摘要(您可以从toString(mc)reflect(mc).summary中获取),其中将包含类名。但是如何自己仅获取类名并不清楚。 - newacct
@David 类似,但并非所有变量都是类实例。那个问题实际上是关于检查类型是否与程序员所寻找的匹配,而我希望仅仅了解整体类型。 - Jiaaro
可能是如何在Swift中打印变量的类型或类?的重复问题。 - JasonMArcher
使用 type(of: yourObjectRefference)。 - Rahul Panzade
14个回答

369

Swift 3 版本:

type(of: yourObject)

9
有趣的事实是,这与隐式解包可选项不兼容!例如,var myVar: SomeType!。 编译器会显示错误“无法将类型'SomeType!.Type'(又名'ImplicitlyUnwrappedOptional <SomeType> .Type')的值转换为预期的参数类型'AnyClass'(又名' AnyObject.Type)“编译器建议在类型后面添加 as!AnyClass,但然后程序会崩溃,显示一些“EXC_BAD_INSTRUCTION”和其他我无法解释的内容。 - LightningStryk
1
如果您正在寻找特定类型名称,当该类型为协议类型时,这可能对您无效。 - Chris Prince
5
如果您有一个作为类型Any传递的字符串String,那么type(of:)会输出Any而不是String - ScottyBlades
1
根据您的目标:if let yourObjectAsString = yourObject as? String { - ScottyBlades
2
如果 type(of: yourObject) == MyObjectClass.self { ... } - Delorean
显示剩余5条评论

112

Swift 2.0:

正确的方法来进行此类类型内省是使用 Mirror结构体

    let stringObject:String = "testing"
    let stringArrayObject:[String] = ["one", "two"]
    let viewObject = UIView()
    let anyObject:Any = "testing"

    let stringMirror = Mirror(reflecting: stringObject)
    let stringArrayMirror = Mirror(reflecting: stringArrayObject)
    let viewMirror = Mirror(reflecting: viewObject)
    let anyMirror = Mirror(reflecting: anyObject)

然后要从Mirror结构中访问类型本身,您将使用属性subjectType,如下所示:

    // Prints "String"
    print(stringMirror.subjectType)

    // Prints "Array<String>"
    print(stringArrayMirror.subjectType)

    // Prints "UIView"
    print(viewMirror.subjectType)

    // Prints "String"
    print(anyMirror.subjectType)
你可以像这样使用它:

你可以使用类似于以下内容的东西:

    if anyMirror.subjectType == String.self {
        print("anyObject is a string!")
    } else {
        print("anyObject is not a string!")
    }

7
好的,我会尽力为您进行翻译。请注意,如果要镜像的对象是可选类型,则将其与非可选类型进行比较将失败。 StringOptional(String) 不同。因此需谨慎。 - Thomas Verbeek
1
正是我想要的,想知道对象的类型是什么。 - Joseph
在这种情况下是否有一种比较类型,可以在可选类型与非可选类型之间进行比较而不会失败? - Chris Prince
这正是我在寻找的。谢谢 @Gudbergur。 - Mubin Mall
这很不错!但是肯定应该添加可选用例(例如:让 anyObject: Any? =“testing”),检查是否为 nil -> 如果不是 nil -> 使其成为非可选项 -> 然后像常规答案一样继续。 - David Villegas

60

dynamicType.printClassName的代码来自Swift书中的一个示例。我不知道直接获取自定义类名的方法,但是您可以使用is关键字来检查实例的类型,如下所示。该示例还展示了如何实现自定义的className函数,如果您真的需要将类名作为字符串呈现。

class Shape {
    class func className() -> String {
        return "Shape"
    }
}

class Square: Shape {
    override class func className() -> String {
        return "Square"
    }
}

class Circle: Shape {
    override class func className() -> String {
        return "Circle"
    }
}

func getShape() -> Shape {
    return Square() // hardcoded for example
}

let newShape: Shape = getShape()
newShape is Square // true
newShape is Circle // false
newShape.dynamicType.className() // "Square"
newShape.dynamicType.className() == Square.className() // true

注意:
NSObject的子类已经实现了它们自己的className函数。如果你正在使用Cocoa,你可以直接使用这个属性。

class MyObj: NSObject {
    init() {
        super.init()
        println("My class is \(self.className)")
    }
}
MyObj()

2
嘿,不确定何时更改了,但正如Alex Pretzlav指出的那样,行为已经改变了。 - Jiaaro
1
是的。从Swift 3.0开始,subjectType不再可用,而dynamicType会导致编译器发出弃用警告信息。 - Raphael

43

Xcode 6.0.1 起(至少我不确定何时添加了此功能),您的原始示例现在可用:

class MyClass {
    var count = 0
}

let mc = MyClass()
mc.dynamicType === MyClass.self // returns `true`

更新:

回答原始问题,您实际上可以成功地使用Objective-C运行时与纯Swift对象一起使用。

请尝试以下操作:

import Foundation
class MyClass { }
class SubClass: MyClass { }

let mc = MyClass()
let m2 = SubClass()

// Both of these return .Some("__lldb_expr_35.SubClass"), which is the fully mangled class name from the playground
String.fromCString(class_getName(m2.dynamicType))
String.fromCString(object_getClassName(m2))
// Returns .Some("__lldb_expr_42.MyClass")
String.fromCString(object_getClassName(mc))

看起来他们将其更改为提供类型而不是实例。 - Jiaaro
@Jiaaro,我更新了我的答案,加入了我认为你在原问题中寻找的内容。 - Alex Pretzlav

39

如果您只需要检查变量是否为类型X,或者它是否符合某个协议,则可以使用isas?,如下所示:

var unknownTypeVariable = …

if unknownTypeVariable is <ClassName> {
    //the variable is of type <ClassName>
} else {
    //variable is not of type <ClassName>
}

这相当于Obj-C中的isKindOfClass

而这相当于conformsToProtocol或者isMemberOfClass

var unknownTypeVariable = …

if let myClass = unknownTypeVariable as? <ClassName or ProtocolName> {
    //unknownTypeVarible is of type <ClassName or ProtocolName>
} else {
    //unknownTypeVariable is not of type <ClassName or ProtocolName>
}

你的回答第二部分是错误的。使用 as? 条件类型转换的 if let 语句也可以像 isKindOfClass 一样工作,只不过在成功时还提供了类型转换的结果。 - awolf
isMemberOfClass的等价条件是object.dynamicType == ClassName.self - awolf

20

Swift 3:

if unknownType is MyClass {
   //unknownType is of class type MyClass
}

我认为 is 存在于 Swift 3 之前...? - Nicolas Miari

12

针对Swift 3.0版本

String(describing: <Class-Name>.self)

适用于Swift 2.0-2.3

String(<Class-Name>)

1
这个答案对我来说正确的重要之处在于,得到的字符串与类名完全匹配 - 所以我可以使用它从NSManagedObject子类中获取Core Data实体名称。我使用的是Swift3版本。 - Kendall Helmstetter Gelner

10

虽然这个问题比较老,但是对于我来说还是有用的(Swift 5.x):

print(type(of: myObjectName))

这是@Jérémy Lapointe在上面的答案的副本(https://dev59.com/k2Af5IYBdhLWcg3w9mps#40132792) - Chris Kobrzak

9

以下是我推荐的两种方法:

if let thisShape = aShape as? Square 

或者:

aShape.isKindOfClass(Square)

以下是详细的示例:

class Shape { }
class Square: Shape { } 
class Circle: Shape { }

var aShape = Shape()
aShape = Square()

if let thisShape = aShape as? Square {
    println("Its a square")
} else {
    println("Its not a square")
}

if aShape.isKindOfClass(Square) {
    println("Its a square")
} else {
    println("Its not a square")
}

4
print(aShape is Square),使用is运算符更可取。 - DawnSong
对我来说获取对象类型的好方法。 - nihasmata

7

评论:我不明白@JérémyLapointe如何回答这个问题。即使实际类型是更具体的子类,使用type(of:)只能通过检查编译时信息来工作。在Swift 5.1中现在有一种更简单的动态查询类型的方法,而不需要像@Dash建议的那样诉诸于dynamicType。如需了解更多详细信息,请参阅SE-0068:将Swift Self扩展到类成员和值类型


代码

Swift 5.1

// Within an instance method context
Self.self

// Within a static method context
self

这允许使用Self缩写来引用包含类型(在structenumfinal class的情况下)或动态类型(在非final class的情况下)。

解释

该提案很好地解释了为什么这种方法改进了dynamicType

引入Self解决了以下问题:

  • dynamicType仍然是Swift小写关键字规则的例外。此更改消除了与Swift新标准不符的特殊情况。Self比其意图更短,更清晰。它镜像反映了指向当前实例的self
  • 它提供了一种更容易访问静态成员的方法。随着类型名称变得越来越大,可读性会降低。MyExtremelyLargeTypeName.staticMember输入和阅读都很麻烦。
  • 使用硬编码类型名称的代码不如自动知道其类型的代码可移植性好。
  • 重命名类型意味着更新代码中的任何TypeName引用。使用self.dynamicType与Swift在简洁和清晰方面的目标相悖,因为它既嘈杂又深奥。

请注意,在具有非终端成员的类类型中,self.dynamicType.classMemberTypeName.classMember可能不是同义词。


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