How to define a static table of strings in iPhone / XCode? - iphone

I want a table of state-names for debug/trace messages as I develop my custom gesture. What is wrong with this declaration and its usage?
static NSDictionary *dictStateNames = nil;
#implementation MyCustomGesture
#synthesize state;
+(void)initStateNames {
if (dictStateNames == nil) {
dictStateNames = [NSDictionary dictionaryWithObjectsAndKeys:
#"StateBegan", [NSNumber numberWithInt:UIGestureRecognizerStateBegan],
#"StateCancelled", [NSNumber numberWithInt:UIGestureRecognizerStateCancelled],
#"StateChanged", [NSNumber numberWithInt:UIGestureRecognizerStateChanged],
#"StateEnded", [NSNumber numberWithInt:UIGestureRecognizerStateEnded],
#"StateFailed", [NSNumber numberWithInt:UIGestureRecognizerStateFailed],
#"StatePossible", [NSNumber numberWithInt:UIGestureRecognizerStatePossible],
#"StateRecognized", [NSNumber numberWithInt:UIGestureRecognizerStateRecognized],
nil];
}
}
-(id) init {
self = [super init];
if (self) {
[MyCustomGesture initStateNames];
state = UIGestureRecognizerStatePossible;
}
return self;
}
-(id) initWithTarget:(id)target action:(SEL)action {
self = [super initWithTarget:target action:action];
if (self) {
[MyCustomGesture initStateNames];
state = UIGestureRecognizerStatePossible;
}
return self;
}
+(NSString*) stateName: (UIGestureRecognizerState) state {
NSString *retName = [dictStateNames objectForKey:[NSNumber numberWithInt:state]];
if (retName == nil) {
return [NSString stringWithFormat:#"Unknown state (%#)", state];
} else {
return retName;
}
}
-(NSString*) currentStateName {
return [MyCustomGesture stateName:state];
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"%s (%#): %#", __FUNCTION__, [self currentStateName], event);
}

When you store a reference to an object in a static variable, you need to ensure that it doesn't get deallocated. So you can either send it a retain message, or create with alloc instead of a convenience creation method. For example:
dictStateNames = [[NSDictionary dictionaryWithObjectsAndKeys:
// List of objects and keys...
nil] retain];
or this...
dictStateNames = [NSDictionary alloc] initWithObjectsAndKeys:
// List of objects and keys...
nil];
Also, you can coalesce your stateNames getter and the initialization code into a single method, so you'll typically see Objective-C developers write a method like this:
+ (NSDictionary *)stateNames
{
static NSDictionary *stateNames;
if (stateNames == nil) {
stateNames = [NSDictionary alloc] initWithObjectsAndKeys:
// List of objects and keys...
nil];
}
return stateNames;
}
That way, there's no need to call it in an instance method (which would be wrong anyway, since a new dictionary would be created each time an instance is initialized, and unless you handled it differently, the previous one would be leaked).
On another (unrelated) note, consider rewriting your init method as follows:
- (id)init
{
return [self initWithTarget:nil action:NULL];
}

[NSDictionary dictionaryWithObjectsAndKeys] takes an object first followed by its key (as the method name suggests), which seems a bit backward but there it is.

Related

Return a variable from inside a block

