2015-09-22 1 views
6

После обновления нашей кодовой базы до Swift2 я столкнулся с необычной проблемой. Набор не вычитает и не объединяет, как ожидалось.Swift 2.0 Set работает не так, как ожидалось, когда содержится подкласс NSObject

class A: NSObject { 
    let h: Int 

    init(h: Int) { 
     self.h = h 
    } 

    override var hashValue: Int { 
     return h 
    } 
} 

func ==(lhs: A, rhs: A) -> Bool { 
    return lhs.hashValue == rhs.hashValue 
} 

let a = A(h: 1) 
let b = A(h: 1) 

var sa = Set([a]) 
let sb = Set([b]) 

sa.subtract(sb).count // Swift1.2 prints 0, Swift 2 prints 1 

sa.contains(a) // Swift1.2 true, Swift 2 true 
sa.contains(b) // Swift1.2 true, Swift 2 false 

Похоже, что новый набор не использует hashValue для внутренних операций. Любая идея заключается в том, что ошибка или способ обхода этой проблемы?

ответ

11

Я немного играл со своим кодом. Я был в состоянии заставить его работать как больше не подклассов NSObject, но вместо того, чтобы в соответствии с протоколом Hashable:

class A: Hashable { 
    let h: Int 

    init(h: Int) { 
     self.h = h 
    } 

    var hashValue: Int { 
     return h 
    } 

} 

func ==(lhs: A, rhs: A) -> Bool { 
    return lhs.hashValue == rhs.hashValue 
} 

let a = A(h: 1) 
let b = A(h: 1) 

var sa = Set([a]) 
let sb = Set([b]) 

sa.subtract(sb).count // Swift1.2 prints 0, Swift 2 prints 1 

sa.contains(a) // Swift1.2 true, Swift 2 true 
sa.contains(b) // Swift1.2 true, Swift 2 false 

a.hashValue == b.hashValue 

Когда вы унаследовав от NSObject, ваша перегрузка == была на самом деле не выполняется. Если вы хотите, чтобы работать с NSObject, вы должны переопределить isEquals:

override func isEqual(object: AnyObject?) -> Bool { 
    if let object = object as? A { 
     return object.h == self.h 
    } else { 
     return false 
    } 
} 
+0

Спасибо! У меня возникла проблема с подклассом MKAnnotation (который также должен расширять NSObject). У вас есть ссылка на документацию по этому вопросу? – brki

+1

Оба isEqual и hashValue, кажется, необходимы, чтобы сделать Set правильно с NSObject в Swift 3 –

2
//: Playground - noun: a place where people can play 

import Foundation 

class X: NSObject { 
    var x:String 
    var y:Int 

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

    override var hashValue: Int { 
     return x.hashValue^y.hashValue 
    } 

    override func isEqual(object: AnyObject?) -> Bool { 
     if let rhs = object as? X { 
      return x == rhs.x && y == rhs.y 
     } else { 
      return false 
     } 
    } 
} 

func == (lhs:X, rhs:X) -> Bool { 
    return lhs.isEqual(rhs) 
} 

let x = X(x: "x1", y: 1) 
let y = X(x: "x1", y: 1) 
X(x: "x1", y: 1) == X(x: "x1", y: 1) // Swift 'x == y' (true) 
x.isEqual(y)       // Obj-C '[x isEqual: y]' (true) 

var s:Set<X> = [X(x: "x1", y: 1)] 
s.count // count == 1 
s.insert(X(x: "x2", y: 1)) 
s.count // count == 2 
s.insert(X(x: "x1", y: 1)) 
s.count // count == 2 
s.insert(X(x: "x2", y: 1)) 
s.count // count == 2 

Я проводил время в поисках правильного ответа на это, пока я не попал на этот вопрос/ответ. Я вернулась к основам игровой площадки XCode, чтобы посмотреть, что происходит. Использование подклассов NSObject в Swift Set делает кучу более удобочитаемого кода.

+0

Я не знаю, почему так много людей выбрали другой ответ. Для меня это ценный и правильный. Большое спасибо. –

+0

Как раз упомянуть, это необходимо в 'NSObjectProtocol', если вы переопределите' func isEqual (_ object: Any?) -> Bool', вы должны переопределить 'var hash: Int {get}'. Как и в документах, он говорит: «Если два объекта равны (как определено методом isEqual (_ :)), они должны иметь одно и то же значение хэш-функции. '. https://developer.apple.com/reference/objectivec/nsobjectprotocol/1418859-hash –