NSMutableArray not mutable?!

Discussion in 'iOS Programming' started by Mac Me Up, Aug 26, 2008.

  1. macrumors regular

    Joined:
    Jun 25, 2005
    Location:
    Australia
    #1
    Ok, so I'm relatively new to Objective-C, but not to programming. I have set up an NSMutableArray to hold a collection of objects, that I want to add and remove from. So in my interface:
    Code:
    NSMutableArray *savedLocations;
    if I try to add or remove objects from this array I get an exception:
    "mutating method sent to immutable object"

    I can get around this by copying the array adding my object, and then assigning the pointer to the new object, but this just seems silly.
    Code:
    NSMutableArray *test = [savedLocations mutableCopy];
    [test addObject:location];
    self.savedLocations = test;
    [test release];
    
    I figure I must be doing something rather stupid. Can someone give me any pointers about this?
     
  2. Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #2
    Please post the code that actually matters: where does something get assigned to savedLocations? Most likely you are really ending up with a NSArray, not an NSMutableArray...
     
  3. thread starter macrumors regular

    Joined:
    Jun 25, 2005
    Location:
    Australia
    #3
    The code is simply:
    Code:
    [savedLocations addObject:myLocation];
    
     
  4. Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #4
    No, that is where you try and use the array. I am asking you where you either create an array object or get an array object from elsewhere. Sounds like you are doing this:

    Code:
    NSMutableArray *savedLocations;
    [savedLocations addObject:someObject];
    
    This will never work: you have not created an array. You have reserved space for a pointer to an array, but it's not pointing at one.

    You need to either use an alloc/init pair, a convenience constructor or otherwise get an array.

    If this is what you are doing it represents a fundamental lack of understanding of what you are doing and you should stop writing code and go back to the beginning and read the language documentation...
     
  5. thread starter macrumors regular

    Joined:
    Jun 25, 2005
    Location:
    Australia
    #5
    Ok...I think maybe I'm not being specific enough. I am doing this in my init method:
    Code:
    savedLocations = [[NSMutableArray alloc] init];
    then I am trying to do this in another method:
    Code:
    -(void)saveLocation:(SavedLocation *)location{
    	[savedLocations addObject:[location retain]];
    }
    
    When I call that method I get an error. If I restructure that method to look like this:
    Code:
    -(void)saveLocation:(SavedLocation *)location{
    	NSMutableArray *temp = [savedLocations mutableCopy];
    	[temp addObject:location];
    	
    	self.savedLocations = temp;
    	[temp release];
    }
    
    The error goes away.

    This class I am doing all this in is a Singleton, but I wouldn't think that this would make any difference?
     
  6. Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #6
    Hmm, your initialisation is correct. As the error you are getting is a run-time not compile-time error (right) you must be assigning a non-mutable NSArray to that variable at some point. You'll need to go over your code till you find it.

    I would also note that
    Code:
    [savedLocations addObject:[location retain]];
    
    will leak memory: you don't need the retain as addObject will retain whatever you pass to it.
     
  7. thread starter macrumors regular

    Joined:
    Jun 25, 2005
    Location:
    Australia
    #7
    Thanks for your help. I commented out all my code for that class and started again. Now it works fine. Thanks for the tip on the retain as well, I had been wondering about that actually. As a JAVA dev all this release retain stuff is tedious, but the rules do seem fairly simply (release what you alloc, etc) I just wasn't quite sure how it works with arrays. Might have to do some more reading I think :D
     
  8. Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #8
    The rules are simply. You already have most of them :D!

    If you create an object via alloc/init or copy you must release it. An object you get via any other means (convenience constructors like [NSArray array] or returned from a method call etc) should be assumed to be autoreleased unless otherwise noted.

    This also goes for objects you pass to other objects: if the other object wants to keep the passed object around it will retain it. So in practical terms anything you pass to NSMutableArray via addObject: or to a NSMutableArray via setObject:forKey: for example will be retained: you should pass autoreleased objects to them.
     
  9. thread starter macrumors regular

    Joined:
    Jun 25, 2005
    Location:
    Australia
    #9
    Ok so my last dumb questions, is there any cases where I need to deallocate NSStrings?

    Eg:
    Code:
    NSString *temp = @"sdfsdfsdfds";
    
    Code:
    NSString *temp2 = [otherString stringByAppendingString:@"appendMe"];
    
    etc?

    My assumption is no, but maybe I'm wrong?
     
  10. macrumors 6502

    Joined:
    Jun 26, 2008
    #10
    Here's a rule of thumb that might help you. If the name of the selector you called starts with init you need to call release. If it doesn't then it's autoreleased for you. Conversely if the selector didn't start with init and you want to keep it around you need to call retain.
     
  11. macrumors member

    davedelong

    Joined:
    Sep 9, 2007
    Location:
    Right here.
    #11
    -copy and -mutableCopy are NOT autoreleased, nor is NSArray's -componentsJoinedByString, nor is a straight up +new. I've run up against all of these in the past 24 hours. :p

    Edit: IIRC, strings declared literally using @"" are internally a subclass of NSString called NSConstantString and are cleaned up automatically for you, since they're constant. Interesting side note:
    Code:
    NSString * string1 = @"Hi";
    NSString * string2 = @"Hi";
    NSLog(@"%x %x", *string1, *string2);
    The compiler will recognized that they're the same string and only create one instance of NSConstantString. Comparing the pointers will show that they're equivalent and identical. The NSLog above will print the same memory address for both string1 and string2, indicating that they both point to the same instance of NSConstantString.

    Dave
     
  12. Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #12
    What are you basing that on?
     
  13. macrumors member

    davedelong

    Joined:
    Sep 9, 2007
    Location:
    Right here.
    #13
    I got a string back from it, tried releasing it, and crashed my app when it came time to pop my NSAutoreleasePool.

    EDIT: WHOOPS, I meant to say it IS autoreleased when I was expect it to not be. LOL I feel dumb now. :p
     
  14. macrumors 6502

    Joined:
    Jun 26, 2008
    #14
    True, copy and mutableCopy not autoreleased, thanks for the correction (although a rule of thumb doesn't have to cover every case...just the general case ;)). On what basis did you expect componentsJoinedByString to not be autoreleased?
     

Share This Page