检查 Swift 对象是否是给定元类型的实例 [英] Check whether Swift object is an instance of a given metatype

查看:28
本文介绍了检查 Swift 对象是否是给定元类型的实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I need to keep a collection of Swift metatypes and write a function which will check if a given object is an instance of one of them. I can do that easily in Java:

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

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

However, I have no idea how to do that in 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 中完成吗?

Is this even possible to be done in Swift?

推荐答案

不幸的是,您目前只能使用带有 is 运算符的命名类型,您还不能使用任意元类型值(虽然真的是 IMO,你应该能够做到).

Unfortunately, you can currently only use a named type with the is operator, you cannot yet use an arbitrary metatype value with it (although really IMO you ought to be able to).

假设您可以控制要比较的元类型的创建,实现相同结果的一种解决方案是创建一个包装器类型,其中包含一个存储执行 is 的闭包的初始化程序代码>检查通用占位符:

Assuming you have control over the creation of the metatypes that you want to compare against, one solution that achieves the same result would be to create a wrapper type with an initialiser that stores a closure that performs the is check against a generic placeholder:

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

这种方法的唯一限制是,鉴于我们正在使用 T.self 执行 is 检查,我们必须强制执行 T.self == 基础.例如,我们不能接受 AnyType(D.self as C.Type),因为 T.self 将是 C.selfbase 将是 D.self.

The only limitation of this approach is that given we're performing the is check with T.self, we have to enforce that T.self == base. For example, we cannot accept AnyType(D.self as C.Type), as then T.self would be C.self while base would be D.self.

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

However this shouldn't be a problem in your case, as we're just constructing AnyType from metatypes that are known at compile time.

但是,如果您无法控制元类型的创建(即您从 API 获得它们),那么您可以使用它们做的事情就会受到相当多的限制.

If however you don't have control over the creation of the metatypes (i.e you get handed them from an API), then you're quite a bit more limited with what you can do with them.

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

As @adev says, you can use type(of:) in order to get the dynamic metatype of a given instance, and the == operator to determine if two metatypes are equivalent. However, one problem with this approach is that it disregards both class hierarchies and protocols, as a subtype metatypes will not compare equal with a supertype metatypes.

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

One solution in the case of classes is to use Mirror, as also shown in this Q&A:

/// 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 的动态类型到它可能具有的任何超类元类型创建一系列元类型.

We're using sequence(first:next:) to create a sequence of metatypes from the dynamic type of x through any superclass metatypes it might have.

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

However this method still won't work with protocols. Hopefully a future version of the language will provide much richer reflection APIs that allow you to compare the relationship between two metatype values.

然而,鉴于能够使用Mirror的上述知识,我们可以使用它来解除我们T.self == base的上述限制>AnyType 通过单独处理类元类型来包装:

However, given the above knowledge of being able to use Mirror, we can use it to lift the aforementioned restriction of T.self == base from our AnyType wrapper on by handling class metatypes separately:

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,是协议本身的类型.而目前,这个类型只能保存值 P.self.

The case where T.self is a class metatype should be the only case where T.self != base, as with protocols, when T is some protocol P, T.Type is P.Protocol, which is the type of the protocol itself. And currently, this type can only hold the value P.self.

这篇关于检查 Swift 对象是否是给定元类型的实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