Memory management of container classes - iphone

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.

Related

writing data using NSKeyedArchiver

I trying to learn how to save array of objects using NSKeyedArchiver and I coded a small application to do that and I logged to see if the array was saved but everytime I get 0 for array count and here is the code.
ViewController.h
#interface ViewController : UIViewController
{
IBOutlet UITextField *text;
IBOutlet UITextField *textName;
IBOutlet UITextField *textAge;
IBOutlet UILabel *name;
IBOutlet UILabel *age;
BOOL flag;
BOOL choice;
NSString *documentDirectory;
NSMutableArray *anArray;
Person *p;
NSData *data;
}
-(BOOL) dataFilePath;
-(IBAction)readPlist;
-(IBAction) writePlist;
#property (strong,nonatomic)IBOutlet UITextField *text;
#property (strong,nonatomic)IBOutlet UITextField *textName;
#property (strong,nonatomic)IBOutlet UITextField *textAge;
#property (strong,nonatomic)IBOutlet UILabel *name;
#property (strong,nonatomic)IBOutlet UILabel *age;
#property (strong,nonatomic)NSString *documentDirectory;
#property (strong,nonatomic)NSMutableArray *anArray;
#end
ViewController.m
#interface ViewController ()
#end
#implementation ViewController
#synthesize text,documentDirectory,textAge,textName,name,age,anArray;
- (void)viewDidLoad
{
[super viewDidLoad];
// checking if the file was created and show a message if its created or not.
if ([self dataFilePath]) {
NSLog(#"File Created !");
} else {
NSLog(#"File Not Created !");
}
NSLog(#"File location : %#",documentDirectory);
choice = YES;
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(BOOL) dataFilePath
{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
documentDirectory = [path objectAtIndex:0];
documentDirectory = [documentDirectory stringByAppendingPathComponent:#"Milmers­Data.dat"];
return TRUE;
}
- (IBAction)writePlist
{
p.name = textName.text;
p.age = [textAge.text intValue];
[anArray addObject:p];
for (int i=0; i<[anArray count]+1; i++) {
Person *pp = [[Person alloc]init];
pp=[anArray objectAtIndex:i];
NSLog(#"Name: %#",pp.name); // checking the names in pp object but getting null
}
data = [NSKeyedArchiver archivedDataWithRootObject:anArray];
[data writeToFile:documentDirectory options:NSDataWritingAtomic error:nil];
NSLog(#"Array length: %d",[anArray count]); //Always got array count zero.
}
-(IBAction)readPlist
{
NSString *filePath = documentDirectory;
NSMutableArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(#"The array is: %#",array); // it shows that there is nothing in the array
}
#end
I wrote the class for writing .plist files originally but I knew later that I cant store objects in .plist file so I tried so that with archive, thats why the method name have plist in it.
Thank you in advance
Looks like you aren't ever creating an instance of p to add to the array. Try:
Person *p = [[Person alloc] init];
p.name = textName.text;
p.age = [textAge.text intValue];
[anArray addObject:p];
your index limit was also wrong in this loop
for (int i=0; i<[anArray count]; i++) {
NSLog(#"Name: %#", [[anArray objectAtIndex:i] name]);
}
you should really have been seeing a couple of different crashes...
Try adding this in viewDidLoad
[[NSFileManager defaultManager] createFileAtPath:documentDirectory contents:nil error:nil];
It looks like you never do this, and using archives to write to files only works if the file already exists (make sure you only do this once, otherwise every time that view is loaded the file will be emptied of all the data in it). And when you do this
if ([self dataFilePath])
It's pointless, because no matter what it always returns yes, whether the file exists or not.
Does your Person class implement NSCoding?
Specifically you need to implement something like the following in Person.m:
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (!self) {
return nil;
}
self.name = [decoder decodeObjectForKey:#"name"];
self.age = [decoder decodeObjectForKey:#"age"];
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.name forKey:#"name"];
[encoder encodeObject:self.age forKey:#"age"];
}

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.)

return a static const []

So in my model I have the following code... I am successfully able to return each individual value. I want to know how am I able to return the entire speakerTable []... Maybe some advice. Thanks!
typedef struct {
NSUInteger speakerID;
NSString * speakerName;
NSString * speakerPosition;
NSString * speakerCompany;
} SpeakerEntry;
static const SpeakerEntry speakerTable [] =
{
{0, #"name", #"position", #"company"},
{1, #"name", #"position", #"company"},
{-1, nil, nil, nil}
};
This works correctly...
-(NSString *) stringSpeakerCompanyForId:(NSUInteger) identifier{
NSString * returnString = nil;
if ([self helpCount] > identifier) {
returnString = speakerTable[identifier].speakerCompany;
}
return returnString;
}
This does not work at all..
-(id) getSpeaker{
//if ([speakerTable[0].speakerName isKindOfClass:[NSString class]])
// NSLog(#"YES");
NSArray * myArray3 = [NSArray arrayWithArray:speakerTable];
return myArray3;
}
arrayWithArray expects an NSArray, not a C array.
The first one works because you are using it like a C array.
Alternatively - don't use a struct, use an object instead:
Create a class called Speaker.
In Speaker.h
#interface Speaker : NSObject {}
#property (nonatomic, assign) NSUinteger id;
#property (nonatomic, copy) NSString name;
#property (nonatomic, copy) NSString position;
#property (nonatomic, copy) NSString company;
- (void)initWithId:(NSUInteger)anId name:(NSString *)aName position:(NSString *)aPosition company:(NSString *)aCompany;
#end
in Speaker.m
#import "Speaker.h"
#implementation Speaker
#synthesize id, name, position, company;
- (void)initWithId:(NSUInteger)anId name:(NSString *)aName position:(NSString *)aPosition company:(NSString *)aCompany {
if (!([super init])) {
return nil;
}
id = anId;
NSString name = [[NSString alloc] initWithString:aName];
NSString position = [[NSString alloc] initWithString:aPosition];
NSString company = [[NSString alloc] initWithString:aCompany];
return self;
}
- (void)dealloc {
[name release];
[position release];
[company release];
[super dealloc];
}
#end
And now in your calling code you can create an immutable array of speakers with:
Speaker *speaker0 = [[Speaker alloc] initWithId:0 name:#"name0" position:#"position0" company:#"company0"];
Speaker *speaker1 = [[Speaker alloc] initWithId:1 name:#"name1" position:#"position1" company:#"company1"];
Speaker *speakerNull = [[Speaker alloc] initWithId:-1 name:nil position:nil company:nil];
NSArray *speakerArray [[NSArray arrayWithObjects: speaker0, speaker1, speakerNull] retain]
[speaker0 release];
[speaker1 release];
[speakerNull release];
note: this is typed straight in, so feel free to mention/correct typos or errors
The method arrayWithArray takes in an NSArray as an argument, not a C 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.

NSString's superclasses go on forever

I've got a method that when put a breakpoint in it and hover over a string, says it's out of scope and you can drill down into the NSString object for what seems like forever. I've tried to put a screen shot... hope it shows up. I think I have some serious memory management problems...
http://web.me.com/gazelips/Site/Blank_files/screenshot.jpg
Here's the whole .h and .m files. The problem occurs in the updateAdvice method.
#import <UIKit/UIKit.h>
#import "SourcePickerViewController.h"
#import "Chemical.h"
#interface AdjustViewController : UIViewController <SourcePickerViewControllerDelegate>{
// IB controls
UITextField *sourceField;
UITextField *volumeField;
UILabel *startingLabel;
UILabel *targetLabel;
UITextView *adviceLabel;
// Setup variables for the kind of chemical
int numberOfComponents;
NSDictionary *dictionaryOfSources;
// Local ivars
float percentRemove;
float gallonsRemove;
float selectedChemSourceAmount;
int delta;
NSString *selectedChemName;
NSString *selectedChemConcentration;
float selectedChemConstant;
BOOL selectedChemIsLiquid;
NSString *compositeName;
NSString *messageBody;
NSString *adviceMessage;
}
#property (nonatomic, retain) IBOutlet UITextField *sourceField;
#property (nonatomic, retain) IBOutlet UITextField *volumeField;
#property (nonatomic, retain) IBOutlet UILabel *startingLabel;
#property (nonatomic, retain) IBOutlet UILabel *targetLabel;
#property (nonatomic, retain) IBOutlet UITextView *adviceLabel;
#property (nonatomic, retain) NSString *selectedChemName;
#property (nonatomic, retain) NSString *selectedChemConcentration;
#property float selectedChemConstant;
#property BOOL selectedChemIsLiquid;
#property (nonatomic, retain) NSString *compositeName;
#property (nonatomic, retain) NSString *messageBody;
#property (nonatomic, retain) NSString *adviceMessage;
#property int numberOfComponents;
#property (nonatomic, retain) NSDictionary *dictionaryOfSources;
- (IBAction)backgroundTap:(id)sender;
//- (IBAction)textFieldDoneEditing:(id)sender;
- (IBAction)startingSliderChanged:(id)sender;
- (IBAction)startingSliderFinishedChanging;
- (IBAction)targetSliderChanged:(id)sender;
- (IBAction)targetSliderFinishedChanging;
- (IBAction)getChemicalSource;
- (void)updateAdvice;
#end
#import "AdjustViewController.h"
#implementation AdjustViewController
#synthesize sourceField;
#synthesize volumeField;
#synthesize startingLabel;
#synthesize targetLabel;
#synthesize adviceLabel;
#synthesize numberOfComponents;
#synthesize dictionaryOfSources;
#synthesize compositeName;
#synthesize messageBody;
#synthesize adviceMessage;
#synthesize selectedChemName;
#synthesize selectedChemConcentration;
#synthesize selectedChemConstant;
#synthesize selectedChemIsLiquid;
- (IBAction)backgroundTap:(id)sender {
[sourceField resignFirstResponder];
[volumeField resignFirstResponder];
[self updateAdvice];
}
- (IBAction)startingSliderChanged:(id)sender {
UISlider *slider = (UISlider *)sender;
int progressAsInt = (int)(slider.value + 0.5f);
NSString *newValue = [[NSString alloc] initWithFormat:#"%d", progressAsInt];
startingLabel.text = newValue;
[newValue release];
}
- (IBAction)targetSliderChanged:(id)sender {
UISlider *slider = (UISlider *)sender;
int progressAsInt = (int)(slider.value + 0.5f);
NSString *newValue = [[NSString alloc] initWithFormat:#"%d", progressAsInt];
targetLabel.text = newValue;
[newValue release];
}
- (IBAction)startingSliderFinishedChanging {
// [self updateAdvice];
}
- (IBAction)targetSliderFinishedChanging {
// [self updateAdvice];
}
// Present the picker for chlorine selection
- (IBAction)getChemicalSource {
SourcePickerViewController *sourcePickerViewController = [[SourcePickerViewController alloc] init];
sourcePickerViewController.delegate = self;
NSLog(#"getChemicalSource setting numberOfComponents %d", self.numberOfComponents);
sourcePickerViewController.numberOfComponents = self.numberOfComponents;
NSLog(#"getChemicalSource sending numberOfComponents %d", sourcePickerViewController.numberOfComponents);
sourcePickerViewController.dictionaryOfSources = self.dictionaryOfSources;
[self presentModalViewController:sourcePickerViewController animated:YES];
[sourcePickerViewController release];
}
- (void)updateAdvice {
NSLog(#"--updateAdvice");
NSLog(#" selectedChemical name = %#", selectedChemName);
NSLog(#" selectedChemical concentration = %#", selectedChemConcentration);
NSLog(#" selectedChemical constant = %1.6f", selectedChemConstant);
NSLog(#" selectedChemical is liquid = %d", selectedChemIsLiquid);
// First check to see if there is a source AND volume, otherwise prompt user to enter them
if ([volumeField.text isEqualToString:#""] || [sourceField.text isEqualToString:#""]) {
adviceMessage = #"Enter a source and volume.";
}
// If there IS a source and volume, calculate!
else {
if ([selectedChemConcentration isEqualToString:#""]) { // If there's no concentration, make a string with just the name
compositeName = selectedChemName;
NSLog(#" compositeName without concentration = %#", compositeName);
}
else { // But if there is a concentration, make a string with the name and concentration and a space between.
compositeName = [[NSString alloc] initWithFormat:#"%# %#", selectedChemName, selectedChemConcentration];
NSLog(#" compositeName with concentration = %# %#", compositeName, selectedChemConcentration);
}
delta = [targetLabel.text intValue] - [startingLabel.text intValue]; // The difference between target and starting levels
NSLog(#" delta = %d", delta);
sourceAmount = delta * [volumeField.text intValue] * sourceConstant; // Calculates the amount of source chemical necessary in ounces
NSLog(#" sourceAmount = %1.1f", sourceAmount);
// If delta is positive, add chemical
if (delta > 0) {
NSLog(#">> Delta > 0");
if (selectedChemIsLiquid) {
if (sourceAmount > 128) { // Amount is more than a gallon
sourceAmount = sourceAmount / 128;
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f gal of ", self.title, delta, sourceAmount];
}
else { // Less than a gallon
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f fl oz of ", self.title, delta, sourceAmount];
}
}
else { // Chemical is a solid
if (sourceAmount > 16) { // Amount is more than a pound
sourceAmount = sourceAmount / 16;
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f lb of ", self.title, delta, sourceAmount];
}
else { // Less than a pound
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f oz of ", self.title, delta, sourceAmount];
}
}
adviceMessage = [[NSString alloc] initWithFormat:#"%#%#.", messageBody, compositeName];
}
// If delta is zero, stay the course
if (delta == 0) {
NSLog(#"== Delta = 0");
adviceMessage = #"You're on target. No action necessary.";
}
// If delta is negative, remove water
if (delta < 0) {
NSLog(#"<< Delta < 0");
adviceMessage = #"You're over target. Remove some water.";
}
}
adviceLabel.text = adviceMessage; // Set the advice label
[messageBody release]; // And get rid of message
[compositeName release];
[adviceMessage release];
}
- (void)viewDidLoad {
NSLog(#"AdjustViewController launched");
sourceField.text = #"";
adviceLabel.text = #"";
percentRemove = 0;
gallonsRemove = 0;
delta = 0;
selectedChemSourceAmount = 0;
// [self updateAdvice];
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
sourceField = nil;
volumeField = nil;
startingLabel = nil;
targetLabel = nil;
adviceLabel = nil;
dictionaryOfSources = nil;
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[sourceField release];
[volumeField release];
[startingLabel release];
[targetLabel release];
[adviceLabel release];
[dictionaryOfSources release];
[super dealloc];
}
#pragma mark -
#pragma mark Picker View Delegate Methods
// Returns the values from the picker if a source was chosen
- (void)sourcePickerViewController:(SourcePickerViewController *)controller
didSelectSource:(NSString *)source
andConcentration:(NSString *)concentration
andConstant:(float)constant
andIsLiquid:(BOOL)isLiquid {
selectedChemName = source;
selectedChemConcentration = concentration;
selectedChemConstant = constant;
selectedChemIsLiquid = isLiquid;
// Update the source textfield. If concentration is empty, just use the source otherwise concatenate them
if ([selectedChemConcentration isEqualToString:#""]) {
sourceField.text = [[NSString alloc] initWithFormat:#"%#", selectedChemName];
}
else {
sourceField.text = [[NSString alloc] initWithFormat:#"%# %#", selectedChemName, selectedChemConcentration];
}
// [self updateAdvice];
NSLog(#"Returned source = %#, concentration = %#, constant = %1.7f, isLiquid = %d", source, concentration, constant, isLiquid);
NSLog(#"selectedChemical.chemName = %#, chemConcentration = %#, chemConstant = %1.7f, chemIsLiquid = %d", selectedChemName, selectedChemConcentration, selectedChemConstant, selectedChemIsLiquid);
[self dismissModalViewControllerAnimated:YES];
}
// Returns from the picker without choosing a new source
- (void)sourcePickerViewController:(SourcePickerViewController *)controller
didSelectCancel:(BOOL)didCancel {
// [self updateAdvice];
NSLog(#"Returned without selecting source");
[self dismissModalViewControllerAnimated:YES];
}
#end
What you're seeing there is not an infinite chain of superclasses (if you look at the actual address you'll see it does not change after the second time), you're actually looking at the NSObject metaclass. Have a quick read through that link—wonderfully provided by Matt Gallagher—and it will explain what you're seeing in the debugger. NSObject's metaclass's metaclass is itself.
This is actually quite common and not necessarily anything to do with a bug in your code.
If you right click on the string object in the debugger window and select "print description to console", it should work just fine.
I'm just looking at your code anyway. What the other two people said hold... however, memory management issues:
if (sourceAmount > 16) { // Amount is more than a pound
sourceAmount = sourceAmount / 16;
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f lb of ", self.title, delta, sourceAmount];
}
else { // Less than a pound
messageBody = [[NSString alloc] initWithFormat:#"To increase %# by %d ppm, add %1.1f oz of ", self.title, delta, sourceAmount];
}
[...]
[messageBody release]; // And get rid of message
[compositeName release];
[adviceMessage release];
I think it would be better for you to simply write
self.messageBody = [NSString stringWithFormat:#"To increase %#...", self.title];
This automatically invokes the accessor methods which you synthesized using #synthesize messageBody.
It will automatically release the old string, and retain the new one.
What you are doing right now is allocating a new string without releasing the old string. And why do that yourself when you have already synthesized the accessor methods?
Finally, don't forget to properly release the messageBody in dealloc, where you should be releasing it.
- (void)dealloc
{
[messageBody release];
[everythingElseIHaveToRelease release];
[super dealloc];
}
Hope that helps!
H