Cocoa class to reference many entries by a single key - cocoa-touch

I need a Cocoa data structure to keep something like a list of locations of a set of insects in a field over time. Clearly an NSDictionary has to have unique keys, so I couldn't use the insect as a key and MKUserlocation as the object.
Is there any other class besides NSArray where one does not have to step through, if/else-ing at each index?
There seems to be such a thing as NSList discussed in various places but nothing in the Apple docs.
I also saw a reference to NSIndexSet one uses in conjunction to an NSArray, but again reading the Apple docs suggests it's not useful to me.

Since Cocoa framework does not offer a multimap container, you could implement your own by creating an NSMutableDictionary, and populating it with NSMutableArray objects.
NSMutableDictionary locByInsect = [NSMutableDictionary dictionary];
// Adding a location for an insect:
MyInsect *insect = ... // Don't forget to provide hash code and equality checks
MyLocation *location = ...
NSMutableDictionary *insectLocations = locByInsect[insect];
if (insectLocations == nil) {
insectLocations = [NSMutableArray array];
locByInsect[insect] = insectLocations;
}
[insectLocations addObject:location];
// Retrieving all locations for an insect
for (MyLocation *loc in locByInsect[insect]) {
NSLog(#"Saw %# at %#", insect, loc);
}

For each insect have a key in an NSMutableDictionary. Make each key's object an NSMutableArray of locations.

Related

Using SKNode objects for keys in NSDictionary? (Compare issue)

Objective-C
Consider this code:
SKNode *n1 = [SKNode new];
SKNode *n2 = [SKNode new];
NSMutableDictionary *d = [NSMutableDictionary new];
[d setObject:#"A" forKey:n1];
[d setObject:#"B" forKey:n2];
if ([d objectForKey:n1]) {
NSLog(#"true");
}
I'm using SKNode as keys in NSDictionary. I need to test if a given SKNode exists as a Key. The common approaches for testing don't seem to work, as above the result will not return (or print) "true". Neither will the following code:
if ([d allKeys] containsObject:n1]) {
NSLog(#"true");
}
If I make the keys strings; i.e #"key1", #"key2", "true" is printed.
Is it possible to test in this manner?
(It occurred to me that what I wrote as a comment was actually the answer to the question.)
NSDictionary copies its keys (using -copy from the NSCopying protocol). SKNode conforms to NSCopying, but copying a node returns a new node that isn't equal to the old one:
SKNode *a = [SKNode new];
SKNode *b = [a copy];
[a isEqual:b]; // NO
let a = SKNode()
let b = a.copy() as! SKNode
a == b // false
So it's not safe to use SKNodes as keys in a dictionary. As for workarounds: you could perhaps use the node's name as a key (if your nodes' names are unique). You could also use NSMapTable, which doesn't copy its keys.
Your code should work. So something is wrong here, possibly with the implementation of the hashcode and isEqual: method of SKNode.
Log the hash code of your two keys, check what isEqual: returns, log the dictionary and all the keys, and check for anything unexpected.
PS. It turns jtbandes answer is exactly the right one. Of course following my advice and logging everything you would have found that the keys in allKeys are not what's expected, with a fighting chance of figuring out the answer.
So if you have a class with trivial hashcode and isEqual, just taking the pointer as hashcode and comparing pointers for isEqual, then either you have a class where copy creates a new object and a dictionary won't work, or you have a class where copy just returns the original object and it works fine.

Creating local objects, prefrence or simply better?

Is it better to create a local object for later use like
NSDictionary *dic = [NSDictionary Dictionary];
or
NSDictionary * dic = nil;
Is it preference thing or is one better then the other?
it's not like 'the one is better', it's like 'the other is bad'.
If you're going to assign a new object to it later, initialize it to nil, else (you leak memory by losing the reference to the first object created by error.) - EDIT: no, you're not leaking memory (either because of the autorelease or the automatic reference counting, but anyway, that's an extra unneeded method call.) That is bad.
If it's a mutable collection, create it before you use it, else it will continue being nil and ignoring essentially all messages sent to it, which is also bad.
Conclusion: it's not a matter of preference - you must think logically and choose whichever is suited for the specific purpose you are using it for.
If you will use that object later, then you should instantiate it with the first option. If you will have to create an object in some if-else block where you will be reinitializing it with some custom values, then the second option is the way to go.
For example the first option:
NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i < 5; i++) {
[arr addObject:#"string"];
}
or
NSDictionary *dictionary = nil;
BOOL flag;
if (flag) {
dictionary = [NSDictionary dictionaryWithObject:#"string" forKey:#"myKey"];
}
else {
NSArray *objects;
NSArray *keys;
dictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
}

Getting an object from an NSSet

If you can't get an object with objectAtIndex: from an NSSet then how do you retrieve objects?
There are several use cases for a set. You could enumerate through (e.g. with enumerateObjectsUsingBlock or NSFastEnumeration), call containsObject to test for membership, use anyObject to get a member (not random), or convert it to an array (in no particular order) with allObjects.
A set is appropriate when you don't want duplicates, don't care about order, and want fast membership testing.
NSSet doesn't have a method objectAtIndex:
Try calling allObjects which returns an NSArray of all the objects.
it is possible to use filteredSetUsingPredicate if you have some kind of unique identifier to select the object you need.
First create the predicate (assuming your unique id in the object is called "identifier" and it is an NSString):
NSPredicate *myPredicate = [NSPredicate predicateWithFormat:#"identifier == %#", identifier];
And then choose the object using the predicate:
NSObject *myChosenObject = [mySet filteredSetUsingPredicate:myPredicate].anyObject;
NSArray *myArray = [myNSSet allObjects];
MyObject *object = [myArray objectAtIndex:(NSUInteger *)]
replace NSUInteger with the index of your desired object.
For Swift3 & iOS10 :
//your current set
let mySet : NSSet
//targetted index
let index : Int
//get object in set at index
let object = mySet.allObjects[index]
NSSet uses the method isEqual: (which the objects you put into that set must override, in addition, the hash method) to determine if an object is inside of it.
So, for example if you have a data model that defines its uniqueness by an id value (say the property is:
#property NSUInteger objectID;
then you'd implement isEqual: as
- (BOOL)isEqual:(id)object
{
return (self.objectID == [object objectID]);
}
and you could implement hash:
- (NSUInteger)hash
{
return self.objectID; // to be honest, I just do what Apple tells me to here
// because I've forgotten how Sets are implemented under the hood
}
Then, you can get an object with that ID (as well as check for whether it's in the NSSet) with:
MyObject *testObject = [[MyObject alloc] init];
testObject.objectID = 5; // for example.
// I presume your object has more properties which you don't need to set here
// because it's objectID that defines uniqueness (see isEqual: above)
MyObject *existingObject = [mySet member: testObject];
// now you've either got it or existingObject is nil
But yeah, the only way to get something out of a NSSet is by considering that which defines its uniqueness in the first place.
I haven't tested what's faster, but I avoid using enumeration because that might be linear whereas using the member: method would be much faster. That's one of the reasons to prefer the use of NSSet instead of NSArray.
for (id currentElement in mySet)
{
// ** some actions with currentElement
}
Most of the time you don't care about getting one particular object from a set. You care about testing to see if a set contains an object. That's what sets are good for. When you want to see if an object is in a collection sets are much faster than arrays.
If you don't care about which object you get, use -anyObject which just gives you one object from the set, like putting your hand in a bag and grabbing something.
Dog *aDog = [dogs anyObject]; // dogs is an NSSet of Dog objects
If you care about what object you get, use -member which gives you back the object, or nil if it's not in the set. You need to already have the object before you call it.
Dog *spot = [Dog dogWithName:#"Spot"];
// ...
Dog *aDog = [dogs member:spot]; // Returns the same object as above
Here's some code you can run in Xcode to understand more
NSString *one = #"One";
NSString *two = #"Two";
NSString *three = #"Three";
NSSet *set = [NSSet setWithObjects:one, two, three, nil];
// Can't use Objective-C literals to create a set.
// Incompatible pointer types initializing 'NSSet *' with an expression of type 'NSArray *'
// NSSet *set = #[one, two, three];
NSLog(#"Set: %#", set);
// Prints looking just like an array but is actually not in any order
//Set: {(
// One,
// Two,
// Three
// )}
// Get a random object
NSString *random = [set anyObject];
NSLog(#"Random: %#", random); // Random: One
// Iterate through objects. Again, although it prints in order, the order is a lie
for (NSString *aString in set) {
NSLog(#"A String: %#", aString);
}
// Get an array from the set
NSArray *array = [set allObjects];
NSLog(#"Array: %#", array);
// Check for an object
if ([set containsObject:two]) {
NSLog(#"Set contains two");
}
// Check whether a set contains an object and return that object if it does (nil if not)
NSString *aTwo = [set member:two];
if (aTwo) {
NSLog(#"Set contains: %#", aTwo);
}

Save part of NSDictionary

I have a NSDictionary with a NSString and NSArray
I have to save only the NSArray in a variable without knowing the key.
Is this possible?
If I'm understanding you correctly, you have a dictionary that contains both an NSString and an NSArray, and you want to extract just the NSArray, without knowing what the key is.
One way to do that is to look through the dictionary with fast enumeration:
NSString *key;
for(key in someDictionary){
id someObject = [someDictionary objectForKey: key];
}
and then look at the objects to see which one is an NSArray:
if ([someObject isKindOfClass:[NSArray class]]) {
// do something with the array
}
(obligatory warning: explicitly checking an object's class is often a sign of a flawed design. In most cases, you should be checking for behavior (-respondsToSelector), not class identity)

Unique keys in NSDictionary - how to check given keys are copies?

I have an NSDictionary in which I use my own classes (NSObject subclasses) as keys and would like to make sure that I do not include the same key twice. However, because NSDictionary copies its keys, if I try to check whether an object is in the dictionary, it never thinks it is.
For example,
MyClass* obj = [[MyClass alloc] init];
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
[dict setObject:someObj forKey:obj];
if ([[dict allKeys] contains:obj]) // always returns false
// obj is already in dict
else
// add obj to dict etc.
Similarly, if I want to change the object associated with this key, it seems to create a new entry.
// dict is empty
// say [obj description] gives 'MyClass : 0x1' - (impossible address?)
[dict setObject:someObj forKey:obj];
// dict: { 'MyClass : 0x2' = someObjDesc }
[dict setObject:someOtherObj forKey:obj];
// dict: { 'MyClass : 0x2' = someObjDesc , 'MyClass : 0x3' = someOtherObjDesc }
Also, this same thing leads to not being able to access the items in the dictionary from the original object
[dict setObject:someObj forKey:obj];
[dict objectForKey:obj]; // returns null
So, as far as the uniqueness is concerned, would I be best off keeping track of the keys in a separate array or is there a better way of doing this.
I considered implementing an isEqual method based on a unique variable (such as a name) but didn't think that was the Right Thing to do.
Background (in case it turns out that maybe I'm just using the wrong thing entirely):
I want to keep track of information about a group of people going to different places. So each person at each place has some info. What I've done is used nested dictionaries so the key to the main dictionary is a Person object and the object another dictionary. This latter dictionary has key Place and info as the object. I think this is Java syntax but something like > (the array holds the info). I want to be able to add a Person only if the don't already exist, add a Place (for each person), change the array.
Any help on any of this would be greatly appreciated!
You should always use NSStrings as keys for dictionaries, especially if you are new at objective-C. There are a few things that I can see you are doing wrong with your current implementation - you would need to read up on key requirements for NSDictionaries.
You can do what you want with strings as keys - person's name, etc.
The objects in a dictionary have all the info about a certain person:
NSDictionary* personsInfo = [mainDict objectForKey:#"Jane Smith"];
NSString* addressLine1 = [personsInfo objectForKey#"addressLine1"];
--Tom
The simple answer would be to make it so that the MyClass doesn't actually copy anything.
That would be something like changing:
- (id) copyWithZone:(NSZone *)zone {
MyClass * foo = (MyClass *)[super copyWithZone:zone];
[foo configureCopy];
return foo;
}
To:
- (id) copyWithZone:(NSZone *)zone {
return [self retain];
}