views:

1474

answers:

3

Hi guys,

I'm pretty new to objective-c and try to create a small app for the iphone.
I'm nearly done beside this little error here. Actually, I've searched hours with google to find a proper solution but unfortunately I'm not able to find a solution which works.
I'm using this tutorial here to build up an UITableView: UITableView Tutorial The full error message looks like this:

* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '* -[NSCFArray insertObject:atIndex:]: mutating method sent to immutable object'

This is the Data Controller Header: MyLinksDataController.h

@interface MyLinksDataController : NSObject {

NSMutableArray *tableList; //<---important part

}

- (unsigned)countOfList;
- (id)objectInListAtIndex:(unsigned)theIndex;
- (void)addData:(NSString *)data; //<---important part
- (void)removeDataAtIndex:(unsigned)theIndex;

@property (nonatomic, copy, readwrite) NSMutableArray *tableList; //<---important part

.....

And the Data Controller Method: MyLinksDataController.m

#import "MyLinksDataController.h"

@implementation MyLinksDataController

@synthesize tableList;

- (id)init {

    if (self = [super init]) {

     NSLog(@"Initilizing DataController");
     //Instantiate list
     NSMutableArray *localList = [[NSMutableArray alloc] init];
     self.tableList = [localList copy];
     [localList release];

     //Add initial Data
     [self addData:@"AAAAAAAAAAAAAA"];
     [self addData:@"BBBBBBBBBBBBBB"];

    }

    return self;

}

-------------------------------later on in the source code---------------------------------

- (void)addData:(NSString*)data; {

    [tableList addObject:data]; //<---- here the app crashes

}

I would pretty much appreciate any help.

Thank you for your support in advance.

Daniel

+9  A: 

Sending the copy message to an NSMutableArray -- as in the following statement in init -- returns an immutable copy.

self.tableList = [localList copy];

Cocoa documentation uses the word immutable to refer to read-only, can't-be-changed-after-initialization objects. Hence the subsequenct call to addObject: fails with an error message.

Note how the assignment statement above doesn't trigger any compiler warning. copy returns an id, which fits comfortably -- as far as the compiler is concerned -- in the NSMutableArray* tableList. There's no runtime error here either, as no messages get passed around; an NSArray pointer is just placed in an NSMutableArray pointer variable.

To obtain a mutable copy, use mutableCopy instead.

Note that both copy and mutableCopy create a new array and copy the content of the original to it. A change in the copy will not be reflected in the original. If you just need another reference to the original array, use retain instead.

You can find more detail in the discussion section of the copyWithZone reference and in the NSMutableCopying protocol reference.

Oren Trutner
+1  A: 

You're running into, basically, the memory management rules of Cocoa (specifically, these details). If there is an object with an immutable version and a mutable version, then sending -copy to an object will return an immutable object.

Let's step through the relevant part.

NSMutableArray *localList = [[NSMutableArray alloc] init];

This creates a new, empty mutable array that you own. Fine.

self.tableList = [localList copy];

This creates an immutable copy of the empty array. Furthermore, you own this freshly created copy. That's two objects you own at the moment.

This also assigns your copied object to the tableList property. Let's look at the property declaration:

@property (nonatomic, copy, readwrite) NSMutableArray *tableList;

This property is declared with the copy attribute, so whenever a new value is assigned to it, another -copy method is sent to it. This third copy, however, is not owned by you—it's owned by the object.

[localList release];

That releases the original empty mutable array. Fine, but there's still the one you made in the second line floating around, unreleased. That's a memory leak.

If you actually need a mutable copy of something, you want the -mutableCopy method. (The documentation for these methods is found under NSCopying and NSMutableCopying.) However, you're never going to get a mutable version of something int a property with the copy attribute, since it will send -copy to whatever it is assigned. Your property should use the retain attribute instead of the copy attribute, and the code to initialize it should look something like this:

NSMutableArray *localList = [[NSMutableArray alloc] init];
self.tableList = localList;
[localList release];

Or, a shorter version:

self.tableList = [NSMutableArray array];

There's no need to copy anything in this situation, you're just creating a fresh object.

John Calsbeek
If a copy property is really called for, then substituting a retain property is going to be a poor solution. Since there is no mutablecopy keyword, declare the property as copy, but write your own accessor instead of using synthesized accessors.
Jim Correia
Good point. I'd also throw in that I almost never see objects that advertise an `NSMutableArray` property. The purist in me would want to see a custom setter that stores a mutable copy of the object and a custom getter that returns an autoreleased immutable copy.
John Calsbeek
A: 

That's it, actually the copy messed everything up and mutableCopy fixed it instead. Thanks guys, this really helped me a lot!