Ошибка с оператором equals и NSObjects в Swift 2.0?

Хорошо, что-то странное происходит при написании собственного оператора equals для подклассов NSObject в Swift 2.0 следующим образом:

func ==(lhs: MyObject, rhs: MyObject) -> Bool {
    return lhs.identifier == rhs.identifier
}

Для класса, который выглядит так:

class MyObject: NSObject {
    let identifier: String
    init(identifier: String) {
        self.identifier = identifier
    }
}

Это очень хорошо работает в Swift 1.2 и ниже. Это все еще вроде работы:

let myObject1 = MyObject(identifier: "A")
let myObject2 = MyObject(identifier: "A")
let result = (myObject1 == myObject2)
// result is true

До сих пор так хорошо, но что, если обе переменные были опциями?

let myObject1: MyObject? = MyObject(identifier: "A")
let myObject2: MyObject? = MyObject(identifier: "A")
let result = (myObject1 == myObject2)
// result is false, equals operator was never even called

И еще одна вещь, которая больше не работает:

let myObject1 = MyObject(identifier: "A")
let myObject2 = MyObject(identifier: "A")
let result = (myObject1 == myObject2)
// result is true
let result = (myObject1 != myObject2)
// result is true, equals operator was never even called

Таким образом, очевидно,!= больше не вызывает оператор == и отрицает его. Кажется, что просто сравнивайте экземпляры при использовании!=

Все это происходит только тогда, когда ваш класс является подклассом NSObject (прямо или косвенно). Когда это не так, все работает так, как вы ожидали.

Может ли кто-нибудь сказать мне, является ли это новой "функцией" в Swift 2.0 или просто неприятной ошибкой?

Ответ 1

К сожалению, я не знаю, считается ли это признаком или нет (я так не думаю). Эта проблема возникает, если какой-либо класс подклассы класса, который соответствует Equatable (например, NSObject; он сравнивает фактические экземпляры). Поэтому, если вы только "переопределите" оператор == подкласса, все остальные операторы, такие как:

func !=<T : Equatable>(lhs: T, rhs: T) -> Bool
func ==<T : Equatable>(lhs: T?, rhs: T?) -> Bool
func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool

где T ограничено Equatable Swift использует оператор == базового класса. В качестве временного решения вы можете перегрузить все операторы равенства, которые вы должны использовать так:

func !=(lhs: MyObject, rhs: MyObject) -> Bool { ... }
func ==(lhs: MyObject?, rhs: MyObject?) -> Bool { ... }
func ==(lhs: [MyObject], rhs: [MyObject]) -> Bool { ... }

Изменить: Причина

Причиной такого поведения является то, что если подкласс соответствует Equatable, то Self для самостоятельного требования определяется как этот класс. Поэтому каждый раз, когда == вызывается с (общим) типом, который соответствует Equatable, он вызывает только оператор исходного соответствующего класса.

Ответ 2

Я думаю, что это поведение следует считать ошибкой (все еще присутствующей на Xcode 7 beta 6), но есть, надеюсь, временное обходное решение: переопределить NSObject -isEqual вместо реализации оператора Swift ==.

class MyObject: NSObject {
    let identifier: String
    init(identifier: String) {
        self.identifier = identifier
    }
    override func isEqual(object: AnyObject?) -> Bool {
        guard let rhs = object as? MyObject else {
            return false
        }
        let lhs = self

        return lhs.identifier == rhs.identifier
    }
}

Я нашел еще одну ссылку на проблему с более примерами кода здесь: http://mgrebenets.github.io/swift/2015/06/21/equatable-nsobject-with-swift-2/

Ответ 3

ответ kylealanhale не работает с NSManagedObject (пояснил здесь), поэтому я создал новый протокол NSObjectSubclassEquatable, который можно использовать для сравнения подклассов NSobject.

infix operator =~= {}

public protocol NSObjectSubclassEquatable {

  static func compare(lhs: Self,_ rhs: Self) -> Bool
}


public func =~=<T : NSObjectSubclassEquatable>(lhs: T, rhs: T) -> Bool {

  return T.compare(lhs, rhs)
}

func =~=<Element : NSObjectSubclassEquatable>(lhs: [Element], rhs: [Element]) -> Bool {
  for (lhsElement,rhsElement) in zip(lhs, rhs) {
    if !(lhsElement =~= rhsElement) {
      return false
    }
  }
  return true
}

Пример:

class Point: NSObject {

  var x: Int
  var y: Int

  init(_ x: Int,_ y: Int) {
    self.x = x
    self.y = y
  }
}

extension Point: NSObjectSubclassEquatable {

  static func compare(lhs: Point,_ rhs: Point) -> Bool {
    return lhs.x == rhs.x && lhs.y == rhs.y
  }
}

Point(1,2) =~= Point(1,2) // Prints true
[Point(1,2)] =~= [Point(1,2)] // Prints true