If I have two string objects that both have value "hi", and I add them to an NSMutableSet, are they treated as different objects or not? Thanks a bunch!
Beside the correct comment of #rmaddy, there is a fundamental problem with it: It is simply mostly impossible to guarantee that no objects are equal in a set. (It is easy to guarantee that the are not identical.) This would imply to recheck the equaty of all objects (expensive), when one changes (mostly impossible to detect). This is, because NSSet does not copy its content objects.
Let's have an example:
NSMutableString *first = [#"Amin" mutableCopy];
NSMutableString *second = [#"Amin Negm" mutableCopy];
NSSet *set = [NSSet setWithObjects:first, second];
[first appendString:#" Negm"];
Both objects are equal than, but none is removed. (Which one?)
Related
Just looking for some simple advice on how best to optimize a for loop (in terms of memory usage) in Obj-C and ARC taking this non-optimized arbitrary code as:
NSMutableArray *array = [NSMutableArray array];
for (NSDictionary *dict in NSArray *arrayOfDicts) {
NSString *first = [dict valueForKeyPath:#"object.first"];
NSString *second = [dict valueForKeyPath:#"object.second"];
NSString *third = [dict valueForKeyPath:#"object.third"];
[array addObject:#[first, second, third]];
}
Which of these is better/right in practice (assuming many loops so it might matter)
1) Declare once, copy into array.
NSMutableArray *array = [NSMutableArray array];
NSString *first, *second, *third;
for (NSDictionary *dict in NSArray *arrayOfDicts) {
first = [dict valueForKeyPath:#"object.first"];
second = [dict valueForKeyPath:#"object.second"];
third = [dict valueForKeyPath:#"object.third"];
[array addObject:#[[first copy], [second copy], [third copy]]];
first, second, third = nil;
}
2) Autoreleasepool
NSMutableArray *array = [NSMutableArray array];
for (NSDictionary *dict in NSArray *arrayOfDicts) {
#autoreleasepool {
NSString *first = [dict valueForKeyPath:#"object.first"];
NSString *second = [dict valueForKeyPath:#"object.second"];
NSString *third = [dict valueForKeyPath:#"object.third"];
[array addObject:#[first, second, third]];
}
}
3) Something else?
first, second and third survive beyond the loop in all situations regardless because they're in dict. The array of #[first, second, third] survives regardless because it has been added to the array.
Hence there's going to be very limited difference between the three. valueForKeyPath: doesn't produce a copy so all you're talking about is (i) storage within the retain count table*; and (ii) storage within the autorelease pool.
(i) is not just negligible but so internal to the current implementation of the runtime that there's no point relying on it. (ii) is also negligible but explicitly required by the spec.
Technically the #autoreleasepool will probably be slightly more compact (depending on the total size of an empty pool versus the size required to add an object to the pool versus the length of your array) but I wouldn't obsess over it.
There's absolutely no difference between the first two options. How local the storage is doesn't affect ARC — even without relying on exact ARC implementation details, you're reassigning at the top of the next iteration regardless.
(* retain counts aren't stored until they're greater than 1 as 1 is a very common value and is implied by an object existing at all; hence they go in a separate table and not with the object)
2018 edit: the 64-bit runtime actually stores retain counts greater than 1 but less than a very big number in part of the isa pointer, since the whole 64-bit range isn't currently needed. And if it ever is, they can just move the retain count out again. The very big number was 2^19+1 in the original 64-bit runtime, but it's opaquely sized so may have changed since. So additional retains essentially never increase memory footprint on modern devices.
The biggest speed improvement by far while be to remove the free calls to valueForKeyPath.
valueForKeyPath is given a string argument, for example object.first. It then has to analyze the string: Find the dot in the middle, figure out that the receiver is a dictionary, that "object" doesn't start with an # character so it corresponds to objectForKey and so on. All that three times. Instead
for (NSDictionary *dict in NSArray *arrayOfDicts) {
SomeObject* someObject = [dict objectForKey:#"object"];
[array addObject:#[[someObject.first copy],
[someObject.second copy],
[someObject.third copy]]];
}
It's up to you to decide whether copy is necessary. Objects often have "copy" properties so first, second, third might be copies already.
I have a NSMutableDictionary with NSNumbers. When I finish building the set I need to recalculate all the values using the currently stored value itself. Now I'm using fast enumeration and storing into a new NSMutableSet, but I'm not experienced in Objective C and there must be a more efficient way to do this:
for (id key in temp_target_results)
{
formula_score = MyFormula([[temp_target_results objectForKey:key] doubleValue]);
[target_results setObject:[NSNumber numberWithDouble:formula_score] forKey:key];
}
In the end I'm sorting by value (that's why I'm using NSMutableSet).
I don't know how much more anyone can help you optimize what you are doing because I don't know what's going on behind the scenes (outside of the context of the snippet of code you pasted above, or what's really going on inside your MyFormula function).
One optimization question I have would be: why are you storing everything as NSNumber objects anyways and not an array of doubles? The only advantage (that I can currently see) to doing that is if you're passing your NSNumber objects along in an NSArray, NSSet or NSDictionary that gets written out to disk or passed along in an NSNotification or.
But some of the things I would do would include getting rid of that extra, unnecessary call to objectForKey:
for (NSNumber * myNumber in temp_target_results)
{
formula_score = MyFormula([myNumber doubleValue]);
[target_results setObject:[NSNumber numberWithDouble:formula_score] forKey:[myNumber stringValue]];
}
Another consideration here:
Your target_results appears to not be a NSMutableSet because you are doing a NSMutableDictionary method of setObject:forKey:. If target_results really was a NSMutableSet, you'd only need to call:
[target_results addObject: [NSNumber numberWithDouble: formula_score]];
Say I have:
NSDictionary *stuff; // {"1" => "hi", "2" => "bye"}
NSArray *array = [stuff allKeys];
allKeys makes a copy of stuff's keys, so array is now responsible for releasing this information.
Later on, when I want to
I cannot do:
array = [newStuff allKeys];
because it would just reassign the pointers and orphan the original array. I must first remove the objects
[array removeAllObjects];
Wanted to know if my understand is correct? Thanks!
Not quite.
NSArray *array = [stuff allKeys];
This gives you an array that you don't own. Whether it's technically a copy or not is not your problem. Since the accessor doesn't start with the word "alloc" or "new", or contain the word "copy", you don't own the return value, which means you don't need to release it. (But you do need to retain it if you want to keep it.)
If you later do this:
array = [newStuff allKeys];
that's fine. It stomps on the original reference, as you know, but since you don't own that reference anyways, it's OK to let it go. This new reference is also, of course, not yours unless you retain it.
No. allKeys returns an autoreleased NSArray. It will be released later unless you explicitly retain it. So setting array = [newStuff allKeys]; is perfectly fine. You should probably read this guide on Objective-C.
Cocoa provides NSDictionary, which essentially is an associative array.
Is there a nice way to get bidirectional associativity? i.e. one way would have been if NSDictionary had a keyForObject: method which mirrored the behavior of objectForKey:.
I don't really care if NSDictionary is not the way to get this. I know NSDictionary does provide keysOfEntriesPassingTest: but it returns an NSSet * which doesn't look very clean for the kind of thing I want to have.
Here is an example:
fieldMap = [[NSDictionary alloc] initWithObjectsAndKeys:
#"c0",#"synckey",
#"c1",#"wi_id",
#"c2",#"wi_parent_id",
#"c3",#"wi_type",
#"c4",#"wi_rh_task",
#"c5",#"decision_key",
#"c6",#"wi_stat",
#"c7",#"wi_prio",
#"c8",#"wi_cd",
#"c9",#"wi_ct",
#"cb",#"wi_dh_stat",
#"cc",#"wi_user",
nil];
I want to translate between c0 and synckey and back, and ditto for the other fields.
The closest thing for what you're after is, I believe allKeysForObject:. This returns an NSArray containing the keys corresponding to all occurrences of a given object in the dictionary. Obviously if the object is in the dictionary only once, the NSArray will contain only one object.
So with your example:
fieldMap = [[NSDictionary alloc] initWithObjectsAndKeys:
#"c0",#"synckey",
#"c1",#"wi_id",
#"c2",#"wi_parent_id",
#"c3",#"wi_type",
#"c4",#"wi_rh_task",
#"c5",#"decision_key",
#"c6",#"wi_stat",
#"c7",#"wi_prio",
#"c8",#"wi_cd",
#"c9",#"wi_ct",
#"cb",#"wi_dh_stat",
#"cc",#"wi_user",
nil];
This additional code would return an array containing 1 string object evaluating to #"c7":
NSArray *keyArray = [fieldMap allKeysForObject:#"wi_prio"];
[Aside: Note that this would only work here because of how the compiler works; it takes all occurences of #"wi_prio" and makes them the same object. If instead you had perhaps loaded the dictionary from disk etc, this approach will not work for NSStrings. Instead you should probably use allKeys and then iterate through them, comparing with [mystring isEqualToString:anotherString].]
The CHDataStructures framework has CHBidirectionalDictionary.
When adding items to NSMutableDictionary using the setValue:forKey: method (I suppose this generalizes to any NSObject) does the dictionary retain the second parameter, the NSString?
For example:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
NSString *theString = #"hello";
int i;
for (i=0; i<[theString length]; i++){
NSNumber *myInt = [NSNumber numberWithInt:i];
NSString *character = [NSString stringWithFormat:#"%C",[theString characterAtIndex:i]];
[dict setValue: myInt forKey:character];
}
[dict release];
[pool release];
Clearly, there is no reason to release myInt in the loop, it is retained by dict so it can't be released until the end of the code. But is the same true of character? My thinking is that if NSMutableDictionary stores the string in some other way, then one could create a temporary pool around the loop and release those strings instead of waiting until the release of the dictionary.
I am also curious as to why retainCount of character is 7fffffff as if it is an NSConstantString, I would expect stringWithFormat to return an NSString object which would need retaining, but that doesn't seem to be the case.
It's very common in Cocoa for NSString parameters to be copied instead of retained. That's because you could have just as easily given the dictionary an instance of NSMutableString. Because the string's value could change, NSDictionary makes a copy.
But, regardless of how NSMutableDictionary really operates, you don't have to worry whether character needs to be retained. Once you've passed it to NSMutableDictionary as a parameter, it's really that class's problem to decide how to store the data, unless the documentation specifically tells you that retaining the objects are your responsibility.
I also wouldn't worry too much about the retainCount of any object. Following the retain count of an object too closely can lead you down rabbit holes that just make you spin your wheels.
Finally, I really don't think you need to create your own autorelease pool here. Unless you know with absolute certainty that theString is going to be very long, or you've already observed high memory utilization in Instruments, adding the autorelease pool is an unnecessary optimization.
You don't need to retain character there, the dictionary retains it when you set it as a key and your own code has no need to retain it.
You also don't need to worry about why the retain count isn't what you expect. Maybe the Foundation framework has Flyweight-like instances of a load of single-character NSString instances. In any case if you've got the memory management correct following the guidelines, you'll be OK regardless of what the framework's doing behind the scenes. http://iamleeg.blogspot.com/2008/12/cocoa-memory-management.html