views:

370

answers:

3

I need to check specific positions in an NSArray to see if they have already been initialized, but I am having trouble. I tried to do the following, but it causes my application to crash!

if ((NSMutableArray *)[arrAllBlocks objectAtIndex:iLine] == nil) 
{
    [arrAllBlocks insertObject:[[NSMutableArray alloc] init] atIndex:iLine];
}

NSMutableArray *columArray = (NSMutableArray *)[arrAllBlocks
                                                objectAtIndex:iLine];
[columArray insertObject:newBlock atIndex:iColumn];

What is the best to do this? I already tried some methods like isValid, and things like that!

+4  A: 

You can't. NSArray (and its subclass NSMutableArray) do not allow you to insert nil into the array. That's clearly outlined in the documentation.

If, for some reason, you need to have "empty" values in an array, then you should insert [NSNull null] instead and test for that. From the docs: "The NSNull class defines a singleton object used to represent null values in collection objects (which don’t allow nil values)."

UPDATE:

This means you could change your code very simply to this:

if ([[arrAllBlocks objectAtIndex:iLine] isEqual:[NSNull null]]) {
  [(NSMutableArray *)arrAllBlocks insertObject:[NSMutableArray array] atIndex:iLine];
}
NSMutableArray *columnArray = (NSMutableArray *)[arrAllBlocks objectAtIndex:iLine];
[columnArray insertObject:newBlock atIndex:iColumn];
Dave DeLong
Hummm, ok!But i try what you tell, and not work yet =/I Need this, becouse the number of objects in arrAllBlocks, may change! So i want do that dynamic! I can make a for before that, and create all the instances i need, but it seens a bit hardcode!
baDa
@Dave DeLong: You're right about the usage of NSNull objects, but baDa would have to pre-fill his array with them in order for this to work. If he's going to do that, he may as well pre-fill the array with empty NSMutableArrays instead. I posted an answer with more details.
e.James
@eJames: good point, and good answer (I voted it up). NSNull would be for the case when he wants to maintain indices but still "nil" out certain positions within his array. =)
Dave DeLong
+5  A: 

You have a few options here:

Option 1: Pre-fill the array with instances of NSNull, and then use the code given by Dave DeLong in his answer.

Option 2: (Similar to #1) pre-fill the array with instances of NSMutableArray, and then have no extra code at all. (If you're going to pre-fill, you may as well do this).

Option 3: Do not pre-fill the array, but insert items dynamically as required. This will be almost identical to a pre-fill if the first iLine is near the maximum:

while([arrAllBlocks count] <= iLine)
{
    [arrAllBlocks addObject:[NSMutableArray arrayWithCapacity:0]];
}

NSMutableArray *columArray = (NSMutableArray *)[arrAllBlocks
                                                objectAtIndex:iLine];
[columArray insertObject:newBlock atIndex:iColumn];

Option 4: Use a dictionary to maintain the list of NSMutableArrays:

NSString *key = [NSString stringWithFormat:@"%d", iLine];
NSMutableArray *columnArray = [dictAllBlocks objectForKey:key];
if (columnArray == nil)
{
    columnArray = [NSMutableArray arrayWithCapacity:0];
    [dictAllBlocks setObject:columnArray forKey:key];
}

[columArray insertObject:newBlock atIndex:iColumn];

How to choose:

If the maximum value for iLine is not enormous, I would go with option #2. A handful of NSMutableArrays initialized to zero capacity will take up very little memory.

If the maximum value for iLine is enormous, but you expect it to be accessed sparsely (i.e., only a few values of iLine will ever be accessed), then you should go with Option #4. This will save you from having to fill an NSMutableArray with objects that never get used. The overhead of converting the string-value key for the dictionary will be less than the overhead for creating all of those blanks.

If you're not sure, try out each option and profile them: measure your memory usage and the time required to execute. If neither of these options work, you may have to explore more complex solutions, but only do that if it turns out to be necessary.

A note of caution:

The original code that you posted has a memory leak in the following line:

[arrAllBlocks insertObject:[[NSMutableArray alloc] init] atIndex:iLine];

The NSMutableArray objects that you initialize here are never released. When you call [[NSMutableArray init] alloc], a brand new object is created (with a reference count of one). The insertObject method then adds that new object to arrAllBlocks, and retains it (increasing its retain count to 2). Later, when you release arrAllBlocks, the new array will be sent a release message, but that will only reduce its retain count to one again. At that point, it will stick around in RAM until your program exits.

The best thing to do here is to use [NSMutableArray arrayWithCapacity:0] instead (as I have done in my examples). This returns a new NSMutableArray, just the same as your code did, but this instance has already been autoreleased. That way, arrAllBlocks can take ownership of the new object and you can be sure that it will be released when appropriate.

e.James
+1  A: 

To check for NSNull you can simply compare against the pointer, since it's a Singleton:

if ([NSNull null] == [arrAllBlocks objectAtIndex:iLine]) {
  [arrAllBlocks insertObject:[NSMutableArray array] atIndex:iLine];
}
NSMutableArray *columnArray = [arrAllBlocks objectAtIndex:iLine];
[columnArray insertObject:newBlock atIndex:iColumn];

I also removed the unsightly casts. Casting is rarely necessary in Objective-C. It usually just adds noise, and can hide real bugs. Since you're experiencing crashes, it's worth removing the casts from this code and listen to what the compiler has to tell you about it.

Telling the compiler to ignore warnings for a piece of code does not make the underlying problem with it go away!

Stig Brautaset