[swift-users] Set with element of NSObject subclasses didn't work as expected

Hooman Mehr hooman at mac.com
Wed Mar 29 12:13:57 CDT 2017


Yes, this is a serious shortcoming in the transition to Swift as the main application programming language for Apple platforms.

With the existing architecture (lack of stable Swift ABI) system frameworks and any binary distributed frameworks still have to be written in Objective-C. If you are already an experienced developer of Apple platforms, you already know the behavior and expectations of ObjC runtime and NSObject family. But new developers are coming to the platform everyday and now they are starting with Swift. This really needs to be better addressed as more people start developing for Apple platforms using Swift with no prior knowledge of ObjC runtime and NSObject.

I think the best way to draw some attention to this is by filing bug reports on lack of adequate documentation, at least.

Also, please note that if you want to define operators that act on class hierarchies, you should put all of the logic of the operator in a method and have the operator simply delegate to the method to preserve (some) polymorphism. Although infix operators can’t easily be fully polymorphic, given the lack of multiple (or at least dual) dispatch. The problematic part of getting Equatable right in this case is that any instance of a subclass by definition should work as an instance of its superclass and this can create bugs if you are not careful. `Equatable` protocol requires both sides of the equality test to have identical type for the mathematical definition of equality to hold. In practice, you may need to relax this in some situations, but you should be fully aware of what you are doing.

> On Mar 29, 2017, at 2:36 AM, Zhao Xin via swift-users <swift-users at swift.org> wrote:
> 
> Now I understand it. What I don't understand is why there is nowhere metioning this. All docs are talking about Hashable, clearly that NSObject didn't rely on it.
> 
> Zhaoxin
> 
> Get Outlook for iOS <https://aka.ms/o0ukef>
> From: Saagar Jha <saagar at saagarjha.com>
> Sent: Wednesday, March 29, 2017 11:47:51 AM
> To: Zhao Xin
> Cc: Swift Users List
> Subject: Re: [swift-users] Set with element of NSObject subclasses didn't work as expected
>  
> NSObject’s hash (which Set uses) is done via ObjectIdentifier, which IIRC uses something along the lines of the object's address. That’s why you’re getting the behavior you’re seeing.
> 
> Saagar Jha
> 
>> On Mar 28, 2017, at 20:20, Zhao Xin via swift-users <swift-users at swift.org <mailto:swift-users at swift.org>> wrote:
>> 
>> Turns out that for `NSObject`, protocol `Equatable` wasn't used. Instead, it used `NSObjectProtocol.isEqual(_ object: Any?)`. Also, override `func isEqual(_ object: Any?) -> Bool` requires to override `var hash: Int { get }` as well.
>> 
>> I think this behavior should be mentioned in Swift docs or manual in `Set` section.
>> 
>> Below code works. 
>> 
>> class Bar:NSObject {
>>     let value:Int
>>     
>>     override public var hashValue: Int { return value }
>>     public static func ==(lhs: Bar, rhs: Bar) -> Bool {
>>         return lhs.value == rhs.value
>>     }
>>     // required by NSObjectProtocol
>>     override func isEqual(_ object: Any?) -> Bool {
>>         if let rhs = object as? Bar {
>>             return self == rhs
>>         }
>>         return false
>>     }
>>     override var hash: Int { return self.hashValue }
>>     
>>     init(_ value:Int) {
>>         self.value = value
>>     }
>> }
>> 
>> let barSetA:Set = [Bar(8), Bar(9)]
>> let barSetB:Set = [Bar(9), Bar(10)]
>> let barResultC = barSetA.intersection(barSetB) // {{NSObject, value 9}}
>> let barResultD = barSetA.subtracting(barSetB) // {{NSObject, value 8}}
>> 
>> Gladly I find it here <http://stackoverflow.com/questions/32726524/swift-2-0-set-not-working-as-expected-when-containing-nsobject-subclass>. 
>> 
>> Zhaoxin
>> 
>> 
>> 
>> On Wed, Mar 29, 2017 at 3:50 AM, Zhao Xin <owenzx at gmail.com <mailto:owenzx at gmail.com>> wrote:
>> Please see the code first.
>> 
>> import Foundation
>> 
>> class Foo:Hashable {
>>     let value:Int
>>     
>>     public var hashValue: Int { return value }
>>     public static func ==(lhs: Foo, rhs: Foo) -> Bool {
>>         return lhs.value == rhs.value
>>     }
>>     
>>     init(_ value:Int) {
>>         self.value = value
>>     }
>> }
>> 
>> let fooSetA:Set = [Foo(8), Foo(9)]
>> let fooSetB:Set = [Foo(9), Foo(10)]
>> let fooResultC = fooSetA.intersection(fooSetB) // {{value 9}}
>> let fooResultD = fooSetA.subtracting(fooSetB) // {{value 8}}
>> 
>> 
>> class Bar:NSObject {
>>     let value:Int
>>     
>>     override public var hashValue: Int { return value }
>>     public static func ==(lhs: Bar, rhs: Bar) -> Bool {
>>         return lhs.value == rhs.value
>>     }
>>     
>>     init(_ value:Int) {
>>         self.value = value
>>     }
>> }
>> 
>> let barSetA:Set = [Bar(8), Bar(9)]
>> let barSetB:Set = [Bar(9), Bar(10)]
>> let barResultC = barSetA.intersection(barSetB) // Set([])
>> let barResultD = barSetA.subtracting(barSetB) // {{NSObject, value 9}, {NSObject, value 8}}
>> 
>> 
>> Behaviors of `func intersection(Set<Set.Element>)` and `func subtracting(Set<Set.Element>)` were different between normal Swift class and NSObject subclasses. I had thought they should be the same. It seemed that Set<NSObject> relied on addresses of NSObject instances instead of their hashValues. That made the Set useless.
>> 
>> Swift version: 3.1 (swiftlang-802.0.48 clang-802.0.48)
>> Xcode 8.3 (8E162)
>> 
>> Zhaoxin
>> 
>> 
>> 
>> 
>> 
>> 
>> 
>> 
>> 
>> 
>> 
>> _______________________________________________
>> swift-users mailing list
>> swift-users at swift.org <mailto:swift-users at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-users
> 
> _______________________________________________
> swift-users mailing list
> swift-users at swift.org
> https://lists.swift.org/mailman/listinfo/swift-users

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20170329/50b7cb9c/attachment.html>


More information about the swift-users mailing list