views:

157

answers:

3

I have a string (currently defined in my .h file) that I'd like to fill and reuse in my .m file.

Here's the setup:

  1. User clicks btnA in my interface
  2. btnA runs a method (buttonAClicked) that sets the NSString's value to "foo"
  3. USer clicks btnB in my interface
  4. btnB runs a method (buttonBClicked) that returns the value of the NSString (eg: "foo")
  5. User clicks btnA again and the method updates the NSString "a new value"...
  6. Profit!

Here's some code:

/* Modal_TestAppDelegate.h */
// in the @interface block //
@public
NSString *countOfMatches;
// in the main area of the .h //
@property (nonatomic, readwrite, reatain) NSString *countOfMatches;
/* Modal_TestAppDelegate.m */
@synthesize countOfMatches;
-(void)applicationDidFinishLaunching:(UIApplication *)application{
... other code ...
self.countOfMatches = [[NSString alloc] initWithFormat:@"0"];
}
-(void)updateButtonClicked:(id)sender{
countOfMatches = @"1";
NSLog(@"countOfMatches is now: %@",countOfMatches);
}
-(void)readButtonClicked:(id)sender{
NSLog(@"I wonder what countOfMatches is set to now? %@",countOfMatches); // CRASH!
}

the "readButtonCicked area is where I'm crashing - it looks like I can't read the countOfMatches string anymore.

Any ideas on how I can simply reuse a "variable" throughout a single class (if I'm calling the .m implementation a "class" correctly - this is my first attempt and I'm kinda ripping pages out of the several Xcode and iPhone SDK books I have).

Thanks!

+3  A: 

You should set your NSString property to copy, not retain. (More here)

@property (nonatomic, readwrite, copy) NSString *countOfMatches;

You're also leaking memory on this line

self.countOfMatches = [[NSString alloc] initWithFormat:@"0"];

It could be

self.countOfMatches = [[[NSString alloc] initWithFormat:@"0"] autorelease];

or even better:

self.countOfMatches = [NSString stringWithFormat:@"0"];

or even best (and really, what it should be):

self.countOfMatches = @"0";

There's no sense using any of the "format" methods of NSString -- you're just setting it to a static string.

Shaggy Frog
Thanks for the cleanup - now, how would I go about accessing that inside the second Action? I'll test out what you've posted, but it looks like I'm still not able to get access to the countOfMatches inside the "readButtonClicked" method.
Chad Edge
Hot-dog! that seems to work. Now, in my "-(void)readButtonClicked:(id)sender{NSLog(@"I wonder what countOfMatches is set to now? %@",countOfMatches); // CRASH!}" I'm getting the correct return of "countOfMatches."Finally, it's time to iterate through that (it's actually a holder for the number of matches in a UIWebView) - in order to scrollTo the right part of a Web page. Thanks a ton!
Chad Edge
Well, countOfMatches doesn't exist inside readButtonClicked's scope, and since both updateButtonClicked and readButtonClicked exist at the same level, both methods should be able to "see" countOfMatches.
Shaggy Frog
If you're using countOfMatches as a counter (and I'm pretty sure you are), why are you using an NSString? I would use a number type (like NSUInteger) myself, as it's much easier to increment that, than it is to increment a *string* *representation* of a number.
Shaggy Frog
Yeah, I am using it as a counter - it was originally set that way because I'm searching inside a UIWebView using countOfMatches = [webView stringByEvaluatingJavaScriptFromString:@"some js to search a page which returns the number of matches"];I think I could just to a conversion from the JS returned string into an int, right?
Chad Edge
I haven't done a heck of a lot with JavaScript inside iPhone apps, and nearly all of it has been one-way calls (telling the JS to write something to a UIWebView layer), so I'm not sure about the answer to that one personally.
Shaggy Frog
A: 

Your problem is occurring when you set countOfMatches to the string literal @"1" in updateButtonClicked:

String literals are autoreleased, which means that when this pass of the run loop is complete, it will receive the -release message.

The run loop completes when your top-most method is finished, which means that your updateButtonClicked: method will work fine, but when it's complete, countOfMatches will be pointing to garbage memory.

If you push your read button after the update button, your application will try to find an object in the memory that countOfMatches is pointing to and will find garbage instead. This is why you're application is crashing.

Two possible solutions exists:

  1. Access the variable through the property, which will take care of memory management for you:

self.countOfMatches = @"1";

  1. Access the variable directly and retain the string literal:

countOfMatches = [@"1" retain];

Martin Gordon
Really? I thought literals were actually immune from retain/release (to prevent problems when you have pooled strings and inadvertently do [@"1" release].
David Dunham
A: 

The property has a copy characteristic, so it's making a copy of whatever's assigned, which in turn has the characteristic of retain (the copy it makes has a retain count of 1), so it doesn't matter if it's a string-literal or not.

But here's a slightly off-topic issue: if you're going to be mutating the value at all, why not store it as an NSInteger or NSUInteger?

If you're getting an NSString from somewhere else, just use the NSString method -integerValue and store the number.

And then any time you needed countOfMatches as a string, use NSNumber to get the value into a string form on the fly. This will also have the added benefit of gaining you localized string values. Assuming that self.countOfMatches is now an NSInteger:

NSString* countOfMatchesStr = [[NSNumber numberWithInteger:self.countOfMatches] descriptionWithLocale:[NSLocale currentLocale]];

Any good Cocoa or Cocoa Touch application should be internationalizable from the start, whether or not you intend to make it available in languages other than the initial language you're writing it in.

That means doing the following, at minimum:

  • whenever you create a new Xcode project, select each xib file, get info on it, click the General tab and at the bottom of the pane, click the button Make File Localizable. It's better to do this before you import the project into version control because it moves the xib file from the project level into a subfolder called XXXX.lproj, where XXXX is the language code of the language you're working in.

  • Never put user-facing string-literals into your source code! Create a .strings file: create a new file. When the template window comes up, in the Mac section choose Resource, then .strings file. Name the file Localizable.strings and save it to your project. Then be sure to follow the steps in the last bullet point to actually make it localizable.

  • for all those string-literals that you DID put into your source code, wrap them in calls to NSLocalizedString(@"My Button Label", @"an optional comment, use empty string if no comment"); I'll leave it to you to read more about how to format the contents of .strings files.

God of Biscuits