Sorting an NSMutableArray with custom objects 'overwrites' some objects - iphone

For a little iPhone application I am making, I want to sort a NSMutableArray.
I found 2 ways of doing this, but they both result in the same thing. Sorting the array will cause some objects to 'overwrite' eachother.
First off, here is my code:
AppDelegate.h
NSMutableArray* highScores;
Somewhere down that AppDelegate.h, I also make this variable a property so that I can access it from differen classes:
#property (retain, nonatomic) NSMutableArray* highScores;
When my application starts, I read the high scores from a file and import them into my NSMutableArray.
AppDelegate.m
NSMutableData* data = [NSData dataWithContentsOfFile:highScoresPath];
NSKeyedUnarchiver* decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
self.highScores = [decoder decodeObjectForKey:#"highscoresArray"];
The objects I store in this NSMutableArray are from the type HighScore.
HighScore.h
#interface HighScore : NSObject {
int score;
int roundsPlayed;
int wrongAnswers;
NSString* name;
NSDate* datetime;
}
#property int score;
#property int roundsPlayed;
#property int wrongAnswers;
#property (nonatomic, copy) NSDate* datetime;
#property (nonatomic, copy) NSString* name;
- (id) init;
- (void) update:(int)roundScore:(BOOL) correct;
#end
HighScore.m
#import "HighScore.h"
#implementation HighScore
#synthesize score, roundsPlayed, wrongAnswers, name, datetime;
- (id) init
{
self.name = #"";
self.score = 0;
self.roundsPlayed = 0;
self.wrongAnswers = 0;
self.datetime = [NSDate date];
return self;
}
- (void) update:(int)roundScore:(BOOL) correct
{
self.score += roundScore;
if (!correct)
self.wrongAnswers++;
self.roundsPlayed++;
self.datetime = [NSDate date];
}
- (id) initWithCoder:(NSCoder *) decoder
{
self.name = [[decoder decodeObjectForKey:#"name"] retain];
self.score = [decoder decodeIntForKey:#"score"];
self.roundsPlayed = [decoder decodeIntForKey:#"roundsPlayed"];
self.wrongAnswers = [decoder decodeIntForKey:#"wrongAnswers"];
self.datetime = [[decoder decodeObjectForKey:#"datetime"] retain];
return self;
}
- (void) encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:self.name forKey:#"name"];
[encoder encodeInt:self.score forKey:#"score"];
[encoder encodeInt:self.roundsPlayed forKey:#"roundsPlayed"];
[encoder encodeInt:self.wrongAnswers forKey:#"wrongAnswers"];
[encoder encodeObject:self.datetime forKey:#"datetime"];
}
- (NSComparisonResult) compareHighscore:(HighScore*) h
{
return [[NSNumber numberWithInt:self.score] compare:[NSNumber numberWithInt:h.score]];
}
#end
Now, when I try to sort my array by using the following code:
NSArray *sortedArray;
sortedArray = [highScores sortedArrayUsingSelector:#selector(compareHighscore:)];
It somehow screws up my highScores array, I get an X amound of highscores with the same score and name.
What am I doing wrong?

I'm noticing that in your initWithCoder: method, you're not doing this:
if (self = [super initWithCoder:coder]) {
// Decode your stuff here
}
Same with your regular init method. There needs to be a call to [super init].
Also, since you defined your string properties as copy and you're using the property syntax, there's no need to retain them. They will be retained for you by the synthesized accessor.
Otherwise, your code looks fine to me. Just remember: every init method must always have a call to a super's init... method.

You're trying to sort using #selector(compare:), not #selector(compareHighscore:), which I presume was your intention.

try
sortedArray = [highScores sortedArrayUsingSelector:#selector( compareHighscore: )];

Post the actual compareHighscore: method. The most important thing is that it has to be consistent, that is if a <= b and b <= c, then a <= c and if a < b and b < c then a < c. If you managed to write a compare method that is not consistent, anything can happen.

Related

how to decode multiple objects from data stored in file in iOS

I have a form with information first name and last name and some other information. I use a person class to store this information. on submit click I archiving it in a file person.txt using NSCoding implemented in person class. if I add multiple persons in the file person.txt, how can I get all the person objects stored in the file. decoding the person class just gives me the last added person.
If you want all of the person objects serialized, then you need the NSArray or whatever other collection class in which they are stored to be the root object for the NSKeyedArchiver. e.g.: (assumes ARC)
#import <Foundation/Foundation.h>
#interface Person:NSObject <NSCoding>
#property (nonatomic, copy) NSString *lastName;
#property (nonatomic, copy) NSString *firstName;
// etc.
#end
#implementation Person
#synthesize lastName = _lastName;
#synthesize firstName = _firstName;
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.lastName forKey:#"ln"];
[aCoder encodeObject:self.firstName forKey:#"fn"];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if( !self ) { return nil; }
_lastName = [aDecoder decodeObjectForKey:#"ln"];
_firstName = [aDecoder decodeObjectForKey:#"fn"];
return self;
}
#end
int main(int argc, char *argv[]) {
NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
Person *me = [Person new];
me.lastName = #"Kitten";
me.firstName = #"Mittens";
Person *you = [Person new];
you.lastName = #"Youe";
you.firstName = #"JoJo";
NSArray *people = [NSArray arrayWithObjects:me,you,nil];
NSData *serializedData = [NSKeyedArchiver archivedDataWithRootObject:people];
// write your serializedData to file, etc.
[p release];
}
Why the .txt extension on your archive, though? It just binary data, right?

Memory leak within graph datasource

I have recently inherited a large project using an sqlite3 database and currently I am muscling through a significant amount of memory leaks scattered throughout. A couple of the leaks have me confused and demoralised after not being able to solve them. This loop within a method leaks an awful amount of bytes but appears to be so simplistic I simply don't know how to change it to prevent the leak.
...
while ((ret=sqlite3_step(selStmt))==SQLITE_ROW)
{
GraphData *item = [GraphData alloc];
item.key = sqlite3_column_int(selStmt, 0);
item.value = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selStmt,1)];
[newData addObject:item];
[item release], item = nil;
}
...
#interface GraphData : NSObject{
NSInteger key;
NSString *value;
}
#property (nonatomic, readwrite) NSInteger key;
#property (nonatomic, retain) NSString *value;
-(id)initWithPrimaryKey:(NSInteger) xid;
-(id)initWithName:(NSString *)n key:(NSInteger)i;
#end
#import "GraphData.h"
#implementation GraphData
#synthesize key,value;
-(id)initWithPrimaryKey:(NSInteger) xid{
self.key = xid;
self.value = #"";
return self;
}
-(id)initWithName:(NSString *)n key:(NSInteger)i{
self.key = 0;
self.value = n;
return self;
}
#end
Almost all the leaks within this datasource class come from this loop.
I'm hoping this is something trivial that my inexperience has overlooked.
Thanks for taking the time to look at my question.
There's no dealloc in your GraphData class.
- (void)dealloc {
[value release];
[super dealloc];
}
(I assume you're not using ARC because you have release in your first code snippet. I really recommend converting to ARC - this kind of leak vanishes.)

Memory management of container classes

I've made a container class to store a single tweet. Its initialized by passing in a dictionary object which is a single tweet.
I then store an array of these 'tweets' which I process through to display in a table.
The project is now finished and I am reviewing everything at the moment and I was wondering is there a better way to do this in the future. Is the memory handled correctly. I declare the string member vars with 'copy' and later in the dealloc I use a 'release' rather than just setting them to 'nil'.
Is my init ok or could that be improved?
Tweet.h
#import
#interface Tweet : NSObject
{
NSString * _userName;
NSString * _tweetText;
NSString * _tweetURL;
}
#property (nonatomic, copy) NSString * userName;
#property (nonatomic, copy) NSString * tweetText;
#property (nonatomic, copy) NSString * tweetURL;
- (id) initWithDict:(NSDictionary *)productsDictionary;
#end
Tweet.m
#implementation Tweet
#synthesize userName = _userName;
#synthesize tweetText = _tweetText;
#synthesize tweetURL = _tweetURL;
- (id) initWithDict:(NSDictionary *)productsDictionary
{
NSDictionary *aDict = [productsDictionary objectForKey:#"user"];
self.userName = [aDict objectForKey:#"screen_name"];
self.tweetText = [productsDictionary objectForKey:#"text"];
NSRange match;
match = [self.tweetText rangeOfString: #"http://"];
if (match.location != NSNotFound)
{
NSString *substring = [self.tweetText substringFromIndex:match.location];
NSRange match2 = [substring rangeOfString: #" "];
if (match2.location == NSNotFound)
{
self.tweetURL = substring;
}
else
{
self.tweetURL = [substring substringToIndex:match2.location];
}
}
else
{
self.tweetURL = nil;
}
return self;
}
-(void) dealloc
{
[self.tweetText release];
[self.tweetURL release];
[self.userName release];
[super dealloc];
}
#end
Many Thanks,
Code
At first sight, I see no inherent flaws here. That looks fine. I would prefer to do:
-(void) dealloc
{
[_tweetText release];
[_tweetURL release];
[_userName release];
[super dealloc];
}
But what you do is good as well.

invalid CFArrayRef problem with Singleton object

I've built a singleton object to manage some data in my app
#interface MyCommon : NSObject {
NSArray *quizz;
int iCurrentQuestion;
};
+ (MyCommon *)singleton;
#property (retain) NSArray *quizz;
#property (assign) int iCurrentQuestion;
#end
MyCommon.m
#import "MyCommon.h"
// MyCommon.m:
#implementation MyCommon
static MyCommon * MyCommon_Singleton = nil;
#synthesize iCurrentQuestion;
+ (MyCommon *)singleton
{
if (nil == MyCommon_Singleton)
{
MyCommon_Singleton = [[MyCommon alloc] init];
NSLog(#"allocating MyCommon_Singleton at %#",MyCommon_Singleton);
}
else {
NSLog(#"accessing singleton : %#", MyCommon_Singleton);
}
return MyCommon_Singleton;
}
- (NSArray*) getQuizz{
return quizz;
}
- (void) setQuizz:(NSArray *)array {
quizz = [NSArray arrayWithArray:array];
NSLog(#"setQuizz : %#",quizz);
}
There is no problem for writing the quizz object (setQuizz), however when I try to access it for reading, I get a crash : the quizz looks invalid and Xcode notify me an invalid CFArrayRef
I don't know what's wrong with my code.
You provide a custom setter for quizz but it doesn't comply with how the property is declared.
You're not retaining quizz when you're setting a new value. It's likely to be released just after, leading to a crash when you access it.
You should write
- (void)setQuizz:(NSArray *)array {
if (quizz != array) {
NSArray *tmp = quizz;
quizz = [array retain]; // retain the new value
[tmp release]; // release the old one
}
NSLog(#"setQuizz : %#",quizz);
}
this is way more code than it needs to be. First if you are going to be providing your own method you should declare so in the #property declaration which you didn't. Also your not properly retaining your variables. Additionally you should be using dispatch_once() for a thread safe & fast way to guarantee the singleton is only created once.
#interface MyCommon : NSObject {}
#property(nonatomic, retain) NSArray *quiz;
#property (assign) int iCurrentQuestion;
+ (MyCommon *)singleton;
#end
#implementation MyCommon
#synthesize quiz;
#synthesize iCurrentQuestion;
-(id)init {
self = [super init];
if(self) {
quiz = [[NSMutableArray alloc init];
iCurrentQuestion = 0;
}
return self;
}
+ (MyCommon *)singleton {
static MyCommon *singleton = nil;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
singleton = [[MyCommon alloc] init];
});
return singleton;
}
#end
then you just do
[MyCommon singleton].quiz = //some array

Wondering do i have a mem leak(reported as potential)

I create a NSMutableString called mutableScoreHolder outside my do-while loop.
I alloc it, 'copy' it into an other object called a 'Match' that contains a NSString.
When I build and analyze the code it reports a potential memory leak of my mutableScoreHolder.
NSString * scoreHolder;
NSMutableString * mutableScoreHolder;
count = 1;
numMatchCounter = 0;
int startOfScoreIndex = 0;
int endOfScoreIndex = 0;
int numberOfGoals=0;
do
{
match = [substring rangeOfString: #"points_bg"];
if (match.location == NSNotFound)
{
break;
}
if ((match.location + match.length) > ([substring length]))
{
break;
}
substring = [substring substringFromIndex:(match.location + match.length)];
match2 = [substring rangeOfString: #">"];
startOfScoreIndex = match2.location+1;
match2 = [substring rangeOfString: #"<"];
endOfScoreIndex = match2.location;
if ((count % 2) == 1)
{
scoreHolder = [substring substringWithRange:NSMakeRange(startOfScoreIndex, (endOfScoreIndex-startOfScoreIndex))];
mutableScoreHolder = [[NSMutableString alloc] initWithString:scoreHolder];
numberOfGoals += [scoreHolder intValue];
}
else
{
Match *aMatch = [theMatchScoresArray objectAtIndex:(numMatchCounter)];
numberOfGoals += [[substring substringWithRange:NSMakeRange(startOfScoreIndex, (endOfScoreIndex-startOfScoreIndex))] intValue];
[scoreHolder stringByAppendingString:#" - "];
[mutableScoreHolder appendString:#" - "];
[scoreHolder stringByAppendingString:[substring substringWithRange:NSMakeRange(startOfScoreIndex, (endOfScoreIndex-startOfScoreIndex))]];
[mutableScoreHolder appendString:[substring substringWithRange:NSMakeRange(startOfScoreIndex, (endOfScoreIndex-startOfScoreIndex))]];
aMatch.theScore = [mutableScoreHolder copy];
aMatch.matchGoalCount = numberOfGoals;
numMatchCounter++;
numberOfGoals=0;
}
count++;
}
while ( match.length != 0 );
Here is my Match Object Class.
#interface Match : NSObject
{
NSString *teamName1;
NSString *teamName2;
NSString *theScore;
int matchGoalCount;
NSMutableArray *scorersArray;
NSMutableArray *scorerOrderArray;
NSString *matchStatus;
NSString *matchDate;
bool matchNotStarted;
}
#property(nonatomic) int matchGoalCount;
#property(nonatomic) bool matchNotStarted;
#property(nonatomic, retain) NSString *teamName1;
#property(nonatomic, retain) NSString *teamName2;
#property(nonatomic, retain) NSString *theScore;
#property(nonatomic, retain) NSString *matchStatus;
#property(nonatomic, retain) NSString *matchDate;
#property(nonatomic, retain) NSMutableArray *scorersArray;
#property(nonatomic, retain) NSMutableArray *scorerOrderArray;
-(id)init;
#end
#import "Match.h"
#implementation Match
#synthesize teamName1;
#synthesize teamName2;
#synthesize theScore;
#synthesize scorersArray;
#synthesize matchDate;
#synthesize matchStatus;
#synthesize matchGoalCount;
#synthesize scorerOrderArray;
#synthesize matchNotStarted;
-(id)init
{
//NSLog(#"****** MATCH INIT ******");
self = [super init];
scorersArray =[[NSMutableArray alloc] init];
scorerOrderArray =[[NSMutableArray alloc] init];
return self;
}
-(void) dealloc
{
[scorersArray release];
[scorerOrderArray release];
[teamName1 release];
[teamName2 release];
[theScore release];
[matchStatus release];
[matchDate release];
[scorersArray release];
[scorerOrderArray release];
[super dealloc];
}
#end
I do find there there is a string leaked when i run instruments checking for leaks. So I think this 'potential leak' might be the leak I see.
Because the scores has a retain
#property(nonatomic, retain) NSString *theScore;
And there is a string copied into it
aMatch.theScore = [mutableScoreHolder copy];
Could that give a retain count of 2? And so then leak?
Sorry for the complicated question! Has my head spinning trying to get my head around it.
Thanks
-Code
You're definitely leaking here, for 2 separate reasons.
The first is you're alloc/init'ing an NSMutableString and stuffing it into mutableScoreHolder. This is a local variable, and as soon as this value goes out of scope (or gets replaced the next time a new array is created) the old value is leaked. This should be an autoreleased value instead, as in [NSMutableString stringWithString:scoreHolder].
The second is you're copying the string and stuffing the resulting value into a property. What you should do is redeclare that property as copy
#property(nonatomic, copy) NSString *theScore;
and then just assign mutableScoreHolder to that property directly
aMatch.theScore = mutableScoreHolder
With your existing code, you copy the array, and then the property retains it. With this change, the property copies it directly, and no extra retains are used.
Note, in general it's a good idea to declare properties with supported types as copy. This includes things like NSString, NSArray, NSDictionary, etc. If you're assigning an already-immutable object to the property, the copy falls back to retain instead and there's no performance hit. But in situations like yours where you're assigning mutable objects, it will copy it as appropriate and keep an immutable snapshot in the property.
And there is a string copied into it
aMatch.theScore = [mutableScoreHolder copy];
Could that
give a retain count of 2? And so then
leak?
exactly. You could change the property of theScore from retain to copy to fix this. THen you can use aMatch.theScore = mutableScoreHolder;
and there is (at least) one other leak:
mutableScoreHolder = [[NSMutableString alloc] initWithString:scoreHolder];
this gets never released.