一尘不染

检查Swift对象是否是给定元类型的实例

swift

我需要保留Swift元类型的集合并编写一个函数,该函数将检查给定对象是否是其中之一的实例。我可以在Java中轻松做到这一点:

Class c = x.getClass();
c.isInstance(someObj)

但是,我不知道如何在Swift中做到这一点:

var isInt = 7 is Int.Type // compiles

let x = Int.self
var isInt = 7 is x // compiler error - Use of undeclared type 'x'

这甚至可以在Swift中完成吗?


阅读 219

收藏
2020-07-07

共1个答案

一尘不染

不幸的是,您目前只能对is运算符使用命名类型,还不能对其使用任意的元类型值(尽管实际上 应该 可以使用IMO )。

假设您可以控制要与之进行比较的元类型的创建,则达到相同结果的一种解决方案是创建一个包装器类型,该包装器类型具有一个初始化器,该初始化器存储一个is对通用占位符执行检查的闭包:

struct AnyType {

  let base: Any.Type
  private let _canCast: (Any) -> Bool

  /// Creates a new AnyType wrapper from a given metatype.
  /// The passed metatype's value **must** match its static value,
  /// i.e `T.self == base`.
  init<T>(_ base: T.Type) {
    precondition(T.self == base, """
      The static value \(T.self) and dynamic value \(base) of the passed \
      metatype do not match
      """)

    self.base = T.self
    self._canCast = { $0 is T }
  }

  func canCast<T>(_ x: T) -> Bool {
    return _canCast(x)
  }
}

protocol P {}
class C : P {}
class D : C {}

let types = [
  AnyType(P.self), AnyType(C.self), AnyType(D.self), AnyType(String.self)
]

for type in types {
  print("C instance can be typed as \(type.base): \(type.canCast(C()))")
  print("D instance can be typed as \(type.base): \(type.canCast(D()))")
}

// C instance can be typed as P: true
// D instance can be typed as P: true
// C instance can be typed as C: true
// D instance can be typed as C: true
// C instance can be typed as D: false
// D instance can be typed as D: true
// C instance can be typed as String: false
// D instance can be typed as String: false

这种方法的唯一局限性在于,鉴于我们正在执行is检查T.self,因此必须强制执行T.self == base。例如,我们不能接受AnyType(D.self as C.Type),因为那么T.selfC.self同时baseD.self

但是,在您的情况下这应该不成问题,因为我们只是AnyType从编译时已知的元类型构造而来。


但是,如果您无法控制元类型的创建(即您从API获得元类型),那么您在使用元数据时所受的限制就更大了。

如@adev所说,您可以使用type(of:)来获取给定实例的动态元类型,并使用==运算符确定两个元类型是否等效。但是,这种方法的一个问题是它同时忽略了类的层次结构和协议,因为子类型的元类型不会与父类型的元类型进行比较。

关于类的一种解决方案是使用Mirror,如本问答中所示:

/// Returns `true` iff the given value can be typed as the given
/// **concrete** metatype value, `false` otherwise.
func canCast(_ x: Any, toConcreteType destType: Any.Type) -> Bool {
  return sequence(
    first: Mirror(reflecting: x), next: { $0.superclassMirror }
  )
  .contains { $0.subjectType == destType }
}

class C {}
class D : C {}

print(canCast(D(), toConcreteType: C.self)) // true
print(canCast(C(), toConcreteType: C.self)) // true
print(canCast(C(), toConcreteType: D.self)) // false
print(canCast(7, toConcreteType: Int.self)) // true
print(canCast(7, toConcreteType: String.self)) // false

我们正在使用sequence(first:next:)从动态类型x到它可能具有的任何超类元类型来创建元类型序列。

但是,此方法仍不适用于协议。希望该语言的未来版本将提供更丰富的反射API,使您能够比较两个元类型值之间的关系。


然而,考虑到能够使用上面的知识Mirror,我们可以用它来解除上述限制T.self == base从我们的AnyType包装由单独处理类元类型:

struct AnyType {

  let base: Any.Type
  private let _canCast: (Any) -> Bool

  /// Creates a new AnyType wrapper from a given metatype.
  init<T>(_ base: T.Type) {

    self.base = base

    // handle class metatypes separately in order to allow T.self != base.
    if base is AnyClass {
      self._canCast = { x in
        sequence(
          first: Mirror(reflecting: x), next: { $0.superclassMirror }
        )
        .contains { $0.subjectType == base }
      }
    } else {
      // sanity check – this should never be triggered,
      // as we handle the case where base is a class metatype.
      precondition(T.self == base, """
        The static value \(T.self) and dynamic value \(base) of the passed \
        metatype do not match
        """)

      self._canCast = { $0 is T }
    }
  }

  func canCast<T>(_ x: T) -> Bool {
    return _canCast(x)
  }
}

print(AnyType(D.self as C.Type).canCast(D())) // true

其中的情况下T.self是一个类元类型应该是其中唯一的情况下T.self != base,与协议,当T是一些协议PT.TypeP.Protocol,它是协议本身的类型。目前,此类型只能保存valueP.self

2020-07-07