Objective-C why doesn't my array of array works? - objective-c

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...

Related

Confusion about modifying NSMutableArray contents after using addObject:

So, when I modify things inside of an NSMutableArray I don't get the result I expect. I think the best way to frame this question is with an example. The following code prints "george" (as expected):
NSMutableArray *originalArray = [[NSMutableArray alloc] initWithObjects:#"sally",#"george", nil];
NSMutableArray *secondArray = [[NSMutableArray alloc] init];
[secondArray addObject:originalArray[1]];
secondArray[0] = #"priscilla";
NSLog(#"%#",originalArray[1]);
But this code prints "priscilla":
TestClass *test1 = [[TestClass alloc] init];
test1.clientName = #"sally";
TestClass *test2 = [[TestClass alloc] init];
test2.clientName = #"george";
NSMutableArray *originalArray = [[NSMutableArray alloc] initWithObjects:test1,test2, nil];
NSMutableArray *secondArray = [[NSMutableArray alloc] init];
[secondArray addObject:originalArray[1]];
TestClass *objectTakenFromSecondArray = secondArray[0];
objectTakenFromSecondArray.clientName = #"priscilla";
NSLog(#"%#", ((TestClass *)originalArray[1]).clientName);
I thought that addObject: always copied the object before adding it to the array receiving the addObject: message. Is this not the case?
Thanks!
p.s. here is the interface and implementation for TestClass in case it is pertinent:
#interface TestClass : NSObject
#property (strong,nonatomic) NSString *clientName;
#end
#implementation TestClass
#synthesize clientName = _clientName
#end
I thought that addObject: always copied the object before adding it to the array receiving the addObject: message. Is this not the case?
addObject: does not copy the object. NSArray does not require that its contents even be copyable (not everything is). That probably explains the confusion. If you want to copy it, you need to do so yourself.
You pretty much answered your own question. When you create an NSMutableArray and add an object to it, you are just creating a pointer to that object, wherever it is stored. If you add the same object to another NSMutableArray, that too contains a pointer to the same thing. You might not need the analogy, but for anyone else confused - the NSMutableArray is like a postman with an address to post to, and the object is the house at that address. Two postmen (or two arrays) can have an address for the same house, but there is only one house still. (That is, unless someone explicitly 'copies' the house).
So in your second to last line of code, where you change that .clientName property, you are changing the property of the original *test2 object.
Worth noting in this case, that if you remove that second array, you don't remove the objects it contains necessarily. So in your case, removing that second NSMutableArray from memory does not mean that all of its objects also disappear from memory - unless everything else that points to those objects also is removed. The array does not contain pointers to unique copy of those objects - it just points to the originals.

Allocate using #define

I created a preprocessor definition to allocate and construct classes for me as follows:
#define new(cls) cls* _new() {return [[cls alloc] init];}()
Now I tried using it like:
- (NSMutableArray*) stack
{
if (!_stack)
{
/*_stack = [[NSMutableArray alloc] init];*/
_stack = new(NSMutableArray);
}
return _stack;
}
but it says expected expression and unexpected NSMutableArray interface. What is wrong with my definition and why can't I do it? Is there another way I can do this?
Just use new:
_stack = [NSMutableArray new];
which does the same thing as
_stack = [[NSMutableArray alloc] init];
No need for a macro which will be confusing to others.
For a long time the use of new was discouraged by Apple but it seems with the arrival of ARC it has come back into favor even in Apple's documentation.

Using Autoreleased Objects in iOS apps

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.

Why does releasing my array crash my app?

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.

out of scope - NSMutableArray error

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];