Objective C instance variable is released despite being retained - objective-c

I have a marshmallow class which has (among other things) a CCSprite object as an instance variable.
here is the init method:
-(id) init
{
if((self = [super init]))
{
model = [[CCSprite spriteWithFile:#"marshmallow.png"] retain];
maxSpeed = 5; //160px per second (maxspeed * PTM_Ratio = px/second max)
gravity = 9.81; // in meters/sec^2
health = 3;
}
return self;
}
the variable is declared in another file as a global variable with the line:
Marshmallow *mainChar;
Later in the file, it is set (initiated/alloc'd) with this line:
mainChar = [[mainChar alloc] init];
while writing the previous line, xcode gave me a warning that Marshmallow might not respond to alloc. (I don't think that's related. just mentioning anything that seems wrong)
my problem is that the following line of code returns nil:
[mainChar getModel];
why does it return nil instead of the instance variable?
here is the getModel function:
-(CCSprite *)getModel
{
return model;
}

mainChar = [[mainChar alloc] init];
Shouldn't be
mainChar = [[Marshmallow alloc] init];
?
The message says an object from that class might not respond to it, not the class itself.

Your problem is in the initialization of your mainChar variable. The line you're looking for is this:
mainChar = [[mainChar alloc] init];
The warning you got is telling you that instances of type Marshmallow will not respond to the -alloc message. That is your problem: you want to call the +alloc class method instead, like so:
mainChar = [[Marshmallow alloc] init];

I think you want to do
mainChar = [[MarshMallow alloc] init];
instead of
mainChar = [[mainChar alloc] init];
The error message you got is very important.

Related

cannot init a class object : objective-C

I'm trying to make a simple subclass of CCNode, but I can't create the object.
It gives me the error "* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* +[ContentPane<0x206898> init]: cannot init a class object.'"
Here is my subclass of CCNode:
#import "ContentPane.h"
#implementation ContentPane{
int content[8][4];
CCSprite *_rockPath1;
CCSprite *_rockPath2;
}
- (id)init {
self = [super init];
if (self) {
CCLOG(#"ContentPane created");
}
return self;
}
#end
Here is where I try to initiate it:
- (void)didLoadFromCCB {
// tell this scene to accept touches
self.userInteractionEnabled = TRUE;
_counter = 0;
ContentPane *pane = [ContentPane init];
}
Couple things,
In Obj-c when you want to initialize an Object, you need to allocate space for it.
That is done using the alloc keyword.
so your ContentPane *pane = [ContentPane init];
turns into ContentPane *pane = [[ContentPane alloc] init];
Also, whatever tutorial you are following, Stop... the way you have declared your variables we call them (iVars) is a very old fashioned way of doing things, they should really be properties. and Boolean values are represented by YES and NO not TRUE and FALSE
If you are here just like me wondering why you code is crashing.
[NSArray init];
should be :
[[NSArray alloc] init];
or
[NSArray array];
You crash could caused by any other class here NSArray here is for reference only.

Objective C Constructer without init

When I'm creating custom classes, I'd like to be able to skip the alloc init part of the code once I go to construct an instance of the class. Similar to how it's done with:
NSString * ex = [NSString stringWithFormat...];
Basically I already have the class set up with a custom initializer method to set up my basic variables. However, when I'm on the front end and actually making these critters I have to say:
[[Monster alloc] initWithAttack:50 andDefense:45];
and I'd rather be able to say
[Monster monsterWithAttack:50 andDefense:45];
I know it's a simple stupid thing to just get rid of the alloc part but it makes the code more readable so I'd prefer to do it that way. I originally tried just changing my method from
-(id)initWithAttack:(int) a andDefense:(int) d
to
-(id)monsterWithAttack:(int) a andDefense:(int) d
and then changing my self = [super init] to self = [[super alloc] init]; but that clearly doesn't work! Any ideas?
You have to make a class method
+(id)monsterWithAttack:(int) a andDefense:(int) d
in which you create, initialize, and return an instance (and don't forget your memory management):
+(id)monsterWithAttack:(int) a andDefense:(int) d {
// Drop the autorelease IF you're using ARC
return [[[Monster alloc] initWithAttack:a andDefense:d] autorelease];
}
What you want is a convenience constructor. It's a class method that returns a useable instance of a class and allocates memory for it at the same time.
-(id)initWithAttack:(int)a andDefense:(int)d;
+(id)monsterWithAttack:(int)a andDefense:(int)d;
+(id)monsterWithAttack:(int)a andDefense:(int)d {
//-autorelease under MRC
return [[[self class] alloc] initWithAttack:a andDefense:d];
}
-(id)initWithAttack:(int)a andDefense:(int)d {
self = [super init];
if (self){
//custom initialization
}
return self;
}
You should use a class factory method in the header of monster class.
+(id)monsterWithAttack:(int) attackValue andDefense:(int) defenseValue
in the implementetation of monster class
+(id)monsterWithAttack:(int) attackValue andDefense:(int) defenseValue {
return [[[[self class] alloc] initWithAttack:attackValue andDefense:defenseValue] autorelease];
}
The use of [self class] guarantees the correct dispatch during subclassing. If you are using ARC you can avoid the autorelease method
Class methods of this type use autorelease.
So for instance, you might say:
+ (id)
monsterWithAttack:(int) a
defense:(int) d
{
return [[Monster alloc] initWithAttack:a defense:d]
autorelease];
}

Do I need to allocate NSStrings passed in as parameters to a custom initialization method?

Please consider the following two initialization methods.
The first method simply passes the value of the parameters to their respective NSString properties, but the second allocates the properties and then initializes them using the initWithString: method. Is the allocation in the latter example necessary?
Thanks in advance.
-(id)initWithTitle:(NSString *)theTitle muscleGroup:(NSString *)theMuscleGroup equipment:(NSString *)theEquipment {
if((self = [super init])){
title = theTitle;
muscleGroup = theMuscleGroup;
equipment = theEquipment;
}
return self;
}
-(id)initWithTitle2:(NSString *)theTitle muscleGroup:(NSString *)theMuscleGroup equipment:(NSString *)theEquipment {
if((self = [super init])){
title = [[NSString alloc] initWithString:theTitle];
muscleGroup = [[NSString alloc] initWithString:theMuscleGroup];
equipment = [[NSString alloc] initWithString:theEquipment];
}
return self;
}
The first example is not safe because you are not taking ownership of the strings, so your program will get all crashy if they are later released elsewhere. The second example fixes that problem and will work perfectly well, but is more concisely written thusly:
-(id)initWithTitle2:(NSString *)theTitle muscleGroup:(NSString *)theMuscleGroup equipment:(NSString *)theEquipment {
if((self = [super init])){
title = [theTitle copy];
muscleGroup = [theMuscleGroup copy];
equipment = [theEquipment copy];
}
return self;
}
NSString gives you a copy constructor (-initWithString:), which enables you to do what you are doing in #2, but not all classes do. copy requires the class to implement the NSCopying protocol, but is more conformant with the way a Cocoa API developer would expect to be able to copy objects.
Parameter objects don't get copied when you pass them in. So your first example may not always work, it depends how you've initialized your strings.
The following is safer (although remember to release the objects in your dealloc method):
-(id)initWithTitle:(NSString *)theTitle muscleGroup:(NSString *)theMuscleGroup equipment:(NSString *)theEquipment {
if((self = [super init])){
title = [theTitle retain];
muscleGroup = [theMuscleGroup retain];
equipment = [theEquipment retain];
}
return self;
}
Example 1 will assign the pointers. It makes no attempt to retain the objects and is vulnerable to something outside changing the content of the objects.
It could work depending on how the arguments are constructed in the first place;
Example 2 will copy the string objects and retain them. As long as you release in the dealloc then its the preferable method.
FWIW
title = [theTitle copy];
or
title = [[NSString stringWithString:theTitle] retain];
are equally good in Ex 2

What would be a proper way to initialize a instance variable in Objective C without leaking memory?

I have a class like this:
#interface MyCollection : NSObject {
NSMutableDictionary *data;
}
and in the implementation of it, I have a method to init it like this:
- (id) init {
if(self = [super init])
{
self.data = [[NSMutableDictionary alloc] init];
}
return self;
}
Now when I create a object of this class in my code like this:
MyCollection *c = [[MyCollection alloc] init];
... at which point the Leaks utility shows that I have a memory leak in the init function on the very line where I try to set up the instance variable. I am totally new to Objective C & Iphone and I can't just get what is going wrong here. I have read through the Memory Management Guide and all, but I think I'm missing something pretty serious here.
Any help would be greatly appreciated. Thanks for your time already.
you are using self.data =. So there is most likely a property. And it most likely is a property which either copies or retains your object if you use it.
By calling
self.data = [[NSMutableDictionary alloc] init];
The retain count of the NSMutableDictionary increases because of the alloc, and if the property of data has a retain or copy statement you get another increase in retain count.
you could write data = [[NSMutableDictionary alloc] init]; or self.data = [NSMutableDictionary dictionary]. This would increase the retain count only one time.
And don't forget to release the object in dealloc.
You have to release the object in your dealloc method. That's why it's showing up as a leak.
to add to what fluchtpunkt mentioned you could try this instead:
- (id) init {
if(self = [super init])
{
self.data = [NSMutableDictionary dictionaryWithCapacity:0];
}
return self;
}
and in the dealloc
-(void)dealloc
{
self.data = nil;
}
I see weird situations with the Leaks utility as sometimes it reports old leaks, sometimes it doesn't report new ones, and so on. Also, from what I could collect with all your answers and opinion elsewhere on the web, people are divided on whether one should set a pointer to nil or not.
As of now, I have solved the situation with the following approach.
- (id) init {
if(self = [super init])
{
data = [[[NSMutableDictionary alloc] initWithCapacity:0];
}
return self;
}
-(void)dealloc
{
[data release];
}
Thanks everyone for contributing.
Are you creating the instance of "MyCollection" in the interface section?
If it has method scope try to release it in the same method after you are done with it.

Typecasting a TableViewController Class

I'm having a problem with either typecasting or object scope. I'm getting an uncaught exception:
// Create the object here so that it's scope is outside the `if` statement, right?
searchTableViewController *newViewController;
if (rowSelected) {
// Typecast the object to a searchTableViewController
(searchTableViewController *)newViewController ;
// Initialize and Allocate
newViewController = [[searchTableViewController alloc] initWithSearchBar:NO grouped:NO];
}else{
// Typecast the global object to a personViewController
(personViewController *)newViewController;
// Initialize and Allocate
newViewController = [[personViewController alloc] init];
}
// Act on the object: create a data model object and store it as a property, etc.
newViewController.myDataModel = [[dataModel alloc] initWithSelected:selectedField delegate:newViewController];
I have 2 similar ViewControllers searchTableViewController and personViewController. So I want to be able to instantiate either one or the other, using the same name, that way the rest of my code can act on the viewController using common properties and such.
This is causing a Terminating due to uncaught exception and regardless it seemed like the wrong way to do it anyway. I need help either in the typecasting department or I need help understanding how to declare the scope of these objects properly so I can work with them in and outside of the if statement.
Update
The easiest thing would be for me to write my code as follows. How can I declare, allocate and instantiate an object within an if statement and then have it accessible outside?:
if (rowSelected) {
searchTableViewController *newViewController = [[searchTableViewController alloc] initWithSearchBar:NO grouped:NO];
}else{
personViewController *newViewController = [[personViewController alloc] init];
}
// This will probably give an error since newViewController is not in the proper scope.
newViewController.myDataModel = [[dataModel alloc] initWithSelected:selectedField delegate:newViewController];
You want to declare your newViewController as the common subclass of the two objects you're possibly allocating. Probably UIViewController.
UIViewController *newViewController;
if (rowSelected) {
// Initialize and Allocate
newViewController = [[SearchTableViewController alloc] initWithSearchBar:NO grouped:NO];
}else{
// Initialize and Allocate
newViewController = [[PersonViewController alloc] init];
}
And the cast operation isn't doing anything when you use it in-place like that.
Edit - if both of those classes have common properties, like dataModel, then you can avoid warnings by creating a common base class that derives from UIViewController and which contains those properties. You'd then change the declaration of your view controller variable in the first line above to match the intermediate base class.
Later Edit — if you don't want to create an intermediate base class, you can do the following (newViewController still must be declared as a UIViewController):
if([newViewController respondsToSelector:#selector(setMyDataModel:)]) {
DataModel *dataModel = [[dataModel alloc] initWithSelected:selectedField delegate:newViewController];
[newViewController performSelector:#selector(setMyDataModel:) withObject:dataModel];
}
RE: Your Edit
id newViewController;
if (rowSelected) {
newViewController = [[searchTableViewController alloc] initWithSearchBar:NO grouped:NO];
}else{
newViewController = [[personViewController alloc] init];
}
// This will probably give an error since newViewController is not in the proper scope.
newViewController.myDataModel = [[dataModel alloc] initWithSelected:selectedField delegate:newViewController];