7
votes

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

5

5 Answers

27
votes

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.

5
votes

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

1
votes

If u are assigning localList from another object may be that is not Mutable in that case it can through this kind of error.

I hope it will be helpful.

self.tableList = [localList mutableCopy];
0
votes

Hi instead of mutableCopy i believe "strong" can also be used to tackle this problem. I had similar problem in my code as well because of using "copy" instead of "strong." So the below line:

@property (copy, nonatomic) NSMutableArray *computers;

It should be:

@property (strong, nonatomic) NSMutableArray *computers;

Hope it will be of immense help for beginners making mistakes like me.

0
votes

This will resolve the issue:

NSMutableArray *localList = [[NSMutableArray alloc] init];
self.localList = [[NSMutableArray alloc]initWithArray:localList];