Why do I have to retain/copy this NSString? - ios

I recently rewrote some code in one of my classes, which gave me an error with an NSString. Here is what I have now:
My class header:
#interface MyViewController : UITableViewController {
NSString *myString;
}
#property (nonatomic, retain) NSString *myString; // Or copy instead of retain.
#end
And implemented some methods:
- (void)viewDidLoad {
myString = #"This is";
if (something) {
myString = [NSString stringWithFormat:#"%# a string.", myString]; // *1
}
[myString retain]; // <-- Why do I have to retain/copy here?
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 0) {
/* Some code for creating a UITextView called myTextView */
// ..and then setting the text property:
myTextView.text = myString; // <-- Crashes here if I don't retain/copy in viewDidLoad.
}
}
After some debugging I figured I had to retain/copy the NSString.
Why do I have to retain/copy the NSString in viewDidLoad if I want to use it later on?
Also, I noticed that if I remove the line marked *1, I don't have to retain/copy.

If you use the synthesized setter method to assign the variable, it will automatically retain the NSString for you:
if (something) {
self.myString = [NSString stringWithFormat:#"%# a string.", myString]; // *1
}

One way for you to better understand this would be to prefix your private attribute with an underscore _myString and keep your property as myString - this would make it easy to work out which is being used - the attribute or the property.
As you only seem to be using the value in your class I would question why you have a property at all as I think this is the source of the confusion.
If you have put the prefix in place then you will always know that _myString will need to have a retain when it is assigned to and myString will retain automatically.
The line at *1 is replacing the value of myString with a new string and for some reason this value is autoreleased before your code in the 2nd method is processed. I can't remember the exact reason, but I think this doesn't happen for the first assignment because you create a string literal with #"This is" which is not autoreleased.
I hope this helps.

You can use the retained property accessors which will release/retain automatically
- (void)viewDidLoad {
self.myString = #"This is";
if (something) {
self.myString = [NSString stringWithFormat:#"%# a string.", myString]; // *1
}
// no need to retain
}
The reason it does not crash when you remove the line marked *1 is because myString is still pointing at #"This is", which is a litteral, which lives in a corner of memory for the entire duration of the program and is never destroyed, hence that memory location remains valid.

Related

Obj-C - best way to check if NSString passed to method equals previous invocation?

I'm new to Objective-C and I'm trying to determine if the NSString being passed to my method is the same as the NSString previously passed to the same method.
Is there a simple way to do this?
If you're looking to do this per instance of your class (and not globally):
#interface MyClass : NSObject
- (void)myMethod:(NSString *)value;
#end
#interface MyClass ()
#property (copy, nonatomic) NSString *value;
#end
#implementation MyClass
- (void)myMethod:(NSString *)value
{
if ([self.value isEqualToString:value])
{
// Values are the same!
}
else
{
self.value = value;
}
}
#end
Store the string as an instance variable of the class, each time the method is called, compare the instances and replace with the new parameter.
Just to elaborate on what #Wain said:
Add Instance Variable:
#interface ViewController ()
{
NSString * lastString;
}
Then in your method:
- (void) methodWithString:(NSString *)string {
if ([string isEqualToString:lastString]) {
NSLog(#"Same String");
}
else {
NSLog(#"New String");
lastString = string;
}
}
A variation on the theme most answers are following: If you wish to do this per instance of your class then you can use an instance variable. However as this is something which is really specific to your method, you don't want to declare this variable in any interface and recent compilers help you with this by enabling instance variable declaration in the implementation. For example:
#implementation MyClass
{
NSString *methodWithString_lastArgument_; // make it use clear
}
- (void) methodWithString:(NSString *)string
{
if ([string isEqualToString:methodWithString_lastArgument_])
{
// same argument as last time
...
}
else
{
// different argument
isEqualToString:methodWithString_lastArgument_ = string.copy; // save for next time
...
}
}
(The above assumes ARC.)
The string.copy is shorthand for [string copy] and is there to handle mutable strings - if the method is passed an NSMutableString then this will copy its value as an (immutable) NSString. This protects the method from the mutable string changing value between calls.
If you want to do this on a global basis, rather than per instance, you can declare a static variable within your method and thus completely hide it from outside the method:
- (void) methodWithString:(NSString *)string
{
static NSString *lastArgument = nil; // declare and init private variable
if ([string isEqualToString:lastArgument])
{
// same argument as last time
...
}
else
{
// different argument
isEqualToString:lastArgument = string.copy; // save for next time
...
}
}
HTH

Is there anyway I can compare a String (which is a word) and a letter which is input by the user and receive an output as a BOOL

I'm new to IOS dev and am making simple programs this one is a hangman game.
I wanted to pick a random string from a plist file (completed).
I now want to compare the user input text (from a text field) and compare it to the string we have randomly picked from our plist.
Here is my code for MainViewController.m as it is a utility. Only the MainView is being used currently.
#import "MainViewController.h"
#import "WordListLoad.h"
#interface MainViewController ()
#end
#implementation MainViewController
#synthesize textField=_textField;
#synthesize button=_button;
#synthesize correct=_correct;
#synthesize UsedLetters=_UsedLetters;
#synthesize newgame=_newgame;
- (IBAction)newg:(id)sender
{
[self start];
}
- (void)start
{
NSMutableArray *swords = [[NSMutableArray alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"swords" ofType:#"plist"]];
NSLog(#"%#", swords);
NSInteger randomIndex = arc4random() % [swords count];
NSString *randomString = [swords objectAtIndex:randomIndex];
NSLog(#"%#", randomString);
}
This is where i would like to implement the checking
I have tried characterAtIndex and I can't seem to get it to work for hard coded placed in the string let along using a for statement to systematic check the string.
- (void)check: (NSString *) randomString;
{
//NSLogs to check if the values are being sent
NSLog(#"2 %#", self.textField.text);
}
- (IBAction)go:(id)sender
{
[self.textField resignFirstResponder];
NSLog(#"1 %#", self.textField.text);
[self check:(NSString *) self.textField];
_textField.text = nil;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self start];
}
To compare 2 strings: [string1 equalsToString:string2]. This will return true if string1 is equal to string2. To get the string contained in a UITextfield: textfield.text.
Given that it's a hangman game, I assume you are trying to see if a single letter is contained by a given string - so equalsToString: wouldn't be what you want.
Instead, probably better to use rangeOfString:options:
if ([randomString rangeOfString:self.textfield.text options:NSCaseInsensitiveSearch].location != NSNotFound){
// Do stuff for when the letter was found
}
else {
// Do stuff for when the letter wasn't found
}
Also, as was pointed out by Patrick Goley, you need to make sure you're using the textfield.text value to get the string from it. Same with storing the initial word you'll be using as the hidden word.
There are also a couple of other minor code issues (semicolon in the function header, for example) that you'll need to clean up to have a functioning app.
Edit: Made the range of string call actually use the textfield's text, and do so case-insensitive (to prevent false returns when a user puts a capital letter when the word is lower case, or vice-versa). Also included link to documentation of NSString's rangeOfString:options:
For your check method you are sending the UITextfield itself, instead of its text string. Instead try:
[self check: self.textfield.text];
You'll also need to create an NSString property to save your random string from the plist, so you can later access to compare to the textfield string like so:
declare in the interface of the class:
#property (nonatomic,strong) NSString* randomString;
in the start method:
self.randomString = [swords objectAtIndex:randomIndex];
in the check method:
return [self.randomString isEqualToString:randomString];

passing values string values to an object and recieving a string in return in ios

Hi a very simple app it takes in 2 arguments via 2 text boxes, and then totals them and displays them in a label called result. The idea is to have it handled via an object called brain, for which in the later part i have given the code. problem is foo is zero and when you click the button the result goes to nothing.
The plan is to use this to build a better model view architecture for a bigger app i have completed.
#import "calbrain.h"
#import "ImmyViewController.h"
#interface ImmyViewController ()
#property (nonatomic, strong) calbrain *brain;
#end
#implementation ImmyViewController
#synthesize brain;
#synthesize num1;
#synthesize num2;
#synthesize result;
-(calbrain *) setBrain
{
if (!brain) {
brain = [[calbrain alloc] init];
}
return brain;
}
- (IBAction)kickit:(UIButton *)sender {
NSString *number1 = self.num1.text;
NSString *number2 = self.num2.text;
NSString *foo;
foo = [brain calculating:number1 anddouble:number2];
self.result.text = foo;
// self.result.text = [brain calculating:self.num1.text anddouble:self.num2.text];
}
#end
#implementation calbrain
-(NSString *) calculating:(NSString *)number1 anddouble:(NSString *)number2
{
double numb1 = [number1 doubleValue];
double numb2 = [number2 doubleValue];
double newresult = (numb1 + numb2);
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
NSString *numberAsString = [numberFormatter stringFromNumber:[NSNumber n numberWithFloat:newresult]];
return numberAsString;}
Check your brain using NSLog in the (IBAction)kickit:(UIButton *)sender function. I guess you didn't initialise brain. If this is not the case, you need to provide more code.
i did just that, i came to the conclusion the setter for brain isnt working properly
i put the alloc init line of code before i needed to alloc init the brain, and it works fine, i stubbed out the setter,
i will go back and see why it wasnt overriding the setter made by properties, but interesting stuff none the less. it means i can change my actual larger app to have a cleaner more organised architecture.
thanks for your time.
Try initializing your brain object in viewDidLoad() using your setter method. You have to call setter method to get your brain object initialized.
Something like this
viewDidLoad()
{
brain = [self setBrain];
//You can also do this
brain = [[calbrain alloc] init];
}
and use that brain object in your (IBAction)kickit: method.
Hope this helps.

Overriding "text" in UILabel does not work in iOS 6

My class "TypographicNumberLabel" is a subclass of UILabel. This class overrides the "text" setters and getters of UILabel with the purpose to produce nicely rendered numbers in a table. For instance, it can add some extra white space for right alignment, unary plus signs, append units, etc.
My problem is that this class has worked perfectly fine up to iOS 5.1, but in iOS 6, it has stopped working: It is now rendering exactly as the standard UILabel (but when its properties are accessed from code, they are still giving correct results).
Since this class is used in a huge mass of legacy code, I would really like to repair my original code instead of rewriting it using completely new methods. So, please focus your answers on explaining how to override "-text" and "-setText:" for UILabel in iOS 6.
This is (a simplified version of) my code:
#interface TypographicNumberLabel : UILabel {
NSString *numberText;
}
// PROPERTIES
// "text" will be used to set and retrieve the number string in its original version.
// integerValue, doubleValue, etc. will work as expected on the string.
// The property "text" is declared in UILabel, but overridden here!
// "typographicText" will be used to retrieve the string exactly as it is rendered in the view.
// integerValue, doubleValue, etc. WILL NOT WORK on this string.
#property (nonatomic, readonly) NSString* typographicText;
#end
#implementation TypographicNumberLabel
- (void) renderTypographicText
{
NSString *renderedString = nil;
if (numberText)
{
// Simplified example!
// (Actual code is much longer.)
NSString *fillCharacter = #"\u2007"; // = "Figure space" character
renderedString = [fillCharacter stringByAppendingString: numberText];
}
// Save the typographic version of the string in the "text" property of the superclass (UILabel)
// (Can be retreived by the user through the "typographicText" property.)
super.text = renderedString;
}
#pragma mark - Overridden UILabel accessor methods
- (NSString *) text
{
return numberText;
}
- (void) setText:(NSString *) newText
{
if (numberText != newText)
{
NSString *oldText = numberText;
numberText = [newText copy];
[oldText release];
}
[self renderTypographicText];
}
#pragma mark - TypographicNumberLabel accessor methods
- (NSString *) typographicText
{
return super.text;
}
#end
Example of use (aLabel is loaded from .xib file):
#property(nonatomic, retain) IBOutlet TypographicNumberLabel *aLabel;
self.aLabel.text = #"12";
int interpretedNumber = [self.aLabel.text intValue];
This type of code works perfectly fine in both iOS 5.1 and in iOS 6, but the rendering on screen is wrong in iOS 6! There, TypographicNumberLabel works just like a UILabel. The "figure space" character will not be added.
The issue is at
- (NSString *) text
{
return numberText;
}
You can see the method ([self text]) is called internally, so it's better to return the text you want to be shown, otherwise you can easily ruin internal control logic:
- (NSString *) text
{
return [super text];
}
After having submitted my question, I found a solution myself. Perhaps not the definite solution, but at least a useful workaround. Apparently, the rendering logic of UILabel has changed when attributedText was introduced in iOS 6. I found that setting the attributedText property instead of super.text will work.
To be more specific:
The following line in renderTypographicText
super.text = renderedString;
should be replaced with
if (renderedString && [UILabel instancesRespondToSelector: #selector(setAttributedText:)])
super.attributedText = [[[NSAttributedString alloc] initWithString: renderedString] autorelease];
else
super.text = renderedString;
then the rendering works fine again!
This is a bit "hackish", I admit, but it saved me from rewriting a huge amount of legacy code.

UITableView titleForHeaderInSection does not returns correct stringWithFormat

I encounter a strange problem when attempting to return a composite string in the tableView's titleForHeaderIn section.
If I NSLog the string, it seems to be good, but when i return it, it crashes !
Here's my code :
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
NSString *title = NSLocalizedString(#"favorites",#"");
NSLog(#"%#", title); // this prints the correct title ("Items" for example...)
int number = (*_tabsections_especes)[0][0];
NSLog(#"%d", number); // this prints the correct number ( "5", for example...)
NSLog(#"%#", [NSString stringWithFormat:#"%# : %d", title, number ] );
// this prints the correct concatenated string ("Items : 5", for example);
return [NSString stringWithFormat:#"%# : %d)", title, number ];
// --> this either crashes the app, or returns anything in the title,
// for example the title of a resource image or another pointer...
}
If I replace "(*_tabsections_especes)[0][0]" by "5", for example, the problem persists.
So, it seems that the issue is about using NSLocalizedString in the stringWithFormat method, then returning it.
What am I doing wrong ?
Use this before
NSString *result = [NSString stringWithFormat:#"%# : %d)", title, number ];
return result;
or use this
NSString *result = [[NSString alloc]initStringWithFormat:#"%# : %d)", title, number ]]autorelease];
return result;
I tested your method and it works. Look for your bug elsewhere.
I have found where the problem was.
Actually it was not in tableView:titleForHeaderInSection, but rather in tableView:viewForHeaderInSection.
In fact, it is because I use a subclass of UIView for the viewForHeaderInSection.
In this subclass, I have an ivar named "title".
In the init method of this subclass, I set this ivar like this :
title = myTitle; // (myTitle is an argument of the custom init method)
And, just later, I use this title like this in the drawRect method :
[title drawAtPoint:CGPointMake(8, 9) withFont:[UIFont systemFontOfSize:19]];
This works fine if I pass a static string like #"example string" from titleForHeaderInSection, and via viewForHEaderInSection.
But not at all if I pass an autorelease object like stringWithFormat.
So, the solution is simply to retain my ivar in the subclass like this:
title = [myTitle retain];
and to release it in the dealloc method of my subclass:
[title dealloc];
Like this, it works and it doesn't crash. I hope this helps and that the explanations are clear.

Resources