3

I have an NSMutableSet set that contains custom made objects who are a subclass of SKNode. I am making a game where these objects are added and removed from the NSMutableSet. I am adding and removing from the main thread, so threading isn't an issue. For some reason sometimes an object isn't removed because it can't be found. The following method returns NO:

[self.set containsObject:object]

I looked into the this problem and printed the address and hash number of the object and all the objects in the NSMutableSet, and sure enough it appears in the set.

What could be the reason that the object isn't found if the address and hash numbers equal? I understand that the containsObject method uses the isEqual which compares these two values.

Ian MacDonald
  • 13,472
  • 2
  • 30
  • 51
  • It is not possible that **[self.set containsObject:object]** method removes the object, May be you are passing object reference, and after some time, due to memory issue, or ARC, object get removed, so it may not found when you try to get it by [self.set containsObject:object]. – Viral Savaj Apr 16 '15 at 17:22
  • What does `member:` return? – anon_dev1234 Apr 16 '15 at 17:30
  • Oh, I didn't mean that [self.set containsObject:object] removes the object. I tried [self.fireSet removeObject:fire] first, but it didn't remove the object, therefore I checked to see if the object is contained first. I'm using ARC but the object shouldn't be released because I see it in the NSMutableSet. – Ronen Michael Harati Apr 16 '15 at 17:32
  • If you dump the members of the set and then do `p (BOOL)[fire isEqual:0xNNNNNNNNN]` (where 0xNNNNNNN is the address of the object in the set), is it true or false? – i_am_jorf Apr 16 '15 at 17:34
  • Thanks for the correction, [self.set containsObject:object] returns NO. member returns nil. – Ronen Michael Harati Apr 16 '15 at 17:36
  • i_am_jorf by dumping you mean removing all objects? and how can I check the address directly where it's known only in runtime? – Ronen Michael Harati Apr 16 '15 at 17:58

2 Answers2

4

To use objects as elements of NSSet, keys of NSDictionary etc. they need to implement the hash method and the isEqual: method. If you don't have your own implementation, hash returns the object pointer and isEqual compares object pointers and everything will work just fine.

If you implemented hash and isEqual: yourself, you must make sure of two things: 1. The hash value and the result of isEqual most not change while the object is in the set (changing an NSMutableString that is stored in an NSSet would be a very bad idea). 2. hash and isEqual: must be consistent: Two objects that compare equal must return the same hash value.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • I haven't implemented those two methods, because I checked the hash value and it exists in the NSSet, more so isEqual returns YES if I iterate thorough the set. – Ronen Michael Harati Apr 16 '15 at 17:53
3

The result of hash must be equal and the result of isEqual: must be YES. Just matching the hash is not sufficient. Have you checked isEqual:? The default isEqual: compares object identity, not hash. hash is allowed to be used by collections to speed up comparisons, but it is only an optimization. If two objects return YES for isEqual: they must also return the same hash, but the converse is not true.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Thanks, I checked isEqual: iterating through the set and I have found a match. This is very weird because this happens after [self.set containsObject:object] returned NO for the same object! – Ronen Michael Harati Apr 16 '15 at 17:46
  • That suggests that either `isEqual:` is non-deterministic (returning `YES` in some cases and `NO` in others), or that your `hash` does not follow the rules and sometimes returns different values for equal objects. An easy test for that is to make `hash` return 1 in all cases (that is always a legal hash). – Rob Napier Apr 16 '15 at 17:48
  • I printed out the hash for cases where [self.set containsObject:object] returned NO, and they were equal, in cases where isEqual: returned YES in my iteration, shouldn't that be sufficient? – Ronen Michael Harati Apr 16 '15 at 18:05
  • Wow, I did as you recommended and returned 1 for all my objects hash, and sure enough [self.set containsObject:object] never returned NO. I would like to understand how this solved my problem, because the hashes where equal. Anyway this answer should be up voted! As it solved my problem, thanks. – Ronen Michael Harati Apr 16 '15 at 18:16
  • 2
    @RonenMichaelHarati, your problem is almost certainly that the hash has changed from what it was when you originally added the object to the set. Comparing the hashes at the time of attempted removal doesn't help. If you are actually using the same object (by pointer) and not just an equal object, then of course its hash will equal its hash. But the set has stored the object in some internal data structure based on what its hash **was** at the time it was added. If the hash has changed, then the set won't find it because it looks it up by its current hash. – Ken Thomases Apr 16 '15 at 19:33
  • Ken Thomases, thanks for the explanation, that makes sense. I would also like to know if setting all the objects hash to 1, will cause any performance issues, because I understand that distinct hashes are used for optimizations – Ronen Michael Harati Apr 16 '15 at 22:18
  • It can. I wasn't recommending using a hash of 1 as a working solution, but as a way to explore what the problem actually is. – Rob Napier Apr 16 '15 at 22:22