i have a string declare as such
NSString *str = [[NSString alloc] initWithFormat:#"I require an average GPA of at least %.2f to achieve my Goal of %# this semester - NTU GPA Calculator", pgagoal,(NSString *)[myPickerDelegate.myGoal objectAtIndex: [myPicker selectedRowInComponent:0]]];
i declared a global variable
NSStrinng *tweetString
and wants to copy the the string in str to tweetString. how should i copy it? since both are pointers, i tried:
tweetString = str;
or
tweetString = [NSString stringWithFormat:#"%#", str];
but it doest work.
EDIT:
my code:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex1{
NSLog(#"buttonindex 1 clicked");
NSString *str2;
NSLog(#"tweetString before if: %#", tweetString);
if (pgagoal < 0) {
NSString *str2 = [[NSString alloc] initWithFormat:#"Confirm, Guarantee, Chop and Stamp! I can achieve my Goal of %# this semester - NTU GPA Calculator", (NSString *)[myPickerDelegate.myGoal objectAtIndex: [myPicker selectedRowInComponent:0]]];
NSLog(#"tweetString: < 0 %#", str2);
}
else if (pgagoal > 5){
NSString *str2 = [[NSString alloc] initWithFormat:#"Its impossible!, i need an average GPA of at least %.2f to achieve %# this semester - NTU GPA Calculator", pgagoal,(NSString *)[myPickerDelegate.myGoal objectAtIndex: [myPicker selectedRowInComponent:0]]];
NSLog(#"tweetString: >5 %#", str2);
}
else{
NSString *str2 = [[NSString alloc] initWithFormat:#"I require an average GPA of at least %.2f to achieve my Goal of %# this semester - NTU GPA Calculator", pgagoal,(NSString *)[myPickerDelegate.myGoal objectAtIndex: [myPicker selectedRowInComponent:0]]];
NSLog(#"tweetString with else: %#", str2);
}
//did i update tweetString correctly?
tweetString = [NSString stringWithString:str2]; <-- stop working from this point EXC_BAD_ACCESS
NSLog(#"tweetString after if else: %#", tweetString);
[self sendEasyTweet:tweetString];
NSLog(#"tweetString: %#", tweetString);
[str2 release];
}
- (void)sendEasyTweet {
// Set up the built-in twitter composition view controller.
TWTweetComposeViewController *tweetViewController = [[TWTweetComposeViewController alloc] init];
// Set the initial tweet text. See the framework for additional properties that can be set.
[tweetViewController setInitialText:tweetString];
// Create the completion handler block.
[tweetViewController setCompletionHandler:^(TWTweetComposeViewControllerResult result) {
switch (result) {
case TWTweetComposeViewControllerResultCancelled:
// The cancel button was tapped.
NSLog(#"Tweet cancelled");
break;
case TWTweetComposeViewControllerResultDone:
// The tweet was sent.
NSLog(#"Tweet done");
break;
default:
break;
}
// Dismiss the tweet composition view controller.
[self dismissModalViewControllerAnimated:YES];
}];
// Present the tweet composition view controller modally.
[self presentModalViewController:tweetViewController animated:YES];
}
EDIT2:
Debbuger output:
2011-12-29 09:54:22.963 GPA[487:707] buttonindex 1 clicked
2011-12-29 09:54:22.966 GPA[487:707] tweetString before if: NTU GPA Calculator <-- i init the string at viewDidLoad
2011-12-29 09:54:22.968 GPA[487:707] tweetString with else: I require an average GPA of at least 1.56 to achieve my Goal of Third Class Honors this semester - NTU GPA Calculator
(gdb)
EDIT3:
my tweetString is declared in view controller.h as
#interface GPAMainViewController : UIViewController <GPAFlipsideViewControllerDelegate>{
UIPickerView * myPicker;
GPAAppDelegate * myPickerDelegate;
IBOutlet UITextField *txtGPA;
IBOutlet UITextField *txtTotalAU;
IBOutlet UITextField *txtRemainingAU;
double pgagoal;
NSString *tweetString;
}
#property (nonatomic, retain) IBOutlet UIPickerView * myPicker;
#property (nonatomic, retain) IBOutlet GPAAppDelegate *myPickerDelegate;
#property (nonatomic, retain) UITextField *txtGPA;
#property (nonatomic, retain) UITextField *txtTotalAU;
#property (nonatomic, retain) UITextField *txtRemainingAU;
#property (nonatomic, retain) NSString *tweetString;
-(IBAction)finishEditing:(id)sender;
-(IBAction)calculateGoal: (id) sender;
-(IBAction)showInfo:(id)sender;
-(IBAction)nextField:(id)sender;
-(IBAction)resetField:(id)sender;
-(void)sendEasyTweet:(id)sender;
The reason that it doesn't work (it is probably crashing with an EXC_BAD_ACCESS) is because the scope of the variable str is only within the block in which it is declared, the block of the else part of your if/else statement. Try something like this:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex1 {
NSString* str; //declare string here so it is in scope the entire method
.
. //your code
.
.
if(yourConditionHere) {
//make sure you initialize str here as well so if the else part of the statement
// isn't executed, you aren't trying to access an uninitialized variable
} else {
str = [[NSString alloc] initWithFormat:#"I require an average GPA of at
least %.2f to achieve my Goal of %# this semester - NTU GPA Calculator",
pgagoal,(NSString *)[myPickerDelegate.myGoal objectAtIndex: [myPicker
selectedRowInComponent:0]]]; //give str a value
NSLog(#"tweetString with else: %#", str);
} //Variable str is going out of scope here the way you have your code set up now
tweetString = [str copy];
NSLog(#"tweetString after if else: %#", tweetString);
[self sendEasyTweet:tweetString];
NSLog(#"tweetString: %#", tweetString);
[str release];
}
If you want to copy the string, or use the string after you assign it, you either need to copy it or retain it.
NSString *someString = #"This is a string";
NSString *copiedString = [NSString stringWithFormat:#"%#", someString"];
Within a few seconds, both strings will be nil or some other non value. What you must do is :
NSString *someString = #"This is a string";
NSString *copiedString = [NSString stringWithFormat:#"%#", someString"] retain];
By doing this, you will keep both variables in memory as long as they are viable. But in my opinion a better way, especially when dealing with strings is to use copy, like this :
NSString *someString = #"This is a string";
NSString *copiedString = [NSString stringWithFormat:#"%#", someString"] copy];
This will make someString just go away in a few seconds or clock ticks, but copiedString will live on until the function is finished or the class released.
I suspect that you are not getting the string value inside tweetString because both variables have gone from memory when you want to use it.
If you need a variable to stay around, you must copy or retain it.
Related
I'm using TouchXML to parse an element in iOS. I retrieve a response from a web service using an NSInvocationOperation, then parse and display the results. Everything works fine as the background thread displays results on the main thread using [self performSelectorOnMainThread:#selector(displayLoginresult:) withObject:res waitUntilDone:NO]; but then I get an error:
2011-07-18 11:58:06.108 billsApp[873:7107] *** -[CFString release]: message sent to deallocated instance 0x5d809b0
The code to parse the element is:
-(LoginResult *) tryLogin:(NSString *)userName withPassword:(NSString*)password{
NSURL *url = [UrlUtility TryLogin:userName passwordHash:password];
CXMLDocument *responseObj = [UrlUtility xmlDocWithUrl:url];
if(responseObj == [NSNull null])
return [NSNull null];
CXMLElement *eleUser = [responseObj nodeForXPath:#"//User" error:nil];
CXMLElement *eleResult = [responseObj nodeForXPath:#"//Result" error:nil];
LoginResultType resultType;
//NSLog(#"Result: ");
//NSLog(eleResult );
// NSLog([[eleResult stringValue] lowercaseString]);
if ([[[eleResult stringValue] lowercaseString ] isEqualToString: #"successful"]){
resultType = Successful;
} else {
resultType = InvalidUsernameOrPassword;
}
LoginResult *res = [[LoginResult alloc] init];
res.result = resultType;
for (CXMLElement *resultElement in [responseObj children] ) {
NSLog([NSString stringWithFormat:#"%# %#", [resultElement name], [resultElement stringValue]]);
}
//todo: fix enum parsing =[LoginResult loginResultTypeStringToEnum: [eleResult stringValue]];
if(eleUser != nil) {
CXMLElement *eleClientID = [eleUser nodeForXPath:#"ClientID" error:nil];
CXMLElement *eleCompanyName = [eleUser nodeForXPath:#"CompanyName" error:nil];
CXMLElement *eleCompanyContact = [eleUser nodeForXPath:#"CompanyContact" error:nil];
CXMLElement *eleIsAgent = [eleUser nodeForXPath:#"IsAgent" error:nil];
CXMLElement *eleParentID = [eleUser nodeForXPath:#"ParentID" error:nil];
NSInteger *clientId = [[eleClientID stringValue] integerValue];
NSString *companyName = [eleCompanyName stringValue];
NSString *companyContact = [eleCompanyContact stringValue];
bool isAgent = [Utils stringToBool:[eleIsAgent stringValue]];
NSInteger *parentId = [[eleParentID stringValue] integerValue];
User *user = [[User alloc] initWithData:clientId companyName:companyName companyContact:companyContact isAgent:isAgent parentId:parentId];
res.user = user;
// release elements
// [eleClientID release];
// [eleCompanyName release];
// [eleCompanyContact release];
// [eleIsAgent release];
// [eleParentID release];
//release raw values
// [companyName release];
// [companyContact release];
}
// [eleResult release];
// [eleUser release];
return res;
}
Part of me wants to say it's a bug with TouchXML, but I find that very unlikely. Is there any way to further track down the error?
EDIT: The definitions for the properties on the User class is:
#property (nonatomic, readwrite) NSInteger clientId;
#property (nonatomic, retain) NSString *companyName;
#property (nonatomic, retain) NSString *companyContact;
#property (nonatomic, readwrite) bool isAgent;
#property (nonatomic, readwrite) NSInteger parentId;
And the instance is initialized with:
-(User*)initWithData:(NSInteger *)clientId companyName:(NSString *)company companyContact:(NSString*)contact isAgent:(bool)agent parentId:(NSInteger*)parentId {
//[self = super init];
self.clientId= clientId;
self.companyName= company;
self.companyContact= contact;
self.isAgent = agent;
self.parentId = parentId;
return self;
}
And the LoginResult class is:
#interface LoginResult : NSObject {
LoginResultType result;
User *user;
NSString * const loginResultTypeArray[4];
}
#property (nonatomic, readwrite) LoginResultType result;
#property (nonatomic, retain) User *user;
Just a try: are you correctly retaining companyName and companyContatct in your User class?
EDIT:
Next thing I would check is loginResultTypeArray. How are string assigned to it? I guess that this advice will also sound trivial to you, but it is really difficult to come up with useful suggestion with so little code...
Can't you get some idea about which CFString is actually being released? If it is not an autoreleased object, possibly the stack trace could point at the method which is sending the release message... this would be very helpful...
Otherwise, I would try and NSLog some of your NSStrings addresses, so that you can compare them with the address you find in the error log (and, again, try and find out which string was actually reused after deallocation).
Finally, another approach to find out which string is used after deletion could be using method swizzling to replace NSString's dealloc with a method of yours that, before calling the swizzled dealloc, does some logging of the objec. This will produce much log info, but knowing the address of the string you could find easily what you need. Find here info about swizzling.
This was a nightmare to track down. I had a method which returned an NSString *, which was then parsed by another method to produce an XML document, then release by the second method. I actually needed to autorelease it in the first method.
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.
I keep getting a memory leak indication from this sql statement when I assign the value retrieved from the database...
Person *tmpPerson = [[Person alloc] init];
tmpPerson.personName = [NSString stringWithUTF8String: (char*)sqlite3_column_text(SelectPersonStmt, 0)];
tmpPerson.personEmail = [NSString stringWithUTF8String: (char*)sqlite3_column_text(SelectPersonStmt, 1)];
[personList addObject:tmpPerson];
[tmpPerson release];
However if i replace the nsobject class object ...tmpPerson with regular NSString's ...leaks doesn't complain anymore? Does anyone know why?
NSString * personName = [NSString stringWithUTF8String:(char*)sqlite3_column_text(SelectPersonStmt, 0)];
NSString * personEmail = [NSString stringWithUTF8String:(char*)sqlite3_column_text(SelectPersonStmt, 1)];
Here is my class definition ... is there anything wrong with it?
#interface Person : NSObject {
NSString* personName;
NSString* personMobile;
NSString* personEmail;
}
#property (nonatomic, retain) NSString* personName, *personEmail, *personMobile;
- (id)init
{
if ((self = [super init])) {
personName = [NSString string];
personEmail = [NSString string];
personMobile = [NSString string];
}
return self;
}
Am I missing something here ? Should I be even initializing these strings, it didn't seem to make any difference? I put them there incase i wanted to initialize them with some default value.
While testing this through instruments, i noticed that the memory leak is triggered during the deallocation method. I tried this and it didn't help either
-(void) dealloc
{
personName = nil;
personEmail = nil;
[super dealloc];
}
Any help would be greatly appreciated. I've seen a lot of posts related to this but I'm not sure if folks are getting the same behavior I have mentioned.
You have to release your ivar in the dealloc:
-(void) dealloc
{
[personName release];
[personEmail release];
[personMobile release];
personName = nil; // Optionnal
personEmail = nil; // Optionnal
personMobile = nil; // Optionnal
[super dealloc];
}
You should release the used strings in the Person struct, not setting it to NULL.
Once you set it to NULL and there are no other objects referring to it, you have a leak, the system does not know how to reclaim it.
EDIT: damn, my answer came 10 seconds late :P
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
I am a newbie to the iphone app world. So I thought I try my luck with a calculator app.
Unfortunately I am running into an issue where if I press a third key in the calculator the app crashes. Sometimes I get this error EXC_BAD_ACCESS. Here is a code in my CalculatorViewController.m file.
#import "CalculatorViewController.h"
#implementation CalculatorViewController
#synthesize screenText;
- (IBAction)buttonPressed:(id)sender {
NSString *title = [sender titleForState:UIControlStateNormal];
[self collect:title];
}
- (void)collect:(NSString *)digitz {
NSString * newText = nil;
if ([digitz isEqualToString:#"+"]) {
[self add:result];
big_digit = nil;
}
else if ([digitz isEqualToString:#"+"]) {
[self sub:result];
}
else if ([digitz isEqualToString:#"x"]) {
[self multiply:result];
}
else if ([digitz isEqualToString:#"="]) {
[self equate:result];
}
else {
if (big_digit != nil && [big_digit isEqualToString:#"0"] == FALSE)
big_digit = [big_digit stringByAppendingFormat:#"%#",digitz];
else
big_digit = (NSMutableString *) digitz;
result = (int) big_digit;
newText = [[NSString alloc] initWithFormat:
#"%#",big_digit];
}
screenText.text = newText;
[newText release];
}
- (void)add:(int)res {
NSString * newText = nil;
ans = ans + res;
newText = [[NSString alloc] initWithFormat:
#"%#",ans];
screenText.text = newText;
[newText release];
}
Can anyone spot an obvious issue here. Here is the respective header file too.
#import <UIKit/UIKit.h>
#interface CalculatorViewController : UIViewController {
UILabel *screenText;
int number;
int result;
int ans;
//NSString *big_digit;
NSMutableString * big_digit ;
}
#property (nonatomic, retain) IBOutlet UILabel *screenText;
- (IBAction)buttonPressed:(id)sender;
- (void)collect:(NSString *)digitz;
- (void)add:(int)num;
- (void)sub:(int)num;
- (void)multiply:(int)num;
- (void)equate:(int)num;
#end
Well, you probably don't want to just cast a string to an integer (ala (int)big_digit). Instead you want to use [big_digit integerValue];
I think what is happening is that your big_digit property is not retained. In this line, you just assign a string to it that is autoreleased:
big_digit = [big_digit stringByAppendingFormat:#"%#",digitz];
On the next pass through, big_digit is != nil, but [big_digit isEqualToString:#"0"] == FALSE fails because big_digit now points to an invalid memory location.
What you want to do is make big_digit a property in your interface, like so...
#property (nonatomic, retain) NSMutableString *big_digit;
I know reading docs sucks, but looking at your code I think you would really find reading through this useful. Memory management in objective c is quite a bit different from regular old C.
http://developer.apple.com/iphone/library/documentation/cocoa/conceptual/memorymgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW1
In your format strings, you're doing this:
newText = [[NSString alloc] initWithFormat:#"%#", ans];
But according to your #interface, ans is an integer. So that line should read:
newText = [[NSString alloc] initWithFormat:#"%d", ans];
since %d is the format specifier for an integer.