如何在swift中检查一个对象是否是一种动态类类型?

时间:2018-01-31 06:33:33

标签: swift

我正在实现一个名为ofType的函数,它过滤掉给定类型的所有元素。

以下是我的代码:

class Animal {}
class Mammal: Animal {}
class Monkey: Mammal {}
class Pig: Mammal {}
class Human: Mammal {}

extension Array {
    func ofType<T>(_ metatype: T.Type) -> [T] {
        return flatMap { type(of: $0) == metatype ? $0 as? T : nil }
//      return flatMap { $0 as? T } // This is not working as the T is always the static type of the parameter, which is Animal in this example.
//      return flatMap { $0 as? metatype } // This is not working either because of the grammar restriction.
    }
}

let animals = [Monkey(), Pig(), Human(), Mammal(), Animal()]
func animalType() -> Animal.Type {
    return Mammal.self
}
animals.ofType(animalType()).count // returns 1, expect to be 4.

在Objc中,我可以使用isKindOf()来检查对象是否是类或子类的实例。 swift isas中有类似的操作,但它们之后的类型应该是静态类型,而不是动态类型值(例如,我可以写is Mammal,但不能{{1} }})。

我无法使用类型参数is Mammal.self,因为在此示例中,T等于T,这不是我想要的。

您对如何实现此功能有任何了解吗?

4 个答案:

答案 0 :(得分:5)

这很有效。只需在as?内使用flatMap即可。如果动物可以施放,它将被返回,否则返回nil并且flatMap扔掉它

class Animal {}
class Mammal: Animal {}
class Monkey: Mammal {}
class Pig: Mammal {}
class Human: Mammal {}

extension Array {
    func ofType<T>() -> [T] 
    {
        return flatMap { $0 as? T }
    }
}

let animals = [Monkey(), Pig(), Human(), Mammal(), Animal()]
let monkeys: [Monkey] = animals.ofType() // A one element array
let mammals: [Mammal] = animals.ofType() // A four element array

如果显式键入输出数组,编译器可以从上下文中推断T,否则将T的类型作为参数传递,但不要在函数中使用它。

如果您希望能够动态检查类型,即您不知道要在编译时过滤的类型,则可以使用镜像。这是一个有点笨重的解决方案,但确实有效:

class Animal
{
    func isInstance(of aType: Any.Type) -> Bool
    {
        var currentMirror: Mirror?  = Mirror(reflecting: self)
        while let theMirror = currentMirror
        {
            if theMirror.subjectType == aType
            {
                return true
            }
            currentMirror = theMirror.superclassMirror
        }
        return false
    }
}
class Mammal: Animal {}
class Monkey: Mammal {}
class Pig: Mammal {}
class Human: Mammal {}


let animals = [Monkey(), Pig(), Human(), Mammal(), Animal()]

for aType in [Animal.self, Mammal.self, Monkey.self]
{
    let result = animals.flatMap { $0.isInstance(of: aType) ? $0 : nil }
    print("\(result)")
}

打印:

[__lldb_expr_12.Monkey, __lldb_expr_12.Pig, __lldb_expr_12.Human, __lldb_expr_12.Mammal, __lldb_expr_12.Animal]
[__lldb_expr_12.Monkey, __lldb_expr_12.Pig, __lldb_expr_12.Human, __lldb_expr_12.Mammal] 
[__lldb_expr_12.Monkey]

编辑在评论中提出Sam的建议后,我发现上述方法最好放在协议扩展中。

protocol TypeCheckable {}

extension TypeCheckable  
{
    func isInstance(of aType: Any.Type) -> Bool
    {
        var currentMirror: Mirror?  = Mirror(reflecting: self)
        while let theMirror = currentMirror
        {
            if theMirror.subjectType == aType
            {
                return true
            }
            currentMirror = theMirror.superclassMirror
        }
        return false
    }
}

然后,您可以通过使其符合协议,将功能添加到任何Swift类型。

class Animal: TypeCheckable { ... }

extension String: TypeCheckable {}

答案 1 :(得分:2)

您可以使用反射来查找与元类型兼容的所有项目,这样做:

class Animal { }
class Mammal: Animal {}
class Monkey: Mammal {}
class Pig: Mammal {}
class Human: Mammal {}

extension Array {
    func ofType<T>(_ metatype: T.Type) -> [T] {
        return flatMap { item in
            var mirror:Mirror? = Mirror(reflecting: item)
            while let currentMirror = mirror {
                mirror = currentMirror.superclassMirror
                if currentMirror.subjectType == metatype {
                    return item as? T
                }
            }
            return nil
        }
    }
}

let animals = [Monkey(), Pig(), Human(), Mammal(), Animal()]
func animalType() -> Animal.Type {
    return Mammal.self
}
let result = animals.ofType(animalType())
print(result) // returns 4 items: Monkey, Pig, Human, Mammal

或者,使用以下代码我使用运算符is,我直接将Mammal.self传递给 函数ofType

class Animal {}
class Mammal: Animal {}
class Monkey: Mammal {}
class Pig: Mammal {}
class Human: Mammal {}

extension Array {
    func ofType<T>(_ metatype: T.Type) -> [T] {
        return flatMap { $0 is T ? $0 as? T : nil }
    }
}

let animals = [Monkey(), Pig(), Human(), Mammal(), Animal()]
let result = animals.ofType(Mammal.self)
print(result) // returns 4 items: Monkey, Pig, Human, Mammal

答案 2 :(得分:2)

我个人认为@JeremyP's suggestion使用Mirror是最好的;虽然我会对它进行一些调整:

/// Conditionally cast `x` to a given dynamic metatype value, taking into consideration
/// class inheritance hierarchies.
func conditionallyCast<T, U>(_ x: T, to destType: U.Type) -> U? {

  if type(of: x) is AnyClass && destType is AnyClass { // class-to-class

    let isCastable = sequence(
      first: Mirror(reflecting: x), next: { $0.superclassMirror }
    )
    .contains { $0.subjectType == destType }

    return isCastable ? (x as! U) : nil
  }

  // otherwise fall back to as?
  return x as? U
}

这里我们使用sequence(first:next:)从动态类型x通过它可能具有的任何超类元类型创建一系列元类型(可能是我见过的第一次使用的函数)看起来很糟糕:P)。另外,当我们知道我们没有进行类到类的转换时,我们又回到了做as?转换,这使得该函数也可以使用协议元类型。

然后你可以简单地说:

extension Sequence {
  func ofType<T>(_ metatype: T.Type) -> [T] {
    return flatMap { conditionallyCast($0, to: metatype) }
  }
}

protocol P {}
class Animal {}
class Mammal: Animal {}
class Monkey: Mammal, P {}
class Pig: Mammal {}
class Human: Mammal, P {}

let animals = [Monkey(), Pig(), Human(), Mammal(), Animal()]

let animalType: Animal.Type = Mammal.self
print(animals.ofType(animalType)) // [Monkey, Pig, Human, Mammal]

print(animals.ofType(P.self)) // [Monkey, Human]

假设您在Apple平台上(即可以访问Objective-C运行时),另一个选择是使用Objective-C元类方法isSubclass(of:)来检查给定的元类型是否为等于,或者是另一个的子类:

import Foundation

/// Conditionally cast `x` to a given dynamic metatype value, taking into consideration
/// class inheritance hierarchies.
func conditionallyCast<T, U>(_ x: T, to destType: U.Type) -> U? {

  let sourceType = type(of: x)

  if let sourceType = sourceType as? AnyClass,
     let destType = destType as? AnyClass { // class-to-class

    return sourceType.isSubclass(of: destType) ? (x as! U) : nil
  }

  // otherwise fall back to as?
  return x as? U
}

这是有效的,因为在Apple平台上,Swift类是建立在Obj-C类之上的 - 因此Swift类的元类型是Obj-C元类对象。

答案 3 :(得分:0)

isKindOf()方法在Swift中也可用,因为它是NSObjectProtocol的一种方法。因此,您真正需要做的是为您的Animal声明创建子类NSObject

注意:这种方法在swift中重命名为isKind(of: Type)

应该像

一样简单
class Animal: NSObject {}

现在,剩下的就是解决这个问题,即并非所有数组都包含NSObject的子类或符合NSObjectProtocol的元素。

为了解决这个问题,我们在swift扩展的声明中添加了一个where子句。

现在应该看起来像

extension Array where Element: NSObjectProtocol 

总而言之,最终的代码应该类似于

class Animal: NSObject {}
class Mammal: Animal {}
class Monkey: Mammal {}
class Pig: Mammal {}
class Human: Mammal {}

extension Array where Element: NSObjectProtocol {
    func ofType<T: NSObjectProtocol>(_ metatype: T.Type) -> [T] {
        return flatMap { $0.isKind(of: metatype) ? $0 as? T : nil }
    }
}

let animals = [Monkey(), Pig(), Human(), Mammal(), Animal()]
func animalType() -> Animal.Type {

    return Mammal.self
}

print(animals.ofType(animalType()).count)