Unexpected behavior with NSMutableOrderedSet - objective-c

Using an NSMutableOrderedSet, I'm getting unexpected behavior.
I set an object at index 0. In the next call, I read out the object at index 0. How is it that the pointer I get back is not the same as the one I just inserted?
- (void)setRecentObject:(SomeObject *)recentObject
{
// self.activeQueue is a viable instance of NSMutableOrderedSet
[[self activeTileQueue] insertObject:recentObject atIndex:0];
SomeObject *whatIJustInserted = [[self activeTileQueue] objectAtIndex:0];
DebugLog(#"set url at zero: %#, and read back url at zero: %#",[recentObject someDescription], [whatIJustInserted someDescription]);
}
To my thinking, at the end of this method, recentObject should == whatIJustInsereted -- and yet both in my logging statement and when I evaluate the pointers after setting a breakpoint, the objects are different instances of SomeObject. Is there something I don't understand about NSMutableOrderedSet?

The reason this did not behave as expected is because the receiver activeTileQueue (the NSMutableOrderedSet instance) already contained the member.
While I specifically sought the use of a set versus an array because I didn't want duplicates of any objects inserted, my assumption about how the insertion would work in this case was incorrect. I assumed that if the set already contained the member I was attempting to insert, that rather than be re-insereted, the member would be repositioned to the index passed in -insertObjectAtIndex. This was incorrect.
If the receiver already contains the member, it is not re-inserted or re-ordered. In short, nothing happens. To obtain the originally desired behavior, I would have to either remove the original member and re-insert it, or use another method on NSMutableOrdered set to reposition/exchange the index within the set, eg:
NSIndexSet *replacementSet = [NSIndexSet indexSetWithIndex:[[self activeTileQueue] indexOfObject:recentObject]];
[[self activeTileQueue] moveObjectsAtIndexes:replacementSet toIndex:0];

Related

Is key 0 the same as null and how do I prevent it from crashing?

I have two variables of type NSMutableDictionary in a function. The function works fine but firebase-analytics show that it does crash on occasions.
In debug-mode the values of one of the variables is 0 key/value pairs. Does this mean that it is completely without value and therefore is nil? If so, how do I check whether it's nil to prevent it from crashing?
Here's the function:
-(void)setPerson:(NSMutableDictionary*)newPerson{
if (thisPerson != newPerson) {
thisPerson = newPerson;
//It Crashes Here In The NSLog... sometimes
NSLog(#"currentPerson: %#",newCurrentPerson);
}
So, thisPerson is expressivly nil at first, and newPerson is 0 key/value pair. Then thisPerson is assigned the key 0/value pair value as well. And sometimes it crashes in the NSLog. Very confusing.
In debug-mode the values of one of the variables is 0 key/value pairs.
Does this mean that it is completely without value and therefore is
nil?
No. It means, that variable contains dictionary object that completely without value and therefore it is NOT nil. It is empty NSMutableDictionary * (or may be NSDictionary * if there some error out of this function).
If so, how do I check whether it's nil to prevent it from crashing?
To check if variable is nil in Objective C you should simply do:
if (newPerson == nil) {
// do something with nil case
}
But it is probably not that you need.
More about nil:
//It Crashes Here In The NSLog... sometimes
NSLog(#"currentPerson: %#",newCurrentPerson);
If you have implicit assumption that NSLog(#"currentPerson: %#",nil); cause crash - then it is wrong. NSLog handles nil perfectly fine in such case.
So I believe that problem somewhere out of code you posted in question.

Curious about NSButtonCell state

I have a NSTableView with an NSTableColumn with an NSButtonCell (a checkbox) inside it, which generates a new instance of NSButtonCell each time a row is added, which I configured in IB. However, I'm curious why in the following chunk of code the second NSLog returns 0.
NSLog(#"%ld", (long)[[self.tableView preparedCellAtColumn:0 row:0]state]);
[[self.tableView preparedCellAtColumn:0 row:0]setState:1];
NSLog(#"%ld", (long)[[self.tableView preparedCellAtColumn:0 row:0]state]);
The fact that it returns 0 means that I am sending a message to an instance of NSButtonCell, right? So why doesn't setState: change the return value of the second NSLog?
If it was receiving nil it would also print 0, I would suggest trying this
NSLog(#"cell:%#", [self.tableView preparedCellAtColumn:0 row:0])
to ensure you are actually getting a valid cell object from the table view.
Where are you calling that code from? After the table is already being displayed? During initialisation?
If the former then there should be a cell available, if the latter then it may not have been created or reallocated from the pool yet.
Try the NSLog command above to ensure you are actually getting a cell back and not nil from the table view.

Does NSMutableSet's addObject method replace a duplicate or ignore insertion?

I am struggling to figure if whether adding an already existing object to a set in Cocoa actually replaces the object or simply ignores addObject: if there is a duplicate. I am using a custom object that is considered the same as another object if a specific field is equal.
I am overriding both isEqual: and hash methods and containsObject: does return true when I call it, but I would like to update the set with the new object and for some reason it doesn't work if I call addObject:.
If it does ignore it, what would be the best data structure to use in place of NSMutableSet in order to have the desired effect?
From the description, it ignores if there is a duplicate.
Adds a given object to the set, if it is not already a member.
You could cast the NSMutableSet to a CFMutableSet, which has methods that allows greater control on how to add objects (you want CFSetSetValue):
CFSetSetValue: The value to be set in theSet. If this value already exists in theSet, it is replaced.
CFSetAddValue: If value already exists in the collection, this function returns without doing anything.
CFSetReplaceValue: If this value does not already exist in theSet, the function does nothing.
NSMutableSet's -addObject: method will not add an object which passes the -member: test, as such it doesn't do anything.
If you still want to update the set with the new object regardless, you can use an NSMutableArray, on which you'd call -replaceObjectAtIndex:withObject::
#interface NSMutableArray(JRAdditions)
- (void)addUniqueObject:(id)object;
#end
#implementation NSMutableArray(JRAdditions)
- (void)addUniqueObject:(id)object {
NSUInteger existingIndex = [self indexOfObject:object];
if(existingIndex == NSNotFound) {
[self addObject:object];
return;
}
[self replaceObjectAtIndex:existingIndex withObject:object];
}
#end
It returns without doing anything. NSMutableSet is toll-free bridged to CFMutableSet, and from the docs for that class:
If value already exists in the collection, this function returns
without doing anything.
I'm sorry, but you are missing the idea of "equality". If you need to add an object to a set because it's different, then the "is equal" operator needs to return it is different.
The ONLY valid time to replace an object in a set would be if it was entirely identical. Basic set semantics.
What you are describing is not a set if you are trying to replace an object with a different object. That is a different type of keyed collection. I would probably suggest the most logical one being a basic dictionary. key = value. So then you just assign the dictionary["objectId"] = object and it will naturally add or replace.
You need to use the right tool for the job.

Why does NSOrderedMutableSet add objects whose data is the same?

I'm creating objects and adding them to a set using -[NSOrderedMutableSet addObject:], but I discovered that only duplicates of the objects themselves are checked for -- the object pointer's address presumably, and that it's possible to add multiple objects that have identical content.
For example:
SomeObject* object = [SomeObject alloc] initWithStuff:stuff];
SomeObject* object2 = [SomeObject alloc] initWithStuff:stuff];
[set addObject:object];
[set addObject:object];
[set addObject:object1];
[set addObject:object2];
The count will be 2.
This makes me wonder what the point of these classes is? Under what circumstances might one have an object and not know if the object itself had already been added to a collection, rather than the data contained within the object?
Whats the easiest way (or what class should I use) to use to ensure the set only contains one of each object based on content?
The way you are looking is the right way, you are forgetting a small detail: how could the NSMutableOrderedSet class know about which instances of SomeObject contain same values?
The answer is simple: you must provide your own implementations of
- (BOOL)isEqual:(id)anObject
- (NSUInteger)hash
So that your instances will return true when compared with same internal values, and two instances with same data will have same hashcode.
Apart from this sets are rather useful because they give you better complexity on checking if an instance is contained in a set or not, and you can quickly do many logical operations on them, like intersection, union, difference and whatever.
If it is a custom object you have, you'd have to implement your own isEqual: and hash method to check for equality and prevent duplicates in the set.

NSManagedObjectContext returns YES for hasChanges when there are none

I created a separate NSManagedObjectContext on a separate thread to perform some store maintenance. However, I have noticed that the context returns YES for hasChanges as soon as a managed object in it is even referenced e.g.
NSString *name = managedObject.name;
This context is created and used exclusively in 1 method. Why is it returning has changes, when there there are none?
That ks difficult to answer without seeing the code. Perhaps your object has a -awakeFromFetch call that touches a property or something else. Normally there should be no changes from just fetching an object unless you are doing something to that object either in the awakeFromFetch or somewhere else in your code.
update
Before the save, grab the deleted array, updated array and inserted array and take a peek at them. That will give you a hint as to what is going on.