Local high scores reporting/Display - iphone

Please can anyone refer me to a tutorial or help me out with this problem. I wish to display high scores and other games statistics at game Over but I don't seem to get a headway despite looking at tutorials and trying stuff out. I don't seem to understand how to use NSUserdefaults to achieve this.
My codes are as follows:
Gameplaylayer.mm
- (id)initWithHUDLayer:(HUDLayer *)hudLayer {
if ((self = [super init]))
{
score = 0;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *defaultArray = [NSDictionary dictionaryWithObject:[NSArray arrayWithObjects:[NSNumber numberWithInt:0],
[NSNumber numberWithInt:0],
[NSNumber numberWithInt:0],
[NSNumber numberWithInt:0],
[NSNumber numberWithInt:0],
nil]
forKey:#"Scores"];
[defaults registerDefaults:defaultArray];
[defaults synchronize];
}
return self;
}
-(void)gameOver:(id)sender{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *HighScores = [NSMutableArray arrayWithArray:[defaults arrayForKey:#"Scores"]];
for (unsigned int i = 0; i < [HighScores count]; i++)
{
if (dist >= [[HighScores objectAtIndex:i] intValue])
{
[HighScores insertObject:[NSNumber numberWithInt:dist] atIndex:i];
[HighScores removeLastObject];
[defaults setObject:HighScores forKey:#"Scores"];
[defaults synchronize];
NSLog(#"Saved new high score of %i", dist);
break;
}
}
}
-(void)update:(ccTime)dt {
CGSize winSize = [CCDirector sharedDirector].winSize;
score += (int)delta;
dist = score - 18;
if (!gameOver )
{
if (!lives)
{
gameOver = true;
[self gameOver];
}
}
}
my game over layer is as follows
-(id)init {
self = [super init];
if (self != nil)
{
self.isTouchEnabled = YES;
CGSize windowSize = [CCDirector sharedDirector].winSize;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *HighScores = [defaults arrayForKey:#"Scores"];
CLabelTTF *title = [CCLabelTTF labelWithString:#"high scores" fontName:#"Courier" fontSize:32.0];
[title setPosition:ccp(screenSize.width / 2, screenSize.height - title.contentSize.height)];
[self addChild:title];
NSMutableString *scoresString = [NSMutableString stringWithString:#""];
for (unsigned int i = 0; i < [HighScores count]; i++)
{
[scoresString appendFormat:#"%i. %i\n", i + 1, [[HighScores objectAtIndex:i] intValue]];
}
CCLOG(#"scores saved %i", dist);
CCLabelTTF *scoresLabel = [CCLabelTTF labelWithString:scoresString dimensions:CGSizeMake(screenSize.width, screenSize.height / 3) alignment:CCTextAlignmentCenter fontName:#"Courier" fontSize:40.0];
[scoresLabel setPosition:ccp(screenSize.width / 2, screenSize.height / 2)];
scoresLabel.color = ccc3(0, 0, 0);
[self addChild:scoresLabel z:9];
}
return self;
}

It's hard to understand your code, please look at this small example below.
Ok, that's saving new score, assuming you have GameController singleton, where scores mutable array is declared and currentScore in that singleton also displays current player's score.
-(void) saveScores {
GameController * controller = [GameController sharedController];
for(int i=0; i<5;i++)
{
if([[[controller.scores objectAtIndex:i] objectForKey:#"score"] intValue] == controller.finalscore)
{
break;
}
if([[[controller.scores objectAtIndex:i] objectForKey:#"score"] intValue] < controller.finalscore) {
for(int j=5;j>i+1;j--)
{
[controller.scores replaceObjectAtIndex:j-1 withObject:[controller.scores objectAtIndex:j-2]];
}
NSMutableDictionary *newEntry = [NSMutableDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:controller.finalscore],#"score",#"Local player",#"name", nil];
[controller.scores replaceObjectAtIndex:i withObject:newEntry];
break;
}
}
controller.currentScore=0;
[[NSUserDefaults standardUserDefaults] setObject:controller.scores forKey:#"OurScores"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
You can see, that we are saving updated scores in NSUserDefaults. So, at our game start we shoul load them. I added this code to GameController init method:
- (void) loadScores {
if([[NSUserDefaults standardUserDefaults] objectForKey:#"OurScores"] == nil)
{
NSMutableDictionary *empty = [NSMutableDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:0],#"score",#"empty",#"name", nil];
for(int i=0;i<5;i++)
[scores addObject:empty];
}
else
{
NSLog(#"Loading scores");
scores = [[NSUserDefaults standardUserDefaults] objectForKey:#"OurScores"];
}
}
And that's how we can show this scores using cocos2d:
-(void) drawScores {
for(int i=0; i<5; i++)
{
CCLabelTTF* score = [CCLabelTTF labelWithString:
[NSString stringWithFormat:#"%#",[[controller.scores objectAtIndex:i] objectForKey:#"name"]] fontName:#"Marker Felt" fontSize:30];
score.anchorPoint=ccp(0,0);
score.position = ccp(115, 180-i*35);
CCLabelTTF* score2 = [CCLabelTTF labelWithString:
[NSString stringWithFormat:#"%d",[[[controller.scores objectAtIndex:i] objectForKey:#"score"] intValue] ] fontName:#"Marker Felt" fontSize:35];
score2.anchorPoint=ccp(0,0);
score2.position = ccp(280, 180-i*35);
score.color=currentClr;
score2.color=currentClr;
[self addChild: score z:3 tag:i];
[self addChild: score2 z:3 tag:i+15];
}
}

Your code has a number of problems, some of which just break with convention but all together they make your program quite unreadable. I have not been able to make sense of it. You are not using comments, nor are you telling us, what exactly is not working.
For instance, you might consider following the convention of having instance variables start with small letters, reserving capitalized names for classes. It is generally a bad idea to give misleading names, such as calling an NSDictinoary "defaultArray". Also, try to use empty lines more sparingly to structure your code for better readability.
That being said, this is how you set a default value:
[[NSUserDefaults standardUserDefaults] setValue:scoreArray
forKey:#"Score"];
I see you know how to retrieve the score.
As for your updating routine, consider that it is normally not a good idea to modify the array your iterating through (in your particular case it could still work, but it is not guaranteed). Also, there is no need to store the whole array in the loop. Just save it once you have contructed the complete new high score array.
Otherwise your code is working, right?

Related

Leak when carrying out operations on annotations in an MKMapView

I've got a method that takes in the annotations (custom PostLocationAnnotation class) to be displayed on a map view and clusters close ones together, outputting an array of MKAnnotation of PostLocationAnnotations and LocationGroupAnnotations (the clusters, which each contain some PostLocationAnnotations). Here's how I call the function (from within an 'updateAnnotations' method, called when the viewport of the map changes):
[annotationsToAdd addObjectsFromArray:[ffMapView annotations]];
[ffMapView addAnnotations:[self clusterAnnotations:annotationsToAdd WithEpsilon:20.0f andMinPts:4]];
annotationsToAdd is initially populated by the annotations that have been retrieved from the server that have not already been added to the map. Therefore I am passing the full list of the annotations that should be put on the map into the clusterAnnotations method. Here is the body of the method:
- (NSArray *)clusterAnnotations:(NSArray *)annotations WithEpsilon:(float)eps andMinPts:(int)minPts
{
NSMutableSet *D = [[NSMutableSet alloc] initWithCapacity:[annotations count]];
NSMutableArray *C = [[NSMutableArray alloc] init];
for (id <MKAnnotation> annotation in annotations)
{
if ([annotation isKindOfClass:[PostLocationAnnotation class]])
{
NSMutableDictionary *dictEntry = [NSMutableDictionary dictionaryWithObjectsAndKeys:
annotation, #"point",
[NSNumber numberWithBool:NO], #"visited",
[NSNumber numberWithBool:NO], #"noise",
[NSNumber numberWithBool:NO], #"clustered", nil];
[D addObject:dictEntry];
[dictEntry release];
} else if ([annotation isKindOfClass:[LocationGroupAnnotation class]])
{
for (PostLocationAnnotation *location in [(LocationGroupAnnotation *)annotation locations])
{
NSMutableDictionary *dictEntry = [NSMutableDictionary dictionaryWithObjectsAndKeys:
location, #"point",
[NSNumber numberWithBool:NO], #"visited",
[NSNumber numberWithBool:NO], #"noise",
[NSNumber numberWithBool:NO], #"clustered", nil];
[D addObject:dictEntry];
[dictEntry release];
}
}
}
for (NSMutableDictionary *P in D)
{
if ([P objectForKey:#"visited"] == [NSNumber numberWithBool:NO])
{
[P setValue:[NSNumber numberWithBool:YES] forKey:#"visited"];
NSMutableSet *N = [[NSMutableSet alloc] initWithSet:[self regionQueryForPoint:P andEpsilon:eps fromList:D]];
if ([N count] < minPts)
{
[P setValue:[NSNumber numberWithBool:YES] forKey:#"noise"];
} else {
LocationGroupAnnotation *newCluster = [[LocationGroupAnnotation alloc] initWithLocations:nil];
[C addObject:newCluster];
[self expandDbscanClusterWithPoint:P andRegion:N andCluster:newCluster andEpsilon:eps andMinPts:minPts fromList:D];
[newCluster release];
}
[N release];
}
}
NSMutableArray *annotationsToAdd = [[[NSMutableArray alloc] initWithCapacity:[annotations count]] autorelease];
for (NSMutableDictionary *P in D)
{
if ([P objectForKey:#"clustered"] == [NSNumber numberWithBool:NO])
{
[annotationsToAdd addObject:[P objectForKey:#"point"]];
}
}
for (LocationGroupAnnotation *cluster in C)
{
[cluster updateCenterCoordinate];
}
[annotationsToAdd addObjectsFromArray:(NSArray *)C];
[D release];
[C release];
return (NSArray *)annotationsToAdd;
}
When I run this I get a zombie message, and I have found that removing [D release] fixes the zombie but causes a leak. Looking at Instruments I can see that the memory address is first Malloc'd in clusterAnnotations, then retained and released a couple of times, then retained a large number of times by regionQueryForPoint (reaching a peak of 47 references), then released twice by clusterAnnotations, then released by [NSAutoreleasePool drain] until the refcount reaches -1 and I get the zombie message error. Here is the code for regionQueryForPoint:
- (NSSet *)regionQueryForPoint:(NSMutableDictionary *)P andEpsilon:(float)eps fromList:(NSMutableSet *)D
{
NSMutableSet *N = [[[NSMutableSet alloc] init] autorelease];
for (NSMutableDictionary *dictEntry in D)
{
if ((dictEntry != P) &&
([[dictEntry objectForKey:#"point"] isKindOfClass:[PostLocationAnnotation class]]))
{
CGPoint p1 = [ffMapView convertCoordinate:[[P objectForKey:#"point"] coordinate] toPointToView:self.view];
CGPoint p2 = [ffMapView convertCoordinate:[[dictEntry objectForKey:#"point"] coordinate] toPointToView:self.view];
float dX = p1.x - p2.x;
float dY = p1.y - p2.y;
if (sqrt(pow(dX,2)+pow(dY,2)) < eps)
{
[N addObject:dictEntry];
}
}
}
return (NSSet *)N;
}
The large number of retains appear to happen when regionQueryForPoint is called from the expandDbScanClusterWithPoint method, so I've included that here for completeness:
- (void)expandDbscanClusterWithPoint:(NSMutableDictionary *)P andRegion:(NSMutableSet *)N
andCluster:(LocationGroupAnnotation *)cluster
andEpsilon:(float)eps
andMinPts:(int)minPts
fromList:(NSMutableSet *)D
{
[cluster addAnnotation:(PostLocationAnnotation *)[P objectForKey:#"point"]];
[P setValue:[NSNumber numberWithBool:YES] forKey:#"clustered"];
BOOL finished = NO;
while (!finished)
{
finished = YES;
for (NSMutableDictionary *nextP in N)
{
if ([nextP objectForKey:#"visited"] == [NSNumber numberWithBool:NO])
{
[nextP setValue:[NSNumber numberWithBool:YES] forKey:#"visited"];
NSSet *nextN = [self regionQueryForPoint:nextP andEpsilon:eps fromList:D];
if ([nextN count] >= minPts)
{
[N unionSet:nextN];
finished = NO;
break;
}
}
if ([nextP objectForKey:#"clustered"] == [NSNumber numberWithBool:NO])
{
[cluster addAnnotation:[nextP objectForKey:#"point"]];
[nextP setValue:[NSNumber numberWithBool:YES] forKey:#"clustered"];
}
}
}
}
I've been dissecting this for ages, counting references, watching pointers and everything but I just can't work out how to safely release this D set. Can anyone see anything I'm not seeing?
You seem to be over-releasing dictEntry with [dictEntry release];. When using dictionaryWithObjectsAndKeys, you're getting an autoreleased object back. So releasing it again will decrease the retain count.
EDIT: If you're unsure how it works and when you're actually retaining objects, you might want to have a look at the memory management docs:
You create an object using a method whose name begins with “alloc”,
“new”, “copy”, or “mutableCopy” (for example, alloc, newObject, or
mutableCopy).

iPhone - NSArray - Pick 4 different items

I've NSArray with over 100 strings.
I would like to pick 4 different strings randomly. I can write traditional way of code using for/while loops and get the task done.
But is there any better way to pick 4 different random strings?
Shuffle an array as described in JEFF LAMARCHE's blog and use first four items)
create a NSSet from your NSArray and fetch first 4 elements.
I wrote some utils as category on NSArray.
You could use it like this:
#import "NSArray+RandomUtils.h"
NSArray *array = [NSArray arrayWithObjects:#"aa", #"ab",#"c",#"ad",#"dd", nil];
NSSet *set = [array setWithRandomElementsSize:4];
This will give you a set of 4 unique random elements.
If you want to allow objects to be in your collection more than once you can do:
NSArray *array = [NSArray arrayWithObjects:#"aa", #"ab",#"c",#"ad",#"dd", nil];
NSArray *resultArray = [array arrayWithRandomElementsSize:4];
#import "NSArray+RandomUtils.h"
#implementation NSArray (RandomUtils)
-(NSMutableArray *)mutableArrayShuffled
{
NSMutableArray *array = [[self mutableCopy] autorelease];
[array shuffle];
return array;
}
-(NSMutableArray *)arrayShuffled
{
return [NSArray arrayWithArray:[self mutableArrayShuffled]];
}
-(id)randomElement
{
if ([self count] < 1) return nil;
NSUInteger randomIndex = arc4random() % [self count];
return [self objectAtIndex:randomIndex];
}
-(NSSet *)setWithRandomElementsSize:(NSUInteger)size
{
if ([self count]<1) return nil;
if (size > [self count])
[NSException raise:#"NSArrayNotEnoughElements"
format:#"NSArray's size (%d) is too small to fill a random set with size %d", [self count], size];
NSMutableSet *set = [NSMutableSet set];
NSMutableArray *array = [self mutableArrayShuffled];
if (size == [array count])
return [NSSet setWithArray:array];
while ([set count]< size) {
id object = [array objectAtIndex:0];
[array removeObjectAtIndex:0];
[set addObject:object];
}
return [NSSet setWithSet:set];
}
-(NSArray *)arrayWithRandomElementsSize:(NSUInteger)size
{
if ([self count]<1) return nil;
NSMutableArray *array = [NSMutableArray array];
while ([array count] < size) {
[array addObject:[self randomElement]];
}
return [NSArray arrayWithArray:array];
}
#end
#implementation NSMutableArray (RandomUtils)
-(void)shuffle
{
NSUInteger count = [self count];
for (NSUInteger i = 0; i < count; ++i) {
NSUInteger nElements = count - i;
NSUInteger n = (arc4random() % nElements) + i;
[self exchangeObjectAtIndex:i withObjectAtIndex:n];
}
}
#end
This has been answered multiple times in the forum. Your key to generate random numbers is
(arc4random() % 100) +1
Above code is capable of generating a random number ranging from 1 to 100. You can use this to get what you want. If you received a random number that you already got, ignore and call again to get a unique random number.

NSMutableArray in NSUserDefaults

I'm sorry for my bad english but i'm trying to explain with best words.
I have some problem when i'm trying to insert an NSMutableArray in NSUserDefaults ([NSUserDefaults setObject:forKey:]: Attempt to insert non-property value ')
My code to insert the array is as follows:
-(void)saveToUserDefaults:(NSMutableArray*)myArray
{
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
NSArray *array = [[NSArray alloc ] initWithArray:myArray];
if (standardUserDefaults)
{
[standardUserDefaults setObject:array forKey:#"MyArray"];
[standardUserDefaults synchronize];
}
}
My code to retrieve the array:
-(NSMutableArray*)retrieveFromUserDefaults
{
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *val = [[NSMutableArray alloc]init];
if (standardUserDefaults)
val = [NSMutableArray arrayWithArray:[standardUserDefaults arrayForKey:#"MyArray"]];
return val;
}
and in my code :
NSMutableArray :
series = [[NSMutableArray alloc]initWithObjects:nil];
//add some object inf my array...
Save my NSMutableArray :
[self saveToUserDefaults:series];
Retrieve my NSMutableArray :
series = [self retrieveFromUserDefaults];
I think it's not the best way to do this, so if anyone have ideas , it'll be helpful for me.
Thanks for reading.
Tommy
Only immutable NSArrays can be placed into defaults. Rather than placing a NSMutableArray there, convert to regular array using [NSArray arrayWithArray:] and place that one into defaults.
For retrieval, retrieve an NSArray and then use [NSMutableArray arrayWithArray:].
You can use following method to get the mutable object from immutable. It's not optimized and only implemented for NSArray and NSDictionary.
+ (id) GetMutable:(id)input {
id result;
if ([input superclass] == [NSArray class] || [input superclass] == [NSMutableArray class]) {
result = [[NSMutableArray alloc] initWithArray:input copyItems:YES];
for (int i = 0; i < [(NSMutableArray*)result count]; i++) {
[(NSMutableArray*)result replaceObjectAtIndex:i withObject:[Globals GetMutable:[(NSMutableArray*)result objectAtIndex:i]]];
}
} else if ([input superclass] == [NSDictionary class] || [input superclass] == [NSMutableDictionary class]) {
result = [[NSMutableDictionary alloc] initWithDictionary:input copyItems:YES];
NSArray *keys=[(NSMutableDictionary*)result allKeys];
for (int i = 0; i < keys.count; i++) {
[(NSMutableDictionary*)result setObject:[Globals GetMutable:[(NSMutableDictionary*)result objectForKey:[keys objectAtIndex:i]]] forKey:[keys objectAtIndex:i]];
}
}
else {
return input;
}
return result;
}
I hope this tutorial helps, who expect answer for this question.
-(void)saveToUserDefaults:(NSString*)myServerName uname:(NSString*)myUserName
pass:(NSString*)myPassword
{
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
if (standardUserDefaults) {
[standardUserDefaults setObject:myServerName forKey:#"ServerKey"];
[standardUserDefaults setObject:myUserName forKey:#"UserNameKey"];
[standardUserDefaults setObject:myPassword forKey:#"PasswordKey"];
[standardUserDefaults synchronize];
}
}
-(NSArray*)retrieveFromUserDefaults
{
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
NSString *serverName = nil;
NSString *userName = nil;
NSString *password = nil;
if (standardUserDefaults)
serverName = [standardUserDefaults objectForKey:#"ServerKey"];
userName = [standardUserDefaults objectForKey:#"UserNameKey"];
password = [standardUserDefaults objectForKey:#"PasswordKey"];
NSArray* credentials = [NSArray arrayWithObjects:serverName,userName,password, nil];
return credentials;
}
To Pass values
[self saveToUserDefaults:serverVariable uname:usernameVariable pass:passVariable];
To get Values
NSArray *result=[self retrieveFromUserDefaults];
Happy coding!!!
The contents of your array can only by plist valid objects: (NSString, NSNumber, NSDate, NSData, NSArray, or NSDictionary objects).
See documentation for NSUserDefaults setObject:forKey: and What is a Property List?
Your code needs a lot of clean up. Here is the simple and correct way to access standard user defaults:
The value you pass to your save method does not need to be mutable, although it can be. But since you are not mutating it inside your method, there's no need for it to be mutable. It just has to be not nil, which you'll check before saving:
-(void)saveToUserDefaults:(NSArray*)myArray
{
if (myArray) {
NSUserDefaults *myDefaults = [NSUserDefaults standardUserDefaults];
myDefaults[#"myArray"] = myArray;
}
}
Standard user defaults only returns non-mutable objects, which you can convert to a mutable copy using the "mutableCopy" method:
-(NSMutableArray*)retrieveFromUserDefaults
{
NSUserDefaults *myDefaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *val = [[NSMutableArray alloc] init];
val = [myDefaults[#"MyArray"] mutableCopy];
return val;
}

Data not save in NSUserDefaults

i having a problem with my NSUserdefaults. When i type in the name in the textbox and click on the highscore the application will not response anything and the keypad will still be on the screen. When i tried to load the data, it say that the data for name is nil. The score that i have is 90. Can someone please tell me what is wrong with my coding.
Lots of thanks.
-(IBAction)savehighscore_button {
int i, ii = -1;
struct high_score {
NSString *name;
int highScore;
};
struct high_score structArray[10];
NSUserDefaults *userPreferences = [NSUserDefaults standardUserDefaults];
for (i=0; i<10; i++) {
if ([userPreferences stringForKey :[NSString stringWithFormat:#"highScoreNameEntry%d",i]]!=nil && [userPreferences stringForKey :[NSString stringWithFormat:#"highScoreEntry%d"]]!=nil) {
structArray[i].name= [userPreferences stringForKey:[NSString stringWithFormat:#"highScoreNameEntry%d",i]];
structArray[i].highScore = [userPreferences integerForKey:[NSString stringWithFormat:#"highScoreEntry%d",i]];
ii = i;
}
}
if (myScore >0) {
for (i==ii; i>=0; i--) {
if (myScore > structArray[i].highScore) {
if (i<9) {
structArray[i+1] = structArray[i];
structArray[i].name = nametextbox.text;
structArray[i].highScore = myScore;
if (i==ii && i<9) {
structArray[i+1].name = nametextbox.text;
structArray[i+1].highScore = myScore;
ii=i+i;
}
else if(i==ii && i<9) {
structArray[i+1].name = nametextbox.text;
structArray[i+1].highScore = myScore;
ii=i+1;
}
}
}
if (ii==-1 && myScore >0) {
structArray[0].name = nametextbox.text;
structArray[0].highScore = myScore;
ii=0;
}
for (i=0; i<=ii; i++) {
[userPreferences setObject:structArray[i].name forKey:[NSString stringWithFormat:#"highScoreNameEntry%d",i]];
[userPreferences setInteger:structArray[i].highScore forKey:[NSString stringWithFormat:#"highScoreEntry%d",i]];
}
}
}
}
Anything saved into user defaults must be of a standard PLIST-compliant class (NSArray, NSDictionary, NSNumber, NSString, NSDate, NSData, NSValue). If it's not one of those, it has to be archived as an NSData instance (and therefore be NSCoding compliant) or packed into an NSValue if it's compatible.
That said, you're making this far more difficult on yourself than you need to. Why not have an array (the high score descriptor container) of dictionaries (each is a descriptor for a high score) that hold a name (an NSString) and the score (an NSNumber)? Each dictionary describes a high score for a given name. You can then store the whole thing by using -setObject:yourArray forKey:#"HighScores".
Since you only get immutable copies back from NSUserDefaults, you'll want to ask for a -mutableCopy of the high scores array (don't forget to release it) when you want to modify things. Either replace a score or delete/add.
Using this approach, you don't have to resort to the (sorry for this) ghastly "string#" approach with a fixed number of scores. Your array will only contain the existing high scores. No waste and all 100% standard Cocoa classes that are fully PLIST'able with no extra work. This also lets you easily sort the array using sort descriptors (sorted by the key you used to store the score number in the dictionary).
Some basic code:
/* Build a fake high scores array */
NSMutableArray * highScores = [NSMutableArray array];
// Bob's score
NSDictionary * bob = [NSDictionary dictionaryWithObjectsAndKeys:
#"Bob", #"name",
[NSNumber numberWithInt:8234323], #"score",
nil];
[highScores addObject:bob];
// Jane's score
NSDictionary * jane = [NSDictionary dictionaryWithObjectsAndKeys:
#"Jane", #"name",
[NSNumber numberWithInt:92346345], #"score",
nil];
[highScores addObject:jane];
// Donald's score
NSDictionary * donald = [NSDictionary dictionaryWithObjectsAndKeys:
#"Donald", #"name",
[NSNumber numberWithInt:5272348], #"score",
nil];
[highScores addObject:donald];
// Sort by high score
NSSortDescriptor * sort = [[NSSortDescriptor alloc] initWithKey:#"score"
ascending:NO];
[highScores sortUsingDescriptors:[NSArray arrayWithObject:sort]];
[sort release];
// Store in user defaults
[[NSUserDefaults standardUserDefaults] setObject:highScores
forKey:#"HighScores"];
Dont forget to use:
[userPreferences synchronize];

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];
}