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

Posted

技术标签:

【中文标题】检查 Swift 对象是不是是给定元类型的实例【英文标题】:Check whether Swift object is an instance of a given metatype检查 Swift 对象是否是给定元类型的实例 【发布时间】:2017-12-06 01:07:30 【问题描述】:

我需要保留一组 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 中完成吗?

【问题讨论】:

var isInt = 7 is Int.Type 实际上不起作用。你的意思是var isInt = 7 is Int 它有效,检查“类型”的语法,它包括“类型 -> 元类型类型” - developer.apple.com/library/content/documentation/Swift/… 哦,对不起,“作品”我的意思是“编译”:) 检查我的答案中添加的图像。它总是会失败。 现在很明显,这实际上是我第一次使用 swift 编码,无法隐藏:) 【参考方案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

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

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


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

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

类情况下的一个解决方案是使用Mirror,如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 的动态类型到它可能具有的任何超类元类型创建一系列元类型。

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


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

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这个值。

【讨论】:

在一个闭包中拥有一个通用初始化程序和“捕获”T 真是太棒了!非常感谢。【参考方案2】:

理想情况下,以下内容应该适用于您的情况,并进行一些更改,例如 type(of: 7) 而不是 7== 运算符而不是 is。但是 swift 有一个错误,它会引发以下错误。

let x = Int.self
var isInt = type(of: 7) == x //binary operator '==' cannot be applied to two 'Int.Type' operands

相反,您可以使用以下代码,它会正常工作。

let x = Int.self
var typeOfX = type(of: 7)
var isInt = typeOfX == x //true

Apple 工程师已在此处确认此错误:

Joe Groff - Twitter

您问题的第一行应该是var isInt = 7 is Int。注意Int 而不是Int.Type。否则 Xcode 会抛出以下警告。

通常在大多数情况下你可以这样做,

if z is String 
   //do something
 

【讨论】:

只是一个小评论 - Int.Type 也有效,我已经回答了上面评论的链接:) @frangulyan 检查现在添加的图像。如显示的警告所示,它不会起作用。那总是会失败的。 是的,你说得对,我的意思是“编译”,我自己更正了 :) 我的问题是先编译,这就是为什么我现在没有考虑结果 :) 据我了解,不能像这样检查子类 您可以将其用作“如果 z 是 MyClass”,其中 z 是子类类型。它不适合你吗?【参考方案3】:

Int.self 并不总是意味着持有 Int.type。

调用 MyClass.self 返回的对象是 MyClass 的 swift metaType。该对象将 init 函数和该类中定义的所有方法公开为 curried 方法(在 Swift 中读取 Instance Methods are Curried Functions)。

使用 isKindOfClass 怎么样?

isKindOfClass:如果接收者是指定类的实例或继承自指定类的任何类的实例,则返回 YES。

参考:https://medium.com/ios-os-x-development/types-and-meta-types-in-swift-9cd59ba92295

【讨论】:

“Int.self 并不总是意味着持有 Int.type”是什么意思? Int.self 是一个元类型值,类型为 Int.Type。因为Int 是一个值类型,所以Int.Type 元类型可以拥有的唯一值是Int.self 如何在swift中使用“isKindOfClass”?

以上是关于检查 Swift 对象是不是是给定元类型的实例的主要内容,如果未能解决你的问题,请参考以下文章

Python——检查对象是不是是某个模块中任何类的实例

反射须知

Swift - 从 Nib 实例化总是返回 UIViewController 类型而不是动态类型

如何检查给定的“方法”对象是不是接受 Julia 0.6 中给定的类型的“元组”?

Swift学习笔记-类型转换

Swift学习笔记-类型转换