views:

329

answers:

2

Hi, I have a simple class with an NSMutableDictionary member variable. However, when I call setObject:forKey I get an error ('mutating method sent to immutable object'). The source of the problem is obvious from the debugger -- my NSMutableDictionary is actually of type NSDictionary.

I must be missing something incredibly simple but can't seem to fix it. Here is the relevant code:

// Model.h
@interface Model : NSObject {
    NSMutableDictionary *piers;
}
@property (nonatomic,retain) NSMutableDictionary *piers;
@end


// Model.m
@implementation Model
@synthesize piers;

-(id) init {
 if (self = [super init]) {
     self.piers = [[NSMutableDictionary alloc] initWithCapacity:2];
        [self createModel];
    }
    return self;
}

-(void) createModel {
 [piers setObject:@"happy" forKey:@"foobar"];  
}
@end

If I put a breakpoint anywhere in the code and investigate self.piers, it is of type NSDictionary. What am I missing so that it is treated as an NSMutableDictionary instead? Thanks!

A: 

Try remove the self..

 piers = [[NSMutableDictionary alloc] initWithCapacity:2];

In ObjC, the notation

obj.prop = sth;

is equivalent to

[obj setProp:sth];

which has totally different semantic than

obj->prop = sth;

Although highly unlikely, probably your mutable dictionary becomes immutable during the -setPiers: procedure. Just say No to self.anything (until you understand how property works).

KennyTM
Thanks for your answer. Unfortunately, removing `self` doesn't work. Anyway, it's my understanding that I do want to set the property of self called `piers`, so I could either use `-setPiers:` or I could `self.piers=`. All of Apple's example code uses `self.property=` when initializing member variables so I followed that paradigm.Another thing to note is that even on breakpoints occuring before the assignment in question has run, `piers` is shown as a NSDictionary in the debugger.
fractacular
@Marc: If you call `[piers setObject:@"happy" forKey:@"foobar"];` directly in `-init`, does that help?
KennyTM
WTF, I must admit that I did not fully investigate your suggestion -- instead I stopped on my breakpoint, noted that `piers` was still a NSDictionary in the debugger, and cried about it. In fact, removing `self` does work, and calling `-setObject:forKey` works everywhere.... even with my variable showing as an NSDictionary. Now I just need to understand why to avoid `self.` in this case -- it's all over Apple's code, as well as all the constructors in my book (Apress "Beginning iPhone 3 Dev."). I am noob, hear me roar. Thx :)
fractacular
@frac: Using `self.prop` is fine as long as you know what you're doing. The dot notation does much more thing than a simple assignment, and the actual action depends on how the property is declared. For example, your original code would have memory leak due to a `-retain` in the setter. OTOH, direct assignment is more explicit (in memory and action), and more efficient too.
KennyTM
@frac: NSDictionary and NSMutableDictionary are part of a class cluster so they can appear similar under the debugger. See http://developer.apple.com/mac/library/documentation/General/Conceptual/DevPedia-CocoaCore/ClassCluster.html for details.
Laurent Etiemble
fractacular: Did you give us different code in your question from what you had in your app? Retaining a mutable dictionary doesn't affect its mutability; therefore, a `retain` property shouldn't set the property to an immutable dictionary when given a mutable one. You would need to be using `copy` for that to happen.
Peter Hosey
+1  A: 

Your code works for me without any modification. I made a Foundation based command line tool (Mac OS X) with this code:

#import <Foundation/Foundation.h>

// Model.h
@interface Model : NSObject {
    NSMutableDictionary *piers;
}
@property (nonatomic,retain) NSMutableDictionary *piers;

-(void) createModel;

@end


// Model.m
@implementation Model
@synthesize piers;

-(id) init {
    if (self = [super init]) {
        self.piers = [[NSMutableDictionary alloc] initWithCapacity:2];
        [self createModel];
    }
    return self;
}

-(void) createModel {
    [piers setObject:@"happy" forKey:@"foobar"];  
}
@end

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // insert code here...
    Model *model = [[Model alloc] init];

    NSLog(@"Model: %@", [model.piers objectForKey:@"foobar"]);

    [pool drain];
    return 0;
}

and it gave me the expected output:

2010-04-06 12:10:19.510 Model[3967:a0f] Model: happy

As KennyTM says, your use of self is a little wrong. in your init, the general pattern is

NSMutableDictionary *aPiers = [[NSMutableDictionary alloc] initWithCapacity:2];
self.piers = aPiers;
[aPiers release];

Later on in the code, you should be using self.piers.

Try making a project like mine and see if the problem still exists. You'll probably find that the problem is somewhere else.

nevan
Thank you -- it works. And thanks for expanding on the memory leak, which I will avoid from now on.
fractacular