In almost all of the books I read and examples I go through I see pointers initialized this way. Say that I have a class variable NSString *myString that I want to initialize. I will almost always see that done this way:
-(id)init {
if (self = [super init]) {
NSString *tempString = [[NSString alloc] init];
self.myString = tempString;
[tempString release];
}
return self;
}
Why can't I just do the following?
-(id)init {
if (self = [super init]) {
self.myString = [[NSString alloc] init];
}
return self;
}
I don't see why the extra tempString is ever needed in the first place, but I could be missing something here with memory management. Is the way I want to do things acceptable or will it cause some kind of leak? I have read the Memory Management Guide on developer.apple.com and unless I am just missing something, I don't see the difference.
If self.myString is a retained property, the second example has to be
-(id)init {
if (self = [super init]) {
self.myString = [[[NSString alloc] init] autorelease];
}
return self;
}
or it will leak. I can only assume this is the case and the first example simply wants to avoid using autorelease.
The second example is correct.
Assuming that myString is an ivar, the first example is actually wrong because it leaves myString with a dangling pointer (a pointer to a deallocated object). If it were self.myString that would be a different story.
Related
Note: This question has been updated with suggestions supplied in answers below in which to bring a fuller context to the present state of the problem.
You may view complete project files here: https://github.com/cxx6xxc/Skeleton/blob/master/README.md
Conditions
I create an NSArray in an object's init method.
I return the NSArray with it's get method.
Problem
Upon arrival, the NSArray is null.
Creating instance
Attempt 1:
This is my original implementation.
- (id)init:
{
labels = [NSArray arrayWithObjects:#"Red", #"Green", #"Blue", nil];
return self;
}
Attempt 2:
Ismael suggested I wrap it with a sub-classing protocol.
neo suggested I retain the NSArray.
- (id)init:
{
self = [super init];
if (self)
{
labels = [NSArray arrayWithObjects:#"Red", #"Green", #"Blue", nil];
[labels retain];
}
return self;
}
Attempt 3:
Anoop Vaidya suggested I force ownership with alloc and NSMutableArray:
- (id)init:
{
self = [super init];
if (self)
{
labels = [[NSMutableArray alloc] initWithObjects:#"Red", #"Green", #"Blue", nil];
}
return self;
}
But, when I return the object, despite the different init suggestions cited above...
Returning the object
- (NSArray *)getLabels
{
return labels;
}
...with NSMutableArray...
- (NSMutableArray *)getLabels
{
return labels;
}
... the NSArray getter returns a null object.
Calling the method
int main(void)
{
id view;
view = [ZZView alloc];
id model;
model = [ZZModel alloc];
id controller;
controller = [[ZZController alloc] init: model: view];
labels = [[controller getModel] getLabels];
if(labels)
NSLog(#"allocated");
else
NSLog(#"not alloced");
[view dealloc];
[model dealloc];
[controller dealloc];
return EXIT_SUCCESS;
}
Question
What am I not doing, missing or what am I doing wrong that causes the null return value?
init methods need to call some [super init], so you will need to do something like this:
- (id)init
{
self = [super init];
if (self) {
labels = [NSArray arrayWithObjects:#"Red", #"Green", #"Blue", nil];
}
return self;
}
Edit: looking at your git repo, I found
controller = [[ZZController alloc] init: model: view];
I'm not entirely sure how the compiler interprets the empty arguments, but my guess is that it reads them as nil, and therefore your ZZController doesn't have model
Also, you have some messy argument order, the first argument (with text init:) is your model, and your second argument (with text model:) is your view
(this according to your - (id)init: (ZZModel*)Model: (ZZView*)View
In order to make it work quickly, you should do
controller = [[ZZController alloc] init:model model:view];
I'm gonna take a (short) leap here and guess you are new to iOS development, so I'll recommend that you read about objc programming, how to write functions, how to send multiple parameters, so on and so forth, and after that, do some refactoring
Cheers!
I suggest you put a breakpoint in both your init and getLabels methods, and check the value of the instance variable that stores the array: you'll see which method does not behave as expected.
Assume you are not using ARC nor synthesising variable labels, you need to retain the array,
- (id)init:
{
labels = [NSArray arrayWithObjects:#"Red", #"Green", #"Blue", nil];
[labels retain];
return self;
}
Also, you need to release it when not using the array to prevent memory leakage.
You can do it in this way, hoping in .h you have NSMutableArray *labels; :
- (id)init{
if (self = [super init]) {
labels = [[NSMutableArray alloc] initWithObjects:#"Red", #"Green", #"Blue", nil];
}
return self;
}
The init method to model was never called, it is only allocated. Therefore, NSArrray labels doesn't exist, because it is created in the init method.
Although experienced with OOP, I am an absolute newbie with Objective-C. I have the following code:
// header files have been imported before this statement...
CCSprite *treeObstacle;
NSMutableArray *treeObstacles;
#implementation HelloWorldLayer {
}
-(id) init
{
// create and initialize our seeker sprite, and add it to this layer
treeObstacles = [NSMutableArray arrayWithObjects: nil];
for (int i=0; i<5; i++) {
treeObstacle = [CCSprite spriteWithFile: #"Icon.png"];
treeObstacle.position = ccp( 450-i*20, 100+i*20 );
[self addChild:treeObstacle];
[treeObstacles addObject: treeObstacle];
}
NSLog (#"Number of elements in array = %i", [treeObstacles count]);
return self;
}
- (void) mymethod:(int)i {
NSLog (#"Number of elements in array = %i", [treeObstacles count]);
}
#end
The first NSLog() statement returns "Number of elements in array = 5". The problem is that (although treeObstacles is a file-scope variable) when calling the method "mymethod", I'll get an EXC_BAD_ACCESS exception.
Can anybody please help me?
Thanks a lot
Christian
you created treeObstacles by
treeObstacles = [NSMutableArray arrayWithObjects: nil];
which will return an autoreleased object, and you didn't retain it so it will be released soon
you have to retain it by calling retain on it
[treeObstacles retain];
of simple create it by
treeObstacles = [[NSMutableArray alloc] init];
and you need to remember to release it when done like
- (void)dealloc {
[treeObstacles release];
[super dealloc];
}
you need to read more about management in Objective-C
https://developer.apple.com/library/mac/#documentation/General/Conceptual/DevPedia-CocoaCore/MemoryManagement.html
or use ARC so no need to worry retain/release anymore
http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html
another problem, you need to call [super init] in your init method
- (id)init {
self = [super init];
if (self) {
// your initialize code
}
}
otherwise your object won't initialize properly
Assume I have a interface like:
#interface it:NSObject
{
NSString* string;
}
#end
#implement it
-(id)init
{
if(self = [super init]){
self.string = [[NSString alloc]init];
}
}
-(void)dealloc
{
[self.string release];
[super release];
}
#end
If I use this class in another file and I call this:
it ait = [[it allow] init];
NSString* anotherString = [[NSString alloc] init];
ait.string = anotherString;
[anotherString release];
Will this cause the string alloced in init method of it a memory leak?
Since the string is not referenced and not autoreleased.
If I don't alloc a string in the init method, what will happen when I call ait.string right before assign anotherString to it?
I think you need
#property (nonatomic, retain) NSString *string;
in your interface and
#synthesize string;
in your implementation for self.string to work.
Then, when you do
self.string = [[NSString alloc] init];
in your init method the string will actually have a retain count of 2 because [[NSString alloc] init]will return a string with a retain count of 1, and using self.string = will retain the object again because the property is declared as 'retain'. This will result in a memory leak, but you can fix by having:
-(id)init
{
if(self = [super init]){
self.string = #"initString";
}
}
or similar.
Then, onto the actual question, if you do as above the string allocated in init wont leak when you reassign with self.string = because properties marked as 'retain' release their current object before retaining the new one.
If you don't assign a string to self.string in the init method it doesn't matter because self.string will just return nil, which you can then deal with in your program.
Will this cause the string alloced in
init method of it a memory leak? Since
the string is not referenced and not
autoreleased.
Yes, exactly. You seem to have got it.
If I don't alloc a string in the init
method, what will happen when I call
ait.string right before assign
anotherString to it?
Do you mean in the following case you are unsure what unknownObject would refer to?:-
It *ait = [[It allow] init];
NSString *unknownObject = ait.string;
This is nonsense. Did you forget to add the accessor methods?
Objective-c doesn't use 'dot syntax' to access instance variables, like, say, Java. If you have an instance of your class 'it' you can only access the 'string' variable from outside that instance by calling the accessor 'getter' method. This is not optional self.string is just a shortcut for the method call [self string] and this method doesn't exist in the code you showed.
Assuming the accessor method are defined elsewhere, the instance variable you called string (this is the worlds worst variable name) is equal to nil. In Objective-c you have to treat nil objects very carefully as the behaviour is different from how many other languages treat the similar null.
In Objective-c this is fine:
NSString *nilString = nil;
[nilString writeToFile:#"/this_file_cannot_exist.data"];
Many other languages would crash here or throw an exception; This can be dangerous, as an operation may fail but your app will continue running. It can also be great tho, because in other languages you will see lots of this..
someObject = controller.currentValue()
if( someObject!=null )
someObject.writeToFile("myFile.data")
In Objective-c the 'if(..)' line isn't needed at all.
You must be careful not to call the accessor method inside your init and dealloc methods, as this can break subclasses. Instead of
- (void)dealloc {
[self.string release]; // This is [[self string] release]
...
you should just use
- (void)dealloc {
[string release];
...
As well as being dangerous the call to [self string] is unnecessary. The same is true in your init methods
if(self=[super init]){
self.string = [[NSString alloc]init]; // shortcut for [self setString:[[NSString alloc] init]]
...
just use
if(self=[super init]){
string = [[NSString alloc] init];
...
You're missing the #property in order to use self.string.
Add this to your .h file
#property (readwrite, copy) NSString *string;
Using Copy instead of Retain will prevent the string from memory-leak even if you release anotherString.
#interface it:NSObject
{
NSString* string;
}
//you should declare a property in order to call it with 'self' prefix
#property (nonatomic, retain) NSString* string;
#end
#implementation it
//you should synthesize your property
#synthesize string;
-(id)init
{
if(self = [super init]){
//you don't to initialize NSString right here (memory leak will have place)
//self.string = [[NSString alloc]init];
//you can store a default value like this (can be omitted):
self.string = #"Default value";
}
return self;
}
-(void)dealloc
{
[self.string release];
[super release];
}
#end
And everything regarding memory management in this class will be fine.
I just ran my app through the Leaks in Instruments and I am being told that the following code causes leaks, but I don't see how.
I allocate some NSMutableArrays in my viewDidLoad with this code:
- (void)viewDidLoad {
[super viewDidLoad];
self.currentCars = [[NSMutableArray alloc] init];
self.expiredCars = [[NSMutableArray alloc] init];
}
Then I populate these arrays inside of my viewWillAppear method with the following:
[self.currentCars removeAllObjects];
[self.expiredCars removeAllObjects];
for (Car *car in [self.dealership cars]) {
if ([car isCurrent])
[self.currentCars addObject:car];
if ([car isExpired])
[self.expiredCars addObject:car];
}
And later in the code I release these arrays here:
- (void) viewWillDisappear:(BOOL)animated {
if (currentCars != nil) {
[currentCars release], currentCars = nil;
}
if (expiredCars != nil) {
[expiredCars release], expiredCars = nil;
}
[super viewWillDisappear:animated];
}
Any ideas? Thanks!
Your leak is here:
self.currentCars = [[NSMutableArray alloc] init];
self.expiredCars = [[NSMutableArray alloc] init];
Assuming that you declared property accessores like this:
#property(nonatomic, retain) NSMutableArray *currentCars;
#property(nonatomic, retain) NSMutableArray *expiredCars;
In my opinion, the best way to find leaks (other than using Instruments) is to keep track of the retain count manually.
If you were to do that with for example currentCars, you would find your leak easily. Here is what happens:
self.currentCars = [[NSMutableArray alloc] init];
// The 'init' makes the retain count 1.
// 'self.currentCars = ..' translates to the setCurrentCars: method.
// You probably did not implement that method yourself,
// but by synthesizing your property it is automatically implemented like this:
- (void)setCurrentCars:(NSMutableArray *)array {
[array retain]; // Makes the retain count 2
[currentCars release];
currentCars = array;
}
// In your viewWillDisappear: method
[currentCars release], currentCars = nil; // Makes the retain count 1 so the object is leaked.
The solution is simple. Use this:
NSMutableArray *tempMutableArray = [[NSMutableArray alloc] init];
self.currentCars = tempMutableArray;
[tempMutableArray release];
A little sidenote. You shouldn't release your objects in viewWillDisappear:. The recommended place to do that is dealloc. So your code would be:
- (void)dealloc {
[currentCars release], currentCars = nil;
[expiredCars release], expiredCars = nil;
[super dealloc];
}
The problem is (probably) that you are using the property accessors for the initial setting of the arrays in -viewDidLoad. Since well-implemented property accessors will retain the object, you are getting 1 retain from the +alloc and another retain from assigning it. To fix this, you should release your arrays after assigning them or use [NSMutableArray array] to get an autoreleased one to use for your initial assignments.
Unless you're doing something very odd in currentCars, expiredCars, dealership or cars, no, there's no leak there.
Instruments' pointer to the location of a leak isn't necessarily where the object is actually leaked, per se. If I were to guess, I'd say you're probably neglecting to release either currentCars or expiredCars in your dealloc method.
Does this seem right, the dataFilePath is on disk and contains the right data, but the MSMutable array does not contain any objects after the initWithCoder? I am probably just missing something, but I wanted to quickly check here before moving on.
-(id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if(self) {
[self setReactorCore:[decoder decodeObjectForKey:#"CORE"]];
}
return self;
}
.
-(id)init {
self = [super init];
if(self) {
if([[NSFileManager defaultManager] fileExistsAtPath:[self dataFilePath]]) {
NSMutableData *data = [[NSMutableData alloc] initWithContentsOfFile:[self dataFilePath]];
NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSMutableArray *newCore = [[NSMutableArray alloc] initWithCoder:unArchiver];
[self setReactorCore:newCore];
[newCore release];
[data release];
[unArchiver release];
} else {
NSMutableArray *newCore = [[NSMutableArray alloc] init];
[self setReactorCore:newCore];
[newCore release];
}
}
return self;
}
EDIT_001
I think I know where I am going wrong, I am archiving NSData and then trying to initialise my NSMutable array with it. I will rework the code and post back with an update.
gary
I am confused as to why you're doing things this way. You do not normally call initWithCoder: yourself. You ask the coder for its contents and it creates the objects for you. The whole decoding part of that method should be id archivedObject = [NSKeyedUnarchiver unarchiveObjectWithFile:[self dataFilePath]], where archivedObject is presumably the array you call newCore in your code (I don't know the contents of the file, so I'm just guessing from what you wrote). In that case, you'll want to mutableCopy it, since I don't think NS*Archiver preserves mutability.
I also hope you aren't expecting your initWithCoder: method that you wrote at the top of your post to be called when this NSArray is unarchived.
"I also hope you aren't expecting your initWithCoder: method that you wrote at the top of your post to be called when this NSArray is unarchived."
is not useful. Why can't you write why it's not called? Thanks
EDIT:
In fact, if the objects in your array implement NSCoding, initWithCoding is called on each of them when you restore your array (restore here means that you call [decoder decodeObjectForKey:#"yourArray"]). I'm actually doing this in my own code. So I think what you wrote is false!