If I release my first array after I copied it to the second array my app crashes. If I autorelease the first array everything works fine. Why? Is there a better way to copy the first array to the second array?
If I call this method I get a ECX_BAD_ACCESS, I am passing an empty array
-(NSArray *)loadSystemDetails
{
AssortedCodeSnippets *acs = [[AssortedCodeSnippets alloc] init];
NSArray *details;
NSString *fp = [self tempPathAndFileName:[self systemDetailsFileName]];
if ([acs fileExistsAtPath:fp]) {
NSArray *array = [[NSArray alloc] initWithContentsOfFile:fp];
details = array;
[array release];
} else {
NSLog(#"No File to Load");
CreateSystem *cls = [[CreateSystem alloc] init];
details = [cls loadData];
[cls release];
[self saveDataFile:details toPath:fp];
}
NSLog(#"details: %#",details);
[acs release];
return details;
}
If I autorelease array it work fine.
-(NSArray *)loadSystemDetails
{
AssortedCodeSnippets *acs = [[AssortedCodeSnippets alloc] init];
NSArray *details;
NSString *fp = [self tempPathAndFileName:[self systemDetailsFileName]];
if ([acs fileExistsAtPath:fp]) {
NSArray *array = [[[NSArray alloc] initWithContentsOfFile:fp]autorelease];
details = array;
} else {
NSLog(#"No File to Load");
CreateSystem *cls = [[CreateSystem alloc] init];
details = [cls loadData];
[cls release];
[self saveDataFile:details toPath:fp];
}
Let's step through this
Key: M = release/retain message, C = sum of the release/retain messages
// +----+---+
// | M | C |
// +----+---+
NSArray *array = [[NSArray alloc] initWithContentsOfFile:fp]; // | +1 | 1 |
details = array; // | 0 | 1 |
[array release]; // | -1 | 0 |
// +----+---+
At this point you can see that you are going to be returning details which has a count of 0 therefore will have been deallocated already = crash.
Copy is the wrong term as you do not actually need a copy as such you just want your pointer details to point to a valid object therefore the following would be more correct
- (NSArray *)systemDetails
{
NSString *filePath = [self tempPathAndFileName:[self systemDetailsFileName]];
NSArray *details = [[[NSArray alloc] initWithContentsOfFile:filePath] autorelease];
if (!details) {
NSLog(#"No File to Load");
CreateSystem *cls = [[CreateSystem alloc] init];
details = [cls loadData];
[cls release]; cls = nil;
[self saveDataFile:details toPath:filePath];
}
NSLog(#"details: %#",details);
return details;
}
Here I am taking advantage of NSArray's method
initWithContentsOfFile:
...[returns] nil if the file can’t be opened or the contents of the file can’t be parsed into an array
This cuts down some of the cruft and makes the method a bit easier to read. I also expanded the variable names to meaningful names (personal preference).
I have also renamed the method as the load art is superflous as essentially you are returning the system details the fact that they are being loaded is not not really a concern to the caller of the method.
It's also important to note that the other answers suggest that you take an additional retain/copy and then remember to release the returned result later on. This goes against cocoa convention as the method name does not contain new/init/copy therefore the callers f the method should not end up owning the result.
Send details assignment a retain message, when assigning details to point to array or the result of cls's -loadData method. Be sure to release the details somewhere after the -loadSystemDetails method.
In your code example, you're not actually copying array into details. Remember that both of these variables are pointers to arrays and not arrays themselves. Thus, the line details = array simply copies the location of the array into details. In other words, after this line, both variables are pointing to exactly the same array in memory. Therefore, when you call release, the object in memory is deallocated, and both details and array are now pointing to a non-existent object. If you want to actually copy the array in memory, use
details = [array copy]
Remember that eventually you'll have to call release on details when you want to get rid of this object.
Related
To return a NSArray or NSDictionary, I have seen most people use the below implementation and this is also what some books suggest. (iOS Development A Practical Approach - )
OPTION 1
-(NSArray*)listOfStudents{
NSMutableArray *temp = [[NSMUtableArray alloc] init];
//Add elements to the array
//
//
//
NSArray *students = [NSArray arrayWithArray:temp];
return students;
}
-(void)viewWillAppear{
self.studentsList = [self listOfStudents];
}
But can this same be done by the below way also?
OPTION 2
-(NSArray*)newListOfStudents{
NSMutableArray *temp = [[NSMUtableArray alloc] init];
NSArray *students = [[NSArray alloc]initWithArray:temp];
[temp release];
//Add elements to the array
//
//
//
return students;
}
-(void)viewWillAppear{
NSArray *array = [self newListOfStudents];
self.studentsList = array;
[array release];
}
Assume these methods are called in the main thread itself.
Interms of memory usage , I think that the second option is good, because it does not create autoreleased objects, because they are released only at when the autorelease pool is drained.
I assume that the main autorelease pool is drained only when the app quits. So if the method in OPTION 1 is used many times ,(since they are getting called in ViewWillAppear) I think that many lists will be in autorelease pool being released only when the app quits.
So is the OPTION 2 approach the better approach?
UPDATE:
I have updated the viewWillAppear implementation for better clarity.
I think in the second example you meant to call
self.studentsList = [self newListOfStudents];
In case that studentsList is a retained property, this would leak now.
Also, that temp array in both examples is just useless overhead. In the second example it's plain nonsense.
The cleanest solution is
-(NSArray *)listOfStudents {
NSMutableArray *list = [NSMutableArray array];
// Add things to array
return list;
}
Two more advices:
1) you might run the static analyzer over your code, which will point to memory issues.
2) if you feel more confident with memory management, switch over to ARC.
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
I have a memory leak problem that just can not understand! Watch this initialization method:
- (id)initWithNomeCompositore:(NSString *)nomeCompositore nomeOpera:(NSString *)nomeOpera {
if (self = [super init]) {
NSString *pathOpere = [[NSBundle mainBundle] pathForResource:kNomeFilePlistOpere ofType:kTipoFilePlist];
NSDictionary *dicOpera = [NSDictionary dictionaryWithDictionary:
[[[NSDictionary dictionaryWithContentsOfFile:pathOpere]
objectForKey:nomeCompositore]
objectForKey:nomeOpera]];
self.nomeCompleto = [[NSString alloc] initWithString:nomeOpera];
self.compositore = [[NSString alloc] initWithString:nomeCompositore];
self.tipologia = [[NSString alloc] initWithString:[dicOpera objectForKey:kKeyTipologia]];
}
return self;}
Then this little variation (note self.tipologia):
- (id)initWithNomeCompositore:(NSString *)nomeCompositore nomeOpera:(NSString *)nomeOpera {
if (self = [super init]) {
NSString *pathOpere = [[NSBundle mainBundle] pathForResource:kNomeFilePlistOpere ofType:kTipoFilePlist];
NSDictionary *dicOpera = [NSDictionary dictionaryWithDictionary:
[[[NSDictionary dictionaryWithContentsOfFile:pathOpere]
objectForKey:nomeCompositore]
objectForKey:nomeOpera]];
self.nomeCompleto = [[NSString alloc] initWithString:nomeOpera];
self.compositore = [[NSString alloc] initWithString:nomeCompositore];
self.tipologia = [[NSString alloc] initWithString:#"Test"];
}
return self;}
In the first variant is generated a memory leak, the second is not! And I just can not understand why! The memory leak is evidenced by Instruments, highlighted the line:
[NSDictionary dictionaryWithContentsOfFile:pathOpere]
This is the dealloc method:
- (void)dealloc {
[tipologia release];
[compositore release];
[nomeCompleto release];
[super dealloc];}
Remember that alloc returns an object that you own.
If you declared your three string properties as retain, assigning those objects to your properties means you now own each one twice—once because you allocked it, and again because you assigned it to your property. The objects remain alive because nothing releases their second ownerships.
If you declared the properties as copy (which is the correct way to declare an NSString property), assigning the object there stores a copy as the value of the property. You do nothing further with the original objects, which remain alive because nothing releases them.
Either way, that is your leak.
The property should be declared as copy; if it already is, don't try to fix the leak by changing that.
You should not use property access here. Remember that assigning to a property is a set<PropertyName>: message, and that your object is not fully initialized yet. Sending a message to an incompletely-initialized or incompletely-deallocated object is asking for trouble, particularly when subclasses are involved, since they may override the accessor methods in ways the superclass doesn't expect.
So, in init only, assign directly to the instance variables. In dealloc only, send release messages directly to the objects in the instance variables. Everywhere else, use property accesses.
You also should not use alloc and initWithString: here. It'll work, but the convention is to send copy messages to the objects you already have, the same as the properties would do. Send copy messages to your input string objects, then assign the copies to your instance variables.
When you do use property accesses, use the convenience constructors (stringWith…:, for example), as these return objects that you do not own. When you assign these objects to your copy-declared properties, you will actually be storing copies that you do own.
The other way would be to use alloc and initWithWhatever:, then immediately autorelease that object before assigning it to the property; this way creates an object that you own, then immediately gives up ownership before assigning it to the property.
Try
nomeCompleto = [[NSString alloc] initWithString:nomeOpera];
compositore = [[NSString alloc] initWithString:nomeCompositore];
tipologia = [[NSString alloc] initWithString:[dicOpera objectForKey:kKeyTipologia]];
or
self.nomeCompleto = nomeOpera;
self.compositore = nomeCompositore;
self.tipologia = [dicOpera objectForKey:kKeyTipologia];
instead of self.xxx = [[yyy alloc] init...].
In the original code, the RHS of the assignment returns an object of retain count +1, and if you make the #property having (retain) or (copy), the final retain count would be +2. Therefore, even if you release these in -dealloc, the net retain count is +1, causing a memory leak.
BTW, there's no point calling +dictionaryWithDictionary:. Just use
NSDictionary* dicOpera = [[[NSDictionary dictionaryWithContentsOfFile:pathOpere]
objectForKey:nomeCompositore]
objectForKey:nomeOpera];
This is probably a completely stupid question, but i'm pretty new at objective-C and programing in general.
i'm trying to make an array of arrays but can't manage to make it work :
#interface ArraysAndDicts : NSObject {
NSMutableArray * mySimpleArray;
NSMutableArray * myComplicatedArray;
}
the implementation :
-(void)generateValueForArrayOfArrays {
[self generateValueForArray];
//this generates an array with 5 elements 'mySimpleArray'
[myComplicatedArray addObject:mySimpleArray];
NSMutableArray * mySecondaryArray = [[NSMutableArray alloc] init];
[mySecondaryArray addObject:#"twoone"];
[mySecondaryArray addObject:#"twotwo"];
[myComplicatedArray addObject:mySecondaryArray];
(i edited out all the NSLogs for clarity)
When running my app, the console tells me :
mySecondaryArray count = 2
mySimpleArray count = 5
myComplicatedArraycount = 0
So, i know there are other ways to make multidimensional arrays,
but i'd really like to know why this doesn't work.
Thank you.
It looks like you're never creating myComplicatedArray. This means that
[myComplicatedArray addObject:mySimpleArray];
is actually
[nil addObject:mySimpleArray];
Sending messages to nil simply has no effect in Objective-C, so nothing happens. When you ask for the count of the array, you're effectively sending [nil count], which will return 0 in your case. You can find more information about sending messages to nil here.
Also, when you're doing
NSMutableArray * mySecondaryArray = [[NSMutableArray alloc] init];
[mySecondaryArray addObject:#"twoone"];
[mySecondaryArray addObject:#"twotwo"];
mySecondaryArray will leak. You should release it, either with autorelease
NSMutableArray * mySecondaryArray = [[[NSMutableArray alloc] init] autorelease];
...
or release, whichever is appropriate.
NSMutableArray * mySecondaryArray = [[NSMutableArray alloc] init];
...
[mySecondaryArray release];
We can't see how myComplicatedArray is initialised. Perhaps it's nil.
Did you initialize myComplicatedArray somewhere?
myComplicatedArray = [[NSMutableArray alloc] init];
[self generateValueForArray];
// etc...
data = [[NSMutableArray arrayWithCapacity:numISF]init];
count = 0;
while (count <= numISF)
{
[data addObject:[[rouge_col_data alloc]init]];
count++;
}
When I step through the while loop, each object in the data array is 'out of scope'
rouge col data 's implementation looks like this..
#implementation rouge_col_data
#synthesize pos;
#synthesize state;
-(id) init {
self = [super init];
return self;
}
#end
Most tutorials I could find only use NSStrings for objects in these kinds of arrays.
-Thanks
Alex E
EDIT
data = [[[NSMutableArray alloc] initWithCapacity:numISF]retain];
//data = [[NSMutableArray arrayWithCapacity:numISF] retain];
count = 0;
while (count < numISF)
{
[data addObject:[[[rouge_col_data alloc]init]autorelease]];
count++;
}
still the same error, even when switching the 'data = '.
You don't need to call init on the result of your arrayWithCapacity: call. arrayWithCapacity: already returns you an initialized (but autoreleased) object. Alternatively you could call [[NSMutableArray alloc] initWithCapacity:].
Your loop has an off by one error; you're starting at zero, so you'll add an extra object. Adding this extra object will succeed - it just doesn't seem like what you're trying to do.
You probably want to autorelease the objects you're adding to the array. The array will retain them on its own. If you do have some need to retain the objects themselves, that's fine, but it's pretty common to let the array do the retention for you.
You should retain the array itself, otherwise it will vanish at the end of the event loop.
The only error I can spot in your code is your NSArray initialization.
Where you do:
data = [[NSMutableArray arrayWithCapacity:numISF] init];
you should be doing:
data = [NSMutableArray arrayWithCapacity:numISF];
This is because arrayWithCapacity is a factory method, and will return you an autoreleased instance. If you want to keep using the object after this method, you'll need to retain it, and your could will look like:
data = [[NSMutableArray arrayWithCapacity:numISF] retain];