EXC_BAD_ACCESS when property set on custom class at initialization? - objective-c

I have a simple Answer class that looks like this:
#interface Answer : NSObject {
NSString *_text;
NSNumber *_votes;
}
#property(nonatomic, retain) NSString *text;
#property(nonatomic, retain) NSNumber *votes;
+(id)initFromAnswerData:(NSSet *)data;
-(id)initWithText:(NSString *)answer;
#end
The implementation looks like this:
#import "Answer.h"
#import "AnswerData.h"
#import "AppDelegate.h"
#implementation Answer
#synthesize text = _text;
#synthesize votes = _votes;
-(id)initWithText:(NSString *)answer {
if( (self=[super init])) {
_text = answer;
_votes = 0;
}
return self;
}
#end
If I create an array of Answers in a view controller using the initWithText: method I inevitably have EXC_BAD_ACCESS errors when I take an Answer in the array and try to find it's text value.
However if I initialize a new Answer, set the text value and then add it to the array I don't have this access issue.
So this causes problems down the line:
[arrayOfAnswers addObject:[[Answer alloc] initWithText:#"Hello"]];
But this doesn't:
Answer *newAnswer = [[Answer alloc] initWithText:nil];
newAnswer.text = #"Hello";
[arrayOfAnswers addObject:newAnswer];
Can anyone explain why?

You're using the attribute _text and _votes directly but not their setters.
So, you're not retaining the input parameter answer for the line
_text = answer;
You should either change to
_text = [answer retain];
or
self.text = answer;

Are you retaining the array that you put your Answers into? That would be my guess at what's wrong.

as you see , your
#property(nonatomic, retain) NSString *text; // this property is retain.
so the setter method of this should be- (void) setText:(NSString*)text{
[_text release];
_text = text;
[_text retain];
so when you call newAnswer.text = #"hello" ,it works, newAnswer holds the text.
but in your initWithText, there's no retain symbol, so sucks.

Related

Cannot access public variable of another class

I know this question is asked often, I've read so much on it but I still cant get it to work. Lets say I have two classes, FirstClass and SecondClass. FirstClass has a label and SecondClass wants to get the text of that label. Here is what I've done:
//FirstClass
#interface FirstClass : UIViewController
{
#public
UILabel *theLabel;
}
#property (strong, nonatomic) UILabel *theLabel;
#implementation FirstClass
#synthesize theLabel;
//SecondClass
#import "MainGameDisplay.h"
#interface SecondClass : UIViewController
{
MainGameDisplay *mainGame;
}
#property (strong, nonatomic) UILabel *theSecondLabel;
#implementation SecondClass
-(void) thisMethodIsCalled {
mainGame = [[FirstClass alloc] init];
self.theSecondLabel.text = mainGame.theLabel.text;
NSLog(#"%#",mainGame.theLabel.text); //Output is '(Null)'
}
theLabel.Text is not nil as it's being changed every second and is also displaying the label on the other controller which is running in the background whilst the SecondClass view is loaded. Could someone please point me to the write direction if I'm completely wrong, or show me some kind of example as to how this would be done. Thank you.
EDIT:
#Implementation FirstClass
#synthesize theLabel;
- (void)viewDidLoad {
[self superview];
[self startTickCount];
}
-(void) startTickCount {
timer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(timeChanger) userInfo:nil repeats:YES];
}
-(void) timeChanger {
theDay++;
NSLog(#"%#",self.theLabel.text);
if (theDay <= 9)
self.theLabel.text = [NSString stringWithFormat: #"0%i", theDay];
else
self.theLabel.text = [NSString stringWithFormat: #"%i", theDay];
if (theDay > 27)
[self monthChanger];
}
That's pretty much it. The NSLog outputs the day as expected.
I assume MainGameDisplay is your FirstClass. Then in order to update theSecondLabel.text in you SecondClass object you need to pass an object of FirstClass and not to instantiate it in method call.
I guess you need to do something like this (this is a very simple example)
Add a property to your SecondClass
#property (nonatomic, strong) FirstClass *firstClass;
After that:
1) create instance of FirstClass, let it have name firstClass.
2) create instance of SecondClass. SecondClass *secondClass = [[SecondClass alloc] init];
3) set property of Second class to instance of FirstClass
secondClass.firstClass = firstClass;
4) now you have a reference to actual object of FirstClass and can access its properties.
-(void) thisMethodIsCalled {
self.theSecondLabel.text = self.firstClasss.theLabel.text;
NSLog(#"%#",mainGame.theLabel.text);
}
I hope this will help.
if you didnt leave out LOTS of code,
-(void) thisMethodIsCalled {
mainGame = [[MainGameDisplay alloc] init];
self.theSecondLabel.text = mainGame.theLabel.text;
NSLog(#"%#",mainGame.theLabel.text); //Output is '(Null)'
}
will not work.. nobody can modify mainGame in between the alloc init and the getting of .text variable....
[#all I know this is not an answer, but the formatting of comments sucks. Ill edit or delete it as needed]
If this is your code exactly, you have two problems. First, Text is unnecessarily capitalized. And, secondly, TheLabel is unnecessarily capitalized.
Edited Code:
-(void) thisMethodIsCalled {
mainGame = [[MainGameDisplay alloc] init];
// 'text' shouldn't be capitalized
// 'theLabel' shouldn't be capitalized
self.theSecondLabel.text = mainGame.theLabel.text;
NSLog(#"%#",mainGame.theLabel.text);
}
The method name is text, not Text. The case matters, and using text with a lower T will cause errors.

objective c class property variables not populated

I have the following simple class definition:
//mycommon.h
#interface CurrentPath : NSObject
#property (nonatomic, strong) NSString* PathString;
#property (nonatomic, strong) NSMutableArray* PathArr;
- (void) addAddressToPath:(NSString*) address;
#end
//mycommon.m
#implementation CurrentPath : NSObject
#synthesize PathString;
#synthesize PathArr;
- (void) addAddressToPath:(NSString*) address{
NSLog(#"addAddressToPath...");
// Add to string
self.PathString = [self.PathString stringByAppendingString:address];
// Add to Arr
[self.PathArr addObject:address];
}
#end
In another class I do #import<mycommon.h> and declare the variable like this:
#interface myDetailViewController :
{
CurrentPath* currentPath;
}
- (void) mymethod;
#end
and in
#implementation myDetailViewController
- void mymethod{
self->currentPath = [[CurrentPath alloc] init];
NSString* stateSelected = #"simple";
[self->currentPath addAddressToPath:stateSelected];
}
#end
Problem is that the PathString and PathArr properties of self->currentPath are empty after this method call which I think should have "simple" in them. Please help!
You have to make sure that your NSString and NSMutableArray properties are initialized when your CurrentPath object is created. Otherwise, the call to stringByAppendingString will result in nil because it is sent to a nil object.
One feasible way would perhaps be
self.currentPath = [NSString string];
// or
self.currentPath = #"";
[self.currentPath addAddressToPath:#"simple"];
More elegant and robust would be to check for nil property in the addAddressToPath method.
if (!self.pathString) self.pathString = [NSString string];
if (!self.pathArr) self.pathArr = [NSMutableArray array];
Notice that am following the objective-c convention and use property names that start with lower case letters.

Can't get an objective-c object to respond to a message. What am I missing?

I'm trying to teach myself Objective-C and as an exercise, I'm trying to write an app with one button and one label. When I click on the button, I want to trigger a calculation then see the results in the label. The following code compiles and runs with no errors or warnings but as far as I can tell, the [object method] 'call' doesn't do anything. I've spent hours on this and just don't see what's wrong. Can anyone explain the problem? Thanks.
*** testMethodViewController.h ****
#import <UIKit/UIKit.h>
#import "testBrain.h"
#interface testMethodViewController : UIViewController
{
IBOutlet UILabel *display;
testBrain *model;
}
- (IBAction)cellPressed:(UIButton *)sender;
#end
*** testMethodViewController.m ****
#import "testMethodViewController.h"
#implementation testMethodViewController
- (testBrain *)model
{
if (!model) {model = [[testBrain alloc] init];}
return model;
}
- (IBAction)cellPressed:(UIButton *)sender
{
int x = [model check:3]; //This method call doesn't work. But gets no errors.
NSLog(#"Results from model: %i", x); //Says x = 0, but I expect 6
NSString *xAsString = [NSString stringWithFormat: #"testBrain: %i", x];
display.text = xAsString; //Label is updated and displays: testBrain: 0
} //I expect: testBrain: 6
#end
*** testBrain.h ****
#import <Foundation/Foundation.h>
#interface testBrain : NSObject {}
- (int) check:(int) anInteger;
#end
*** testBrain.m ****
#import "testBrain.h"
#implementation testBrain
- (int) check:(int) anInteger //3 passed as the parameter.
{
int r = anInteger + anInteger;
NSLog(#"inside check %i", r); //Debugging line: doesn't print.
return r;
}
#end
When this code runs:
int x = [model check:3];
model is nil. In Objective-C, messages sent to nil silently do nothing, and return 0. So, as you see, x is 0 and -check: is never called.
Apparently you were expecting this method to be called automatically:
- (testBrain *)model
{
if (!model) {model = [[testBrain alloc] init];}
return model;
}
However, that method will be called only if you do it yourself, by saying [self model] or self.model. So, this line would fix it:
int x = [[self model] check:3];
Try it and see.
Going a little further: It would be clearer to remove the model method entirely, and create the instance variable model when the UIViewController is created. That way, we can guarantee that model is valid anytime any code in the testMethodViewController class runs.
You would do that by overriding UIViewController's designated initializer:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Now you can initialize your instance variables
model = [[testBrain alloc] init];
}
return self;
}
With your model method, you are halfway towards Lazy Instantiation, however to properly achieve this, you must always acess the lazily instantiated object through its accessor method. You aren't doing this in your button action, so your messages are going to nil, which is silently ignored.
This is one of the reasons you often see instance variables in objective-c declared with a leading or trailing underscore. If you then typed model anywhere in the rest of your class, it would be a compiler error, forcing you to use the accessor. Typically this is implemented with properties and the synthesize statement:
In your interface:
#property (nonatomic, strong) TestBrain* model;
In your implementation:
#synthesize model = model_;
Your model method would be:
-(TestBrain*)model
{
if (!model_)
model_ = [[TestBrain alloc] init];
return model_;
}
You would then use self.model instead of model throughout the rest of the class.
If you are just starting out, the Stanford iOS course on iTunes U is an excellent resource, a lot of this sort of material is covered.
int x = [model check:3];
This line should be:
int x = [self.model check:3];
you are almost there. You need to use #property and #synthesize in order to complete this. The #synthesize directive will direct the compiler to create the setters and getters for a particular property. The #synthesize directive tells the compiler that variable is a property. Properties allow you to use the dot syntax. i.e. self.model which will automatically the call the getter or setter method, depending on the context.
In your testMethodViewController.h file change it to look like this:
#interface testMethodViewController : UIViewController
{
IBOutlet UILabel *display;
testBrain *model;
}
#property (nonatomic,retain) testBrain *model;
- (IBAction)cellPressed:(UIButton *)sender;
#end
then in the .m implementation you need to use #synthesize after the #implementation. Like this:
#implementation testMethodViewController
#synthesize model; // tells the compiler to synthesize the setter and getter for you
- (testBrain *)model
{
if (!model) {model = [[testBrain alloc] init];}
return model;
}
then in your cellPressed: method, you need to use self.model in order for the getter to be called:
- (IBAction)cellPressed:(UIButton *)sender
{
int x = [self.model check:3]; //This method call doesn't work. But gets no errors.
NSLog(#"Results from model: %i", x); //Says x = 0, but I expect 6
NSString *xAsString = [NSString stringWithFormat: #"testBrain: %i", x];
display.text = xAsString; //Label is updated and displays: testBrain: 0
}
Hope this helps.
I dont see anywhere in the testMethodViewController.h file
IBOutlet UIButton *button;
Also check if u have properly connected all IBOutlet, IBAction & delegate, datasource.

Copy or retain NSString parameter?

I'm developing an iOs 4 app with latest SDK and XCode 4.2
I have a question about NSString parameters. This is my class definition:
#import <Foundation/Foundation.h>
#interface BlogEntry : NSObject
{
NSString* title;
NSString* text;
NSDate* date;
NSString* photo;
}
- (id)initWithTitle:(NSString*)titulo text:(NSString*)texto date:(NSDate*)fecha photo:(NSString*)foto;
#end
And implementation:
#import "BlogEntry.h"
#implementation BlogEntry
- (id)initWithTitle:(NSString*)titulo text:(NSString*)texto date:(NSDate*)fecha photo:(NSString*)foto
{
if (self = [super init])
{
title = titulo;
text = texto;
date = fecha;
photo = foto;
}
return self;
}
#end
May I need to retain initWithTitle parameters? Or, may I have to copy them?
If ARC, no. If non-ARC, yes.
For the NSString ivars, usually copy. For the NSDate ivar, retain. The reason for copy with NSString is in case an NSMutableString is passed in your init method. Copying the parameter prevents it from being mutated by your class. Thus, it ensures encapsulation.

addObject in NSMutableArray doesn't work

I'm a beginner to XCode.
Below is my code. I want to add an object to a mutablearray. From the debugger window I can see there is one object added to the array "words". I can also see the property "flag" of that object is "NO". The problem is another property "str" is shown as "out of scope".
Can anyone help me with this issue? Thanks a loooooot! Stucked on this one for the whole afternoon.
NSMutableArray * words=[[NSMutableArray alloc] initWithCapacity:numberOfWords];
Word *w=[[Word alloc] init];
[w setStr:#"abc" flag:NO];
[words addObject: w];
[w release];
--
#interface Word : NSObject{
NSString *str;
BOOL flag;
}
-(void) setStr: (NSString *) s flag:(BOOL) b
{
self.str=s;
flag=b;
}
Do you have a property declaration for your string? Are you retaining the string you are setting?
Still AFAIK 'out of scope' does not necessarily mean it was not set or that nothing has been set. Try an NSLog of the value or something. You might find that there is nothing wrong.
Have a look at this question that talks about scope in GDB:
Objective-C: instance variables out of scope in debugger
Your problem is that the string #"abc" is a temporary object who's scope only exists during the [w setStr:#"abc" flag:NO] method call. You should be able to resolve this problem by making str a #property of Word:
#interface Word : NSObject{
NSString *str;
BOOL flag;
}
#property (retain) NSString* str;
#end
And in your implementation file
#implementation Word
#synthesize str;
-(void) setStr: (NSString *) s flag:(BOOL) b
{
self.str=s;
flag=b;
}
#end