【问题标题】:Compare AnyObjects in Swift without casting them to a specific type比较 Swift 中的 AnyObjects 而不将它们转换为特定类型
【发布时间】:2014-11-12 02:40:43
【问题描述】:

尝试使用 Equatable 协议中定义的“==”运算符比较两个 AnyObject 类型的对象会导致 Swift 中的编译错误。在不知道可用于向下转换的对象的真实类型的情况下,是否有人找到了比较此类对象的方法?

这个问题的背景是我有一个字典 Dictionary 应该通过下标提供值,然后在某些时候我需要比较字典中的值以确保它们是独一无二。

编辑 这是一个演示该问题的 sn-p。

@objc(FooObject)
public class FooObject: NSManagedObject {

    @NSManaged public var properties: Dictionary<String, AnyObject>

    public subscript(property: String) -> AnyObject? {
        get {
            return properties[property]
        }
        set(newValue) {

            for propertyValue in properties.values {
                if propertyValue == newValue { // This line is not compiling: Cannot invoke '==' with AnyObject
                    println("Values in are expected to be unique!")
                    // Throw an exception here ...
                }
            }

            properties[property] = newValue
        }
    }
}

注意在类定义中声明的像 这样的泛型并用作字典的值类型不会解决问题,因为它不能与 NSManagedObject 子类一起使用。

【问题讨论】:

  • AnyObject 在添加到 Dictionary 之前是什么?解决此问题的理想方法是首先不使用AnyObject,可能是通过创建自定义protocolenum 并改用它。
  • 这些值是 AnyObject 类型,因为它们是从外部提供的,并且可以是任何类型,就像在 Objective-C 中一样,您可以将任何值作为值,例如字符串、数字、数据等。我在考虑一个协议,但它并没有解决问题。枚举呢?
  • enums 和 Associated Values 可能是一个很好的解决方案。半相关:stackoverflow.com/a/25863986/3300036
  • 迈克,我已经用一些细节更新了这个问题。正如您所看到的,枚举并不是我所需要的,因为可能可以作为值存储在字典中的类型非常多。另一个问题是字典被用作 NSManagedObject 属性。

标签: swift comparison


【解决方案1】:

使用 Swift 文档中的 === 运算符:

Swift 还提供了两个标识运算符(=== 和 !==),您可以使用它们来测试两个对象引用是否都引用同一个对象实例。

【讨论】:

  • 如果我不关心它们的内存地址,而是关心它们的值怎么办?
  • Ben 提出了一个很好的观点。如果两个相同类型的对象相等但不相同,则此解决方案将不起作用。
【解决方案2】:

我认为这是不可能的。问题是 Equatable 是用一个方法定义的,该方法需要

func ==(a: T, b: T)

在编译时,编译器无法找到正确的函数(因为它不知道提前的类型)。

您可以做到这一点的唯一方法是,如果您对要比较的类型有所了解。然后你可以为每种类型具体调用适当的相等函数:

func compare(a: AnyObject, b: AnyObject) {    
    if let va = a as? Int, vb = b as? Int            {if va != vb {return false}}
    else if let va = a as? String, vb = b as? String {if va != vb {return false}}
    else if let va = a as? Bool, vb = b as? Bool     {if va != vb {return false}}
            ...
    else {
        // not a type we expected
        return false;
    }
}

有趣的是,例如,C# 通过将IComparable 接口定义为具有方法来解决这个问题:

int CompareTo(Object obj)

这允许每个 IComparable 对象与 any 其他对象进行比较(但函数必须始终在此处进行自己的类型检查,以确保 obj 的类型正确)。

