Creating a constant dictionary object - objective-c

I would like to accomplish something like what is being done in this post: Constants in Objective-C
however, i would like to construct an NSDictionary.
if i do something like:
constants.h
extern NSArray *const mFooKeys;
extern NSArray *const mFooObjects;
extern NSDictionary *const mFooDictionary;
constants.m
NSArray *const mFooKeys = [[NSArray alloc] initWithObjects:
#"Foo", #"Bar", #"Baz", nil];
NSArray *const mFooObjects = [[NSArray alloc] initWithObjects:
#"1", #"2", #"3", nil];
NSDictionary *const mFooDictionary = [[NSDictionary alloc] dictionaryWithObjects:mFooObjects
forKeys:mFooKeys];
do i release in dealloc and everything is fine, or is there more to it? this is more a cautious question than a 'something is wrong' question, but i feel like i could really mess this up without realizing it.

In order to have a constant like a NSDictionary that is based on other core data types, you either need to include it in the class that will be using the constant, or create a Singleton class and store the NSDictionary there. There just some class types that will not work in the implementation you are looking at; the constants code you are looking would need to be used as an object in order to work correctly, but I think that kind of defeats the purpose. I'm not clear as what the determining factor is for what you can and can't do in the simple constants implementation, but I ran into the same issue and the Singleton design pattern worked perfectly for me. (Either way, you should dealloc appropriately even though they will exist for the life of the application.)

You're declaring these as constants, so they are single objects that will last for the lifetime of your application. No need to release, as they're needed until the application is quit.
You don't want to release in dealloc, as this will release every time an instance of the relevant class is deallocated.

Related

With ARC, do I still need to follow the old retain release style?

Pre-ARC, this was how you set values to your properties to avoid memory leaks:
NSDictionary *tempDict = [[NSDictionary alloc]init];
self.dictionary = tempDict;
[tempDict release];
But now with arc, do we still need to use the 2 line style, or can we just use a single line setter?
self.dictionary = [[NSDictionary alloc]init];
versus
NSDictionary *tempDict = [[NSDictionary alloc]init];
self.dictionary = tempDict;
Also, in general iOS dev, is it safe to just use properties most of the time, no more using the instance variables directly?
You can use the single-line style.
Perhaps I don't know what you mean by "safe". Under ARC you can be sure that objects will be properly retained and released whether you use properties, instance variables, or a mixture.
Without ARC, it's usually safer (as in, less prone to human error) to always use property setters to ensure that objects are correctly retained and released.

How to fill NSArray in compile time?

In Objective-C, how to do something like is
int array[] = {1, 2, 3, 4};
in pure C?
I need to fill NSArray with NSStrings with the smallest overhead (code and/or runtime) as possible.
It's not possible to create an array like you're doing at compile time. That's because it's not a "compile time constant." Instead, you can do something like:
static NSArray *tArray = nil;
-(void)viewDidLoad {
[super viewDidLoad];
tArray = [NSArray arrayWithObjects:#"A", #"B", #"C", nil];
}
If it's truly important that you have this precompiled, then I guess you could create a test project, create the array (or whatever object) you need, fill it, then serialize it using NSKeyedArchiver (which will save it to a file), and then include that file in your app. You will then need to use NSKeyedUnarchiver to unarchive the object for use. I'm not sure what the performance difference is between these two approaches. One advantage to this method is that you don't have a big block of code if you need to initialize an array that includes a lot of objects.
use this
NSArray *array = [NSArray arrayWithObjects:str1,str2, nil];
As far as i understand you need a one-dimentional array
You can use class methods of NSArray.. For instance
NSString *yourString;
NSArray *yourArray = [[NSArray alloc] initWithObjects:yourString, nil];
If you need more, please give some more detail about your issue
Simple as that: NSArray<NSString*> *stringsArray = #[#"Str1", #"Str2", #"Str3", ...]; Modern ObjectiveC allows generics and literal arrays.
If you want shorter code, then NSArray *stringsArray = #[#"Str1", #"Str2", #"Str3", ...];, as the generics are optional and help only when accessing the array elements, thus you can later in the code cast back to the templatized array.

NSCFArray not acting as NSArray

I'm trying to save data to and XML file on Iphone. For that, I load the wholeXML, add new data and the save it again. The problem arises when i try to store the new data, my
[mArray addObject:newData];
methods crashes, as mArray is not a NSMutableArray, instead, it is a NSCFArray even if I applied a mutableCopy method to it.
As I understand, a NSCFArray is a toll-free bridging to an NSArray, so I can't understand why the mutablyCopy method is not working.
Any idea??
NSMutableDictionary *wholeXML = [[NSMutableDictionary alloc] init];
wholeXML = xmlData;
NSArray *array = [[NSArray alloc] init];
NSMutableArray *mArray = [[NSMutableArray alloc] init];
array = [wholeXML objectForKey:#"Key"];
mArray = [a mutableCopy];
NSCFArray is a private subclass that gets instantiated when you do things with NSArray factory methods or initializers. You're doing too many initializations. Try this simplified version:
NSMutableDictionary *wholeXML = [[NSMutableDictionary alloc] initWithDictionary:xmlData];
NSMutableArray *mArray = [[NSMutableArray alloc] initWithArray:[wholeXML valueForKey:#"Key"]];
NSCFArray is the concrete class for both NSMutableArray and NSArray. It sounds like you are simply mistaken about what kind of array you have. Since the code you posted is obviously not your real code (it won't even compile, and wouldn't exhibit the problem even if it did), it's impossible to tell at what point your program is assigning an immutable array to the variable. But that's what it sounds like is happening.
I will say (and please don't take this as a personal criticism — it's just an observation) that the code you posted suggests you don't have a strong grasp on how classes and object identity work. That's probably the root cause here.
All three of your variables you initialize with [[Something alloc] init], but then you immediately throw away the object and replace it with something else. This means the original object (NSMutableArray in this case) just gets leaked and the variable now contains the new object you have assigned. If that new object isn't an NSMutableArray, it won't magically be turned into one just because that's what the variable held before.

Cocoa: Dictionary with enum keys?

I need to create a dictionary/hashmap where the
Keys are enums
Values are some subclass of NSObject
NSDictionary won't work here (enums don't conform to NSCopying).
I could perhaps use a CFDictionaryRef here, but I'd like to know if is there any other way to achieve this.
Since enums are integers, you can wrap the enum in an NSNumber. When you add/retreive something to/from the map, you pass the enum to the NSNumber constructor...
Assuming you've got an enum like...
enum ETest {
FOO, BAR
};
You can use it in an NSDictionary like this...
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject: #"Foo!" forKey:[NSNumber numberWithInt: FOO]];
NSLog(#"getting value for FOO -> %#",
[dict objectForKey: [NSNumber numberWithInt: FOO]]);
[dict release];
With VoidPointer's suggestion, it may be better to use NSValue for those times when enums turn out not to be integers (such as when -fshort-enums is in play, which should be never as you'd probably break compatibility with Foundation).
NSValue *value = [NSValue value: &myEnum withObjCType: #encode(enum ETest)];
That's not going to add much here but gives you the general "I want to use <name of non-ObjC type> in a collection class" technique.
Notice that with modern compilers you can tell enums to use a fixed underlying type. This means you can control what storage is used for the enum, but as the above solution is general it still applies even when you know this.
Further extending on the suggestion from Graham Lee...
You could use an objective-c category in order to add a method to NSMutableDictionary that allows you to add a value with a key of your non NSObject type. This keeps your code free from the wrapping/unwrapping syntax.
Again, assuming
enum ETest { FOO, BAR };
First, we're adding a convince constructor to NSValue:
#interface NSValue (valueWithETest)
+(NSValue*) valueWithETest:(enum ETest)etest;
#end
#implementation NSValue (valueWithETest)
+(NSValue*) valueWithETest:(enum ETest)etest
{
return [NSValue value: &etest withObjCType: #encode(enum ETest)];
}
#end
Next we'll add 'enum ETest' support to NSMutableDictionary
#interface NSMutableDictionary (objectForETest)
-(void) setObject:(id)anObject forETest:(enum ETest)key;
-(id) objectForETest:(enum ETest)key;
#end
#implementation NSMutableDictionary (objectForETest)
-(void) setObject:(id)anObject forETest:(enum ETest)key
{
[self setObject: anObject forKey:[NSValue valueWithETest:key]];
}
-(id) objectForETest:(enum ETest)key
{
return [self objectForKey:[NSValue valueWithETest:key]];
}
#end
The original Example can thus be transformed to
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject: #"Bar!" forETest:BAR];
NSLog(#"getting value Bar -> %#", [dict objectForETest: BAR]);
[dict release];
Depending on how much you use your enum to access the dictionary this may ease readability of your code quite a bit.
enums don't conform to NSCopying
This is an understatement; enums do not "conform" to anything as they are not objects; they are primitive C values which are interchangeable with integers. That's the real reason why they can't be used as keys. The keys and values of NSDictionary need to be objects. But since enums are integers, you can just wrap them into NSNumber objects. This is probably the simplest option.
Another option, if the enums are contiguous from 0 up to some number (i.e. you didn't set any values manually), is that you can use an NSArray where the index represents the key enum's value. (Any "missing" entries would have to be filled with NSNull.)
The category approach has its own uses, but the newer boxed expressions (e.g. #(FOO)) should take care of type conversion for you. It works very transparently by explicitly boxing the enum when using it as a key.

Does [NSMutableDictionary setValue: value forKey: key] retain NSString key?

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