【问题标题】:How to check whether an object is kind of a dynamic class type in swift?如何快速检查对象是否属于动态类类型?
【发布时间】:2018-07-10 04:35:55
【问题描述】:

我正在实现一个名为 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() 来检查对象是类的实例还是子类。 swiftisas也有类似的操作,但是后面的类型应该是静态类型,而不是动态类型值(比如我可以写is Mammal,但不能写is Mammal.self)。

我也不能使用类型参数T,因为在本例中,T 等于Animal,这不是我想要的。

你知道如何实现这个功能吗?

【问题讨论】:

  • 你不知道在编译时要过滤掉什么类型吗?
  • @Sweeper No. 类型仅在运行时确定。
  • 那么编译器怎么可能知道T是什么?
  • @Sweeper 编译器只知道参数的静态类型,本例中TAnimal
  • 没错!所以我的意思是返回 [Mammal] 是不可能的。

标签: swift


【解决方案1】:

isKindOf() 方法在 Swift 中也可用,因为它是 NSObjectProtocol 的方法。所以你真正需要做的是为你的 Animal 声明子类NSObject

注意:is类方法在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)

【讨论】:

  • 感谢您的回答。我真的不想添加这个约束,因为它会限制仅在实现 NSObjectProtocol 的类上使用的函数。
  • 我相信在 Swift 中没有简单的方法可以做到这一点,而无需回到 Obj-C 时代的代码。在没有子类化NSObject 的情况下这样做是比它的价值更多的努力。我们通常不会在 swift 中继承 NSObject。这并不意味着它反对“Swift 方式”,而是在大多数情况下我们不需要这样做。这是我们需要子类化 NSObject 的少数情况之一,这不是一件坏事。
  • ...俗话说,“不存在的代码就是你不需要调试的代码。”
  • 你可以写成不需要继承NSObject:gist.github.com/hamishknight/5bf45b025fcde4c2e8f9c67fce9fbe7e。虽然由于它仍然依赖于 Obj-C 运行时,但它只能在具有 Obj-C 互操作的平台(即 Apple 平台)上工作。
  • @Sam 哈哈,我喜欢你的哲学。
【解决方案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

【讨论】:

  • 我知道这行得通。我只是无法传入文字类类型,因此编译器无法推断出确切的类型。
  • @AndreaMugnaini 为什么是String(describing: type(of: item)) != String(describing: T.self)?我无法理解你的意思...
  • @Bing 我找到了合适的解决方案
  • @AndreaMugnaini 其他一些人也找到了类似的解决方案。感谢您回答问题!
【解决方案3】:

这行得通。只需在flatMap 中使用as?。如果动物可以施放,则返回,否则返回nilflatMap 将其扔掉

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 在 cmets 中的建议,我想到上述方法最好放在协议扩展中。

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 {}

【讨论】:

  • @Hamish 的回答相当不错。你可以在这里看到它:gist.github.com/hamishknight/5bf45b025fcde4c2e8f9c67fce9fbe7e
  • 我喜欢你的方法。为了简化它,我会将isInstance(of aType: Any.Type) 方法移动到它自己的一个类中,并将Animal 作为它的子类。
  • @Sam 我几乎同意你的看法。事实上,最好的方法是创建一个协议,然后将该方法放入协议扩展中。这样,您可以使用扩展将功能添加到任何类型。
  • 协议扩展实现太棒了!
  • @JeremyP 我不知道你可以扩展协议。看起来真的很不错!
【解决方案4】:

我个人认为@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 元类对象。

【讨论】:

  • 我还是更喜欢你的第二个答案,虽然不像第一个那样有启发性。 :P
  • 我发现isKind(of:) 可用于AnyObject。所以这行let sourceType = type(of: x)可以省略,只检查(x as? AnyObject)?.isKind(of: destType)
  • @Bing 是的,您可以改用isKind(of:);但请记住,对AnyObject 的强制转换总是成功的,因为非 Obj-C 兼容的东西被装在一个不透明的 Obj-C 兼容的盒子里。所以要检查x 是否真的是一个类的实例,你需要说type(of: x) is AnyClass,我发现到那时你最好只使用as?isSubclass(of:) 与元类型:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-12-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-14
  • 2023-04-09
  • 1970-01-01
  • 2011-01-13
相关资源
最近更新 更多