【讨论】:

    【解决方案3】:

    这应该适用于大多数用例:

    func equalAny<BaseType: Equatable>(lhs: Any, rhs: Any, baseType: BaseType.Type) -> Bool {
        guard
            let lhsEquatable = lhs as? BaseType,
            let rhsEquatable = rhs as? BaseType
            else { return false }
        return lhsEquatable == rhsEquatable
    }
    
    var a: Any = "a" as Any
    var b: Any = "b" as Any
    
    equalAny(lhs: a, rhs: b, baseType: NSObject.self) // false
    b = "a"
    equalAny(lhs: a, rhs: b, baseType: NSObject.self) // true
    
    a = CGFloat(5)
    b = 5
    
    equalAny(lhs: a, rhs: b, baseType: NSObject.self) // true
    
    a = ["hello": "world"]
    b = ["hello": "world"]
    
    equalAny(lhs: a, rhs: b, baseType: NSObject.self) // true
    

    【讨论】:

    • 这在 Swift 3 中对我有用,即使使用 IntString 等。看起来所有这些 swift 类型在 Any 时都以某种方式转换为某种基金会等价物,知道这在幕后是如何工作的吗?
    • @Alex AFAIK, Any 相当于 Swift 的 ObjC 的 id,因为它让 编译器 知道“嘿,这可能是任何东西”,但在 runtime,它仍然保持它的真实类型。所以Ints 仍然是Ints 和Strings 仍然是Strings,但是在编译时你不知道。
    • 我很惊讶IntString 和 'Bool' 都转换为 NSObject - 我不认为会是这种情况
    • @Alex Ints 和 Bools 免费桥接到 NSNumber,这是一个 NSObjectStringss 和 NSStrings 也是如此。延伸阅读:developer.apple.com/library/content/documentation/Swift/…
    【解决方案4】:

    如果对象的类型为Any,Swift 3 不能使用=== 进行参考比较。原因是Any 可能包含整数和浮点数或任何其他实际上不是对象的东西。因此,您必须将其转换为 NSObject 才能比较 2 个项目。喜欢

    if (obj1 as! NSObject) === (obj2 as! NSObject)
    {
    //logic goes here
    }
    

    【讨论】:

      【解决方案5】:

      看看实现:

      /// Returns true if these arrays contain the same elements.
      func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool
      

      这意味着编译器需要知道左边和右边的参数必须是相同的类型。所以你正在实现的功能应该是这样的:

      func compareFunc <T: Equatable>(par1: T, par2: T) {
          ...
          if par1 == par2 {
              ...
          }
          ...
      }
      

      编辑:

      你的字典应该是这样的:

      func compareFunc <T: Equatable>(dic1: [String : T], dic2: [String : T]) {
          ...
          if dic1[yourKey] == dic2[yourKey] {
              ...
          }
          ...
      }
      

      编辑 2:

      一个例子:

      func compareFunc <T: Equatable>(dic1: [String : T], dic2 : [String : T]) -> Bool {
      
          return dic1["key"] == dic2["key"]
      }
      
      compareFunc(["key": "value"], ["key": "value"])
      

      【讨论】:

      • 它没有回答这个问题,因为我正在使用的字典类型是 Dictionary 并且你不能将它传递给你的 compareFunc 函数,它不会编译。
      【解决方案6】:

      我什至有点不好意思提出这个建议,但我也遇到了这个问题。我的情况是,我正在比较来自 Dictionaries 的 JSON,它们实际上存在于 2 个不同的 NSManagedObjectContexts 中,因此尽管值可能相同,但对象不会相同。

      无论如何,我所做的只是从任何值创建字符串并进行比较。这是一个 hack,但在我的情况下是为了测试目的,它可以工作。

      set(newValue) {
          for propertyValue in properties.values {
              if String(describing:propertyValue) == String(describing:newValue){ 
                  println("Values in are expected to be unique!")
                  // Throw an exception here ...
              }
          }
          properties[property] = newValue
      }
      

      【讨论】:

      • 这是非常规的,但对于很多用途来说效果很好。显然,这有多好取决于您的类型实现description 的程度,包括类型信息(如果这对您很重要),但仍然是聪明的 hack。
      【解决方案7】:

      如果你想在 Objective-C 中使用它,为什么不把 properties 变成 Dictionary&lt;String, NSObject&gt; 呢?毕竟,NSObjectisEqual,而且你不能将原始类型放入 NSDictionary

      所以它看起来像这样:

      @objc(FooObject)
      public class FooObject: NSManagedObject {
      
          @NSManaged public var properties: Dictionary<String, NSObject>
      
          public subscript(property: String) -> NSObject? {
              get {
                  return properties[property]
              }
              set(newValue) {
      
                  for propertyValue in properties.values {
                      if propertyValue == newValue { // This line is not compiling: Cannot invoke '==' with AnyObject
                          print("Values in are expected to be unique!")
                          // Throw an exception here ...
                      }
                  }
      
                  properties[property] = newValue
              }
          }
      }
      

      【讨论】:

        【解决方案8】:

        尝试使用“==”比较两个 AnyObject 类型的对象 Equatable 协议中定义的运算符导致编译错误 迅速。有没有人在不知道的情况下找到了一种方法来演奏这些对象 可用于向下转换的对象的真实类型?

        问题是,你如何比较两个任意对象的内容相等性?它没有一般的定义。如果对象是NSObject 的实例,或者至少是NSObjectProtocol,那么就有isEqual:,但对于不是的对象,你怎么办?

        【讨论】:

        • 没错,你无法与 AnyObjects 相比,Swift 的书解释了为什么 “If you create your own class or structure to represent a complex data model, for example, then the meaning of “equal to” for that class or structure is not something that Swift can guess for you.” 所以我正在寻找实现它的方法。一种方法是使用泛型,但不幸的是它们不能与 NSManagedObjects 子类一起使用。还有其他想法吗?
        • @DevGansta:我还是不明白你在说什么。你还没有定义你所说的“它”是什么意思。您一直说要比较,但我们不知道您所说的“比较”是什么意思。除非您定义它是什么,否则您无法实现某些东西。那就是问题所在。您没有提供平等的一般定义。
        • 我相信这个问题清楚地定义了问题。 Compere 对象意味着找出它们代表相同的数据,“key” == “key”,1 == 1 等。绝对清楚的是,在 swift 中,您只能比较那些符合 Equatable 协议的对象,但是我将 AnyObjects 保留在字典中,您不能进行这样的演员表:dictionary["key"] as Equatable。所以我试图找出如何在不知道它们实际类型的情况下比较对象。在我的任务中,我假设放入字典的对象符合Equatable 协议。
        • @DevGansta:你不能有Equatable类型的变量,因为它包含一个类型为Self的运算符;但这是因为即使你可以,它也没有用,因为你不能仅仅因为它们都是Equatable 而比较两个变量——它们必须都是相同的Equatable 类型。这样做的唯一方法是引入一个泛型类型参数。
        • @DevGansta:不幸的是,我不知道在这种情况下你怎么做,因为你不能使类成为通用类,因为这样就不能从 Objective-C 访问它的变量;但你也不能将下标设为通用。
        【解决方案9】:

        当我需要比较不相等但它们的内容是相等的值时,我会使用这些方法。魔术就在 compare 闭包中。当您想专注于其他事情而不是让每个班级都平等时,编写起来真的很简单。

        /*Pre requsite: Temp and AnotherTemp must be string convertible*/
        let a:AnyObject = "1"
        let b:AnyObject = Temp("2")
        let b:AnyObject = AnotherTemp(3)
        let arr:[AnyObject] = [a,b,c]
        
        Utils.has(arr,2,{"\($0)") == $1})//true or false
        
        class Utils{    
            /**
             * EXAMPLE: ["a","b","c"].has("b",{$0 == $1})//true
             */
            static func has<T,V>(_ variables:[T],_ match:V,_ method:(T,V)->Bool) -> Bool  where V:Equatable{
                return first(variables, match, method) != nil
            }
            /**
             * Think of this method as: firstOccurence of something
             * Returns the first item that matches PARAM: match according to the constraints in PARAM: method
             * EXAMPLE: ["a","b","c"].first("b",{$0 == $1})//b
             * EXAMPLE: [("a",0),("b",1)].first("b",{$0.0 == $1}).1//b
             * EXAMPLE: [(id:"a",val:0),(id:"b",val:1)].first("b",{$0.id == $1}).val//b
             * NOTE: This method should have an extension, but making an extension for two generic variables proved difficult, more research needed, for now use the ArrayParser.first method call
             * NOTE: you could do: arr.forEach{/*assert logic goes here*/} but forEach can't return early so you are forced to iterate the entire list
             */
            static func first<T,V>(_ variables:[T],_ match:V,_ method:(T,V)->Bool) -> T?  where V:Equatable{
                for item in variables{
                    if(method(item,match)){
                        return item
                    }
                }
                return nil
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2023-03-25
          • 2014-11-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-04-12
          • 1970-01-01
          相关资源
          最近更新 更多