在Swift中检查一个对象是否是给定类型

324

我有一个由AnyObject组成的数组。我想遍历它,并找到所有是数组实例的元素。

如何在Swift中检查对象是否为特定类型?


1
你的问题是关于查找给定对象类型的,但你已经接受了一个只能检查对象是否为给定类型的答案。我建议你编辑你的问题,明确这一点,否则许多读者会对你接受的答案感到不满意。(其他所有答案都类似,所以幸运的是,你不需要担心通过缩小问题范围使它们无效。) - Jeremy
1
我已经编辑了这个问题,以使其与https://dev59.com/rWAf5IYBdhLWcg3w9mts区分开来,我正在投票重新打开它。它们都是有用的、类似的问题,但答案却非常不同,因此将它们分开会很有用。 - Jeremy
1
可能是重复的问题:如何在Swift中找出对象的类型? - Esqarrouth
20个回答

353

如果您想针对特定类型进行检查,可以按照以下方式操作:

if let stringArray = obj as? [String] {
    // obj is a string array. Do something with stringArray
}
else {
    // obj is not a string array
}

您可以使用 "as!",如果 obj 不是类型为 [String],则会引发运行时错误。

let stringArray = obj as! [String]

您也可以逐个检查元素:

let items : [Any] = ["Hello", "World"]
for obj in items {
   if let str = obj as? String {
      // obj is a String. Do something with str
   }
   else {
      // obj is not a String
   }
}

1
如果没有?,为什么只会抛出运行时错误而不是编译时错误呢?听起来像是as?组合在一起会执行运行时检查。什么情况下适合使用没有?as呢?提前致谢。 - Unheilig
@Unheilig 如果您的程序无法从对象不属于该类型的情况中恢复,那么您只应该使用 as 而不带上 ?,因为如果不是该类型,程序将立即停止。在 if 语句中使用 ? 可以让程序继续执行。 - drewag
@Unheilig 这是一个特殊情况,通常赋值 (=) 不再像在 C 中那样返回一个值。这是为了防止在 if 语句中意外使用 = 而不是 == 导致的错误。 - drewag
谢谢您的澄清。就这个问题,如果在 if 语句中使用 varlet 应该没有任何区别,对吗?我知道它们的区别,只是想问当它们在 if 语句中时是否有区别。 - Unheilig
1
@Unheilig 正确,如果您想在本地作用域中修改该值且不影响该作用域之外的值,可以使用 var。 - drewag
显示剩余3条评论

261

Swift 2.2 - 5中,您现在可以执行以下操作:

if object is String
{
}

然后对您的数组进行过滤:

let filteredArray = originalArray.filter({ $0 is Array })

如果您需要检查多种类型:

    switch object
    {
    case is String:
        ...

    case is OtherClass:
        ...

    default:
        ...
    }

1
这个解决方案更短,但有一个缺点:你不能在括号内将 object 当作 String 使用(至少在 Swift 2 中是这样),然而通过使用 let 解决方案则可以。 - Ferran Maylinch
1
@FerranMaylinch 我不明白你的意思,因为在块中使用 object 是可以的。 - meaning-matters
@meaning-matters 比如说,你无法执行 object.uppercaseString,因为变量的类型没有转换为该类型,你只是检查了对象(由变量指向)是否为 String - Ferran Maylinch
如果您要检查的类类型是任意的,那么该怎么做呢?如果您只有一个变量,需要从中获取一个类类型呢? - Alex Zavatone

160

如果你只想知道一个对象是否是给定类型的子类型,那么有一种更简单的方法:

class Shape {}
class Circle : Shape {}
class Rectangle : Shape {}

func area (shape: Shape) -> Double {
  if shape is Circle { ... }
  else if shape is Rectangle { ... }
}

“使用类型检查运算符(is)检查实例是否属于某个子类类型。如果实例属于该子类类型,则类型检查运算符返回true,否则返回false。”摘自:苹果公司《The Swift Programming Language》iBooks
上述中,“属于某个子类类型”这一短语很重要。编译器接受使用is Circleis Rectangle的原因是该值shape被声明为ShapeCircleRectangle的超类)。
如果您正在使用基本类型,则超类将是Any。以下是一个示例:
 21> func test (obj:Any) -> String {
 22.     if obj is Int { return "Int" }
 23.     else if obj is String { return "String" }
 24.     else { return "Any" }
 25. } 
 ...  
 30> test (1)
$R16: String = "Int"
 31> test ("abc")
$R17: String = "String"
 32> test (nil)
$R18: String = "Any"

2
如果我将一个原始类型存储在数组中,或者如果该数组是原始类型之一,那么 is 还能正常工作吗?谢谢。 - Unheilig
如果您将“object”声明为“Any”,它应该可以工作。附带示例更新。 - GoZoner
谢谢回复。看起来很有前途。我唯一的疑问是,根据下面的答案,建议使用AnyObject,似乎因为AnyObject没有继承自NSObject而被反驳了。如果Any不同,那么这也将是一个很好的解决方案。谢谢。 - Unheilig

27

针对Swift 4:

if obj is MyClass{
    // then object type is MyClass Type
}

1
最佳答案给我。 - sgelves

23

我有两种做法:

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")
}

编辑:现在是3个:

let myShape = Shape()
if myShape is Shape {
    print("yes it is")
}

2
isKindOfClassNSObject协议的一个方法;它只适用于采用该协议的类(所有下降自NSObject的类,以及任何明确采用它的自定义Swift类)。 - Nicolas Miari

10

假设drawTriangle是一个UIView的实例。为了检查drawTriangle是否属于UITableView类型:

Swift 3中,

if drawTriangle is UITableView{
    // in deed drawTriangle is UIView
    // do something here...
} else{
    // do something here...
}

这也可以用于您自己定义的类。您可以使用它来检查视图的子视图。


7

为了完整起见,基于已接受的答案和一些其他内容:

let items : [Any] = ["Hello", "World", 1]

for obj in items where obj is String {
   // obj is a String. Do something with str
}

但你也可以使用 compactMap(它还会“映射”filter没有的值):

items.compactMap { $0 as? String }.forEach{ /* do something with $0 */ ) }

使用 switch 版本:

for obj in items {
    switch (obj) {
        case is Int:
           // it's an integer
        case let stringObj as String:
           // you can do something with stringObj which is a String
        default:
           print("\(type(of: obj))") // get the type
    }
}

但是回到问题上,要检查它是否是一个数组(即[String]):

let items : [Any] = ["Hello", "World", 1, ["Hello", "World", "of", "Arrays"]]

for obj in items {
  if let stringArray = obj as? [String] {
    print("\(stringArray)")
  }
}

更普遍地说(请参见这个问题的另一个回答):

for obj in items {
  if obj is [Any] {
    print("is [Any]")
  }

  if obj is [AnyObject] {
    print("is [AnyObject]")
  }

  if obj is NSArray {
    print("is NSArray")
  }
}

6

as? 不总是能给出您期望的结果,因为 as 并不测试数据类型是否是特定种类的数据类型,而仅测试数据类型是否可以被转换为或表示为特定种类。

例如,请考虑以下代码:

func handleError ( error: Error ) {
    if let nsError = error as? NSError {

符合Error协议的每种数据类型都可以转换为NSError对象,因此这将始终成功。然而,这并不意味着error实际上是NSError对象或其子类。

正确的类型检查应该是:

func handleError ( error: Error ) {
    if type(of: error) == NSError.self {

然而,这仅检查确切的类型。如果你想包括NSError的子类,你应该使用:

func handleError ( error: Error ) {
    if error is NSError.Type {

5
请注意:
var string = "Hello" as NSString
var obj1:AnyObject = string
var obj2:NSObject = string

print(obj1 is NSString)
print(obj2 is NSString)
print(obj1 is String)
print(obj2 is String) 

四行代码最后都返回 true,这是因为如果您键入

var r1:CGRect = CGRect()
print(r1 is String)

当然,它会打印出“false”,但是一个警告表明从CGRect到String的转换失败。因此,某些类型被桥接,并且“is”关键字调用了一个隐式转换。

你最好使用以下之一:

myObject.isKind(of: MyClass.self)) 
myObject.isMember(of: MyClass.self))

5

为什么不使用专门为此任务而构建的内置功能呢?

let myArray: [Any] = ["easy", "as", "that"]
let type = type(of: myArray)

Result: "Array<Any>"

type()函数非常简单 : ) - vivi

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