Below I have some code which should return an array, the response from the server happens in a block:
- (NSMutableArray *)getArray
{
NSMutableDictionary* params =[NSMutableDictionary dictionaryWithObjectsAndKeys:
#"pending", #"command",
#"2" , #"userID",
nil];
[[API sharedInstance] commandWithParams:params
onCompletion:^(NSDictionary *json) {
//result returned
if ([json objectForKey:#"error"]==nil) {
NSMutableArray *res = [[NSMutableArray alloc] init];
[res addObject:#"1234"];
RETURN HERE
} else {
//error
[UIAlertView title:#"Error" withMessage:[json objectForKey:#"error"]];
}
}];
}
After parsing the data and creating an array I want to return the array I have created for the getArray method call. So far, after hours of trying I have not had any luck, even with trying some suggestions from previous questions on stackoverflow. Any help would be appreciated.
pass the block as a param to the function
- (NSMutableArray *)getArray:(void (^)(NSArray *))block {}
And then replace the RETURN HERE with block(res);
I'd create a method in somewhere in the class let's say - (void)arrayFetched:(NSArray *)fetchedArray.
Then you modify your code like this:
//...
if ([json objectForKey:#"error"]==nil) {
__weak NSMutableArray *res = [[NSMutableArray alloc] init];
[res addObject:#"1234"];
dispatch_async(dispatch_get_main_queue(), ^{
[self arrayFetched:[res copy]];
});
//...
and then do want you want with the array in the arrayFetched: method.

Populating NSDictionary and NSArrays for Model data

I'm trying to create an NSDictionary full of arrays in the implementation file of my model but my code hasn't worked yet. I want to create arrays that are lists of types of dogs and cats and then add those arrays to a dictionary with keys called DOG and CAT. Here is my code:
#implementation wordDictionary
#synthesize catList = _catList;
#synthesize dogList = _dogList;
#synthesize standardDictionary =_standardDictionary;
- (void)setCatList:(NSMutableArray *)catList
{
self.catList = [NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil];
}
- (void)setDogList:(NSMutableArray *)dogList
{
self.dogList = [NSMutableArray arrayWithObjects:#"pit bull", #"pug", #"chihuahua", nil];
}
-(void)setStandardDictionary:(NSMutableDictionary *)standardDictionary
{
[self.standardDictionary setObject: _catList forKey:#"CAT"];
[self.standardDictionary setObject: _dogList forKey:#"DOG"];
}
- (NSString*)selectKey
{
NSInteger keyCount = [[self.standardDictionary allKeys] count];
NSInteger randomKeyIndex = arc4random() % keyCount;
NSString *randomKey = [[self.standardDictionary allKeys] objectAtIndex:randomKeyIndex];
return randomKey;
}
#end
This code is the model. The model is hooked up to my view controller such that when a user taps a button, the NSString returned from randomKey is displayed in a label on the screen. So the text will read either CAT or DOG. Here's the code for that:
- (IBAction)changeGreeting:(UIButton*)sender {
NSString *chosenKey = [self.dictionary selectKey];
NSString *labelText = [[NSString alloc] initWithFormat:#"%#", chosenKey];
self.label.text = labelText;
}
Unfortunately when I tap the button on the simulator I get an error message saying: Thread 1:EXC_ARITHMETIC (code=EXC_1386_DIV, subcode=0x0) at NSInteger randomKeyIndex = arc4random() % keyCount; and it appears that I'm getting it because neither my NSArray nor my NSDictionary have any objects inside of them.
Does anyone have any idea why my NSArray and NSDictionary haven't been populated?
Thanks very much.
The simple answer is that there isn't any code here that calls the methods to set the arrays or dictionary.
But the real underlying issue is that there are a couple of bad 'patterns' going on here that you should fix:
In your setter methods (setCatList:, setDogList:, setStandardDictionary:) you're not setting the properties in question to the values that are passed in. For example, you should be setting catList to the passed in "catList" variable.
- (void)setCatList:(NSMutableArray *)catList
{
if (_catList != catList) {
[_catList release];
_catList = [catList retain];
}
}
Then you should have some kind of "setup" happening, usually in a method in the view controller like viewDidLoad:
[wordDictionary setCatList:[NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil]];
// and more for the other two setters
Alternately, you can set these default values in the init for the wordDictionary class:
- (id)init {
self = [super init];
if (self) {
[self setCatList:[NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil]];
}
return self;
}
The former is better in most cases, but you may have a good reason to pre-populate your model for all instances of the class.
Assuming you called setCatList:, setDogList: and setStandardDictionary: before. Probably that causing is this :
NSString *chosenKey = [self.dictionary selectKey];
change into this :
NSString *chosenKey = [self selectKey];
UPDATE
I'm trying to make your life easier. no need to create your object if you don't need the most.
- (NSMutableArray*)getCatList
{
return [NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil];
}
- (NSMutableArray*)getDogList
{
return [NSMutableArray arrayWithObjects:#"pit bull", #"pug", #"chihuahua", nil];
}
-(NSMutableDictionary*)getStandardDictionary
{
NSMutableDictionary *standardDictionary = [NSMutableDictionary new];
[standardDictionary setObject:[self getCatList] forKey:#"CAT"];
[standardDictionary setObject:[self getDogList] forKey:#"DOG"];
return [standardDictionary autorelease];
}
- (NSString*)selectKey
{
NSMutableDictionary *standardDictionary = [self getStandardDictionary];
NSInteger keyCount = [[standardDictionary allKeys] count];
NSInteger randomKeyIndex = arc4random() % keyCount;
NSString *randomKey = [[standardDictionary allKeys] objectAtIndex:randomKeyIndex];
return randomKey;
}
- (IBAction)changeGreeting:(UIButton*)sender {
// NSString *chosenKey = [self selectKey];
//NSString *labelText = [[NSString alloc] initWithFormat:#"%#", chosenKey];
self.label.text = [self selectKey]; //no need to convert it to NSString again
}
Two things to consider:
I don't see you calling these:
setCatList:(NSMutableArray*)catList;
setDogList:(NSMutableArray*)dogList;
You use self.catList and self.dogList, but neither of those are synthesized, instead you have beatList and meList synthesized
Change the synthesizes to the catList and dogList, and make sure you call the set list methods, and then you should make some progress.

Adding custom defined objects to NSMutableArray overwrite the whole array

-(id) initWithData:(NSString *)lastName: (NSString *)firstName{
self->firstname = firstName;
self->lastname = lastName;
return self;
}
-(void) printData {
NSLog(#"firstname: %#", firstname);
NSLog(#"lastname: %#", lastname);
}
so whenever I create a new object using the above init function. And Add objects to a NSMutableArray, using the addObject function.
NSMutableArray *objectArray = [[NSMutableArray alloc] init];
CustomObject *tempObject = [[CustomObject alloc] initWithData: #"smith": #"john"];
CustomObject *tempObjectb = [[CustomObject alloc] initWithData: #"brown": #"william"];
[objectArray addObject:tempObject];
[objectArray addObject:tempObjectb];
[[objectArray objectAtIndex:0] printData];
[[objectArray objectAtIndex:1] printData];
objects at index 1, and 0 always equal the whichever object was added to the array last.
This also happens if I use a for loop, or have more than 2 objects, all values when printed, turn to the values of the last added object to the objectArray. Let me know if there is any information that I am missing.
Is there something that I am missing?
Fix your initWithData:lastName: implementation as following:
-(id) initWithData:(NSString *)lastName: (NSString *)firstName
{
self = [super init];
if ( nil != self ) {
self->firstname = [firstName retain];
self->lastname = [lastName retain];
}
return self;
}

Instance of method leaking, iPhone

The following method shows up as leaking while performing a memory-leaks test with Instruments:
- (NSDictionary*) initSigTrkLstWithNiv:(int)pm_SigTrkNiv SigTrkSig:(int)pm_SigTrkSig SigResIdt:(int)pm_SigResIdt SigResVal:(int)pm_SigResVal {
NSArray *objectArray;
NSArray *keyArray;
if (self = [super init]) {
self.SigTrkNiv = [NSNumber numberWithInt:pm_SigTrkNiv];
self.SigTrkSig = [NSNumber numberWithInt:pm_SigTrkSig];
self.SigResIdt = [NSNumber numberWithInt:pm_SigResIdt];
self.SigResVal = [NSNumber numberWithInt:pm_SigResVal];
objectArray = [NSArray arrayWithObjects:SigTrkNiv,SigTrkSig,SigResIdt,SigResVal, nil];
keyArray = [NSArray arrayWithObjects:#"SigTrkNiv", #"SigTrkSig", #"SigResIdt", #"SigResVal", nil];
self = [NSDictionary dictionaryWithObjects:objectArray forKeys:keyArray];
}
return self;
}
code that invokes the instance:
NSDictionary *lv_SigTrkLst = [[SigTrkLst alloc]initSigTrkLstWithNiv:[[tempDict objectForKey:#"SigTrkNiv"] intValue]
SigTrkSig:[[tempDict objectForKey:#"SigTrkSig"] intValue]
SigResIdt:[[tempDict objectForKey:#"SigResIdt"] intValue]
SigResVal:[[tempDict objectForKey:#"SigResVal"] intValue]];
[[QBDataContainer sharedDataContainer].SigTrkLstArray addObject:lv_SigTrkLst];
[lv_SigTrkLst release];
Instruments informs that 'SigTrkLst' is leaking. Even though I have released the instance?
(I know that adding it to the array increments the retainCount by 1 but releasing it twice removes it from the array?)
You are initializing the instance (self), but then replacing it with a dictionary. Assuming that is what you actually want to do (see part 2), you need to release the old self before assigning a new one, and then you must retain the new dictionary so that your init method preserves the retain count. Note that this code returns a NSDictionary object, when your calling code expects a SigTrkLst, which is almost certainly a bad idea.
- (NSDictionary*) initSigTrkLstWithNiv:(int)pm_SigTrkNiv
SigTrkSig:(int)pm_SigTrkSig
SigResIdt:(int)pm_SigResIdt
SigResVal:(int)pm_SigResVal {
NSArray *objectArray;
NSArray *keyArray;
if (self = [super init]) {
self.SigTrkNiv = [NSNumber numberWithInt:pm_SigTrkNiv];
self.SigTrkSig = [NSNumber numberWithInt:pm_SigTrkSig];
self.SigResIdt = [NSNumber numberWithInt:pm_SigResIdt];
self.SigResVal = [NSNumber numberWithInt:pm_SigResVal];
objectArray = [NSArray arrayWithObjects:
SigTrkNiv,SigTrkSig,SigResIdt,SigResVal, nil];
keyArray = [NSArray arrayWithObjects:
#"SigTrkNiv", #"SigTrkSig", #"SigResIdt", #"SigResVal", nil];
[self release];
self = [NSDictionary dictionaryWithObjects:objectArray forKeys:keyArray];
[self retain];
}
return self;
}
Part 2: What is this supposed to do? Normally speaking, an -init... method should return an initialized class instance, starting from an allocated chunk of memory. The first part of your -init method looks right, other than the return value. For example, the following is a normal -init method:
- (id)initSigTrkLstWithNiv:(int)pm_SigTrkNiv
SigTrkSig:(int)pm_SigTrkSig
SigResIdt:(int)pm_SigResIdt
SigResVal:(int)pm_SigResVal {
if (self = [super init]) {
self.SigTrkNiv = [NSNumber numberWithInt:pm_SigTrkNiv];
self.SigTrkSig = [NSNumber numberWithInt:pm_SigTrkSig];
self.SigResIdt = [NSNumber numberWithInt:pm_SigResIdt];
self.SigResVal = [NSNumber numberWithInt:pm_SigResVal];
}
return self;
}
self = [NSDictionary dictionaryWithObjects:objectArray forKeys:keyArray];
When you are doing this what is happening to the SigTrkLst object that self supposed to point? Don't are you loosing reference to SigTrkLst object that was allocated and pointed by self? That object is never released as after this line there is no reference to that.
Though it is technically possible for an init method to return an object of different type, most probably this is not most of the people want and might indicate a potential bug.
EDIT: It seems that you are missing some basics of memory management for iOS. I strongly request you to read Memory Management Programming Guide. This may save you from lots of troubles.

Advice on High Score persistence (iPhone, Cocoa Touch)

I was curious what is considered the better way to manage the reading and writing of a High Score plist file. My High Score class is:
#interface HighScore : NSObject <NSCoding> {
NSString *name;
int score;
int level;
int round;
NSDate *date;
}
Now, I could do method A, add NSCoding methods:
- (void) encodeWithCoder: (NSCoder *) coder {
[coder encodeObject: name
forKey: kHighScoreNameKey];
[coder encodeInt: score
forKey: kHighScoreScoreKey];
[coder encodeInt: level
forKey: kHighScoreLevelKey];
[coder encodeInt: round
forKey: kHighScoreRoundKey];
[coder encodeObject: date
forKey: kHighScoreDateKey];
} // encodeWithCoder
- (id) initWithCoder: (NSCoder *) decoder {
if (self = [super init]) {
self.name = [decoder decodeObjectForKey: kHighScoreNameKey];
self.score = [decoder decodeIntForKey: kHighScoreScoreKey];
self.level = [decoder decodeIntForKey: kHighScoreLevelKey];
self.round = [decoder decodeIntForKey: kHighScoreRoundKey];
self.date = [decoder decodeObjectForKey: kHighScoreDateKey];
}
return (self);
} // initWithCoder
And write it all out with:
if (![NSKeyedArchiver archiveRootObject:highScoresList toFile:path]) ...
Reading it back in would be pretty straight forward. However the plist file, IMHO, looks like crap.
Or I could employ method B:
NSMutableArray *array = [NSMutableArray arrayWithCapacity:20];;
for (HighScore *hs in highScoresList) {
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:
hs.name, kHighScoreNameKey,
[NSNumber numberWithInteger:hs.score], kHighScoreScoreKey,
[NSNumber numberWithInteger:hs.level], kHighScoreLevelKey,
[NSNumber numberWithInteger:hs.round], kHighScoreRoundKey,
hs.date, kHighScoreDateKey,
nil];
[array addObject:dict];
[dict release];
}
and write it all out with:
if (![array writeToFile:path atomically:YES]) ...
Reading it back in is a tiny bit harder. But the plist file looks much cleaner (smaller and compact).
Any thoughts? Am I missing something that is much simpler? (I want to keep the High Scores separate from NSUserDefaults so I am not using that).
Both your ways look fine to me. There is also Core Data in the 3.0 OS, although it might be overkill if all you want to save is a single high score value.
I'm not sure that I understand your objections! Both should work just fine.
Personally I prefer method A. I think it makes sense that an object knows how to encode itself. It makes maintenance easier and any changes more localised. Plus it probably uses less memory.
check out this question, maybe it's useful for your app
I went with method B because: 1. The plist file is more readable and 2. I can save off some file and class version numbering into this method easily:
In my HighScore class:
- (id)initFromDictionary: (NSDictionary *)dict;
{
if (self = [super init]) {
self.name = [dict objectForKey:kHighScoreNameKey];
self.score = [[dict objectForKey:kHighScoreScoreKey] integerValue];
self.game = [[dict objectForKey:kHighScoreGameKey] integerValue];
self.level = [[dict objectForKey:kHighScoreLevelKey] integerValue];
self.date = [dict objectForKey:kHighScoreDateKey];
}
return (self);
}
- (NSDictionary *)putIntoDictionary;
{
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:
name, kHighScoreNameKey,
[NSNumber numberWithInt:score], kHighScoreScoreKey,
[NSNumber numberWithInt:game], kHighScoreGameKey,
[NSNumber numberWithInt:level], kHighScoreLevelKey,
date, kHighScoreDateKey,
nil];
return dict;
}
And in my HighScoreTable class:
- (id) load
{
NSString *path = [self getFilePath];
// [self clear];
NSDictionary *rootLevelPlistDict = [NSDictionary dictionaryWithContentsOfFile:path];
int versNum = [[rootLevelPlistDict objectForKey:kHighScoreVersKey] integerValue];
if (versNum == kHighScoreVersNum) {
NSArray *insideArray = [rootLevelPlistDict objectForKey:kHighScoresKey];
NSDictionary *dict;
for (dict in insideArray) {
HighScore *hs = [[HighScore alloc] initFromDictionary:dict];
[highScoresList addObject:hs];
[hs release];
}
}
return sharedHighScoresSingleton;
}
- (void) save
{
if (!changed)
return;
NSString *path = [self getFilePath];
NSMutableArray *array = [NSMutableArray arrayWithCapacity:kNumberOfHighScores];
for (HighScore *hs in highScoresList) {
NSDictionary *dict = [hs putIntoDictionary];
[array addObject:dict];
[dict release];
}
NSDictionary *rootLevelPlistDict = [[NSDictionary alloc] initWithObjectsAndKeys:
[[[NSBundle mainBundle] infoDictionary]
objectForKey:(NSString*)kCFBundleNameKey], kHighScoreAppNameKey,
[NSNumber numberWithInt:kHighScoreHeaderVersNum], kHighScoreHeaderVersKey,
[NSNumber numberWithInt:kHighScoreVersNum], kHighScoreVersKey,
[NSDate date], kHighScoreCreationKey,
array, kHighScoresKey,
nil];
if (![rootLevelPlistDict writeToFile:path atomically:YES])
NSLog(#"not successful in writing the high scores");
[rootLevelPlistDict release];
}