36

I have an NSMutableArray object (retained, synthesized as all) that is initiated just fine and I can easily add objects to it using the addObject: method. But if I want to replace an object at a certain index with a new one in that NSMutableArray, it doesn't work.

For example:

ClassA.h:

@interface ClassA : NSObject {
    NSMutableArray *list;
}

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

@end

ClassA.m:

#import "ClassA.h"

@implementation ClassA

@synthesize list;

- (id)init 
{
    [super init];
    NSMutableArray *localList = [[NSMutableArray alloc] init];
    self.list = localList;
    [localList release];
    //Add initial data
    [list addObject:@"Hello "];
    [list addObject:@"World"];
}


// Custom set accessor to ensure the new list is mutable
- (void)setList:(NSMutableArray *)newList 
{
    if (list != newList) 
    {
        [list release];
        list = [newList mutableCopy];
    }
}

-(void)updateTitle:(NSString *)newTitle:(NSString *)theIndex
{
    int i = [theIndex intValue]-1;
    [self.list replaceObjectAtIndex:i withObject:newTitle];
    NSLog((NSString *)[self.list objectAtIndex:i]);  // gives the correct output
}

However, the change remains true only inside the method. from any other method, the

NSLog((NSString *)[self.list objectAtIndex:i]);

gives the same old value.

How can I actually get the old object replaced with the new one at a specific index so that the change can be noticed from within any other method as well.

I even modified the method like this, but the result is the same:

-(void)updateTitle:(NSString *)newTitle:(NSString *)theIndex
{
    int i = [theIndex intValue]-1;

    NSMutableArray *localList = [[NSMutableArray alloc] init];
    localList = [localList mutableCopy];
    for(int j = 0; j < [list count]; j++)
    {
        if(j == i)
        {
            [localList addObject:newTitle];
            NSLog(@"j == 1");
            NSLog([NSString stringWithFormat:@"%d", j]);
        }
        else
        {
            [localList addObject:(NSString *)[self.list objectAtIndex:j]];
        }
    }
    [self.list release];
    //self.list = [localList mutableCopy];
    [self setList:localList];
    [localList release];
}

Please help out guys :)

jszumski
  • 7,430
  • 11
  • 40
  • 53
user339076
  • 431
  • 2
  • 5
  • 4
  • 1
    Sorry, but you really need to work through the Objective-C documentation, the memory management papers (which are great), and the property chapters. This code *really* is buggy all over... – Eiko Jun 17 '10 at 19:41

6 Answers6

131

This does the trick:

[myMutableArray replaceObjectAtIndex:index withObject:newObject];
Mário Carvalho
  • 3,106
  • 4
  • 22
  • 26
  • can you tell me. about `replaceObjectAtIndex` method. if I call `list[i] = newTitle;` compile without errors or warnings. is there something wrong with this usage ? Thanks :) – hqt Aug 09 '14 at 19:35
  • 9
    @Mario You didn't even read the question, did you? But hey, neither did 52 other people... – walkytalky Aug 12 '14 at 11:24
8

OK, there are a few bits of confusion here.

You don't need to take a mutableCopy of a newly created NSMutableArray to make it mutable. It's already mutable -- the clue is in the name. You only need to do that in the setter if you want the property to have copy semantics (which you've set, and may have good reason for, of course). But you certainly wouldn't need to do it as shown in your updated updateTitle code, and doing so leaks localList.

Also, you're mixing together property access via self.list and direct use of list in the same method. This is not invalid, but it's bad practice, because it means whatever other stuff the accessor methods do is being randomly bypassed. It's common for properties like this to do everything through self except in the accessors themselves, or in dealloc, and possibly in init (opinions seem to differ on this), where you would access the ivar directly.

Also, never call [self.list release] -- the property accessor doesn't give its caller ownership. Doing this will end in tears, mark my words.

None of this answers the real question, which is why is your change disappearing. The original updateTitle code does not explain this as far as I can see -- it should work. So I suspect that somewhere else you are calling self.list = theOriginalList and hence undoing your change.

Update:

Just for the sake of argument, I'm going to post what I think the code you posted is probably meant to look like. I've preserved your use of a string to pass the index to updateTitle, but I'd like to point out that doing it this way is wrong. It's a number, you should pass it as such. Even if the number comes from a text field or something, that's the caller's concern; the class interface should specify a number. Similarly the apparent change from 1-based to 0-based indexing. Please do not do this sort of thing implicitly, it is a recipe for weeping and gnashing of teeth.

ClassA.h:

#import <Cocoa/Cocoa.h>
@interface ClassA : NSObject
{
    NSMutableArray* list;
}
- (void) setList:(NSMutableArray*)newList;
- (void) updateTitle:(NSString*)newTitle forIndex:(NSString*)theIndex;
@property (nonatomic, copy, readwrite) NSMutableArray* list;
@end

ClassA.m:

#import "ClassA.h"

@implementation ClassA
@synthesize list;

- (id) init
{
    if ( self = [super init] )
    {
        list = [[NSMutableArray alloc] init];
        [list addObject:@"Hello "];
        [list addObject:@"World"];
    }
    return self;
}

- (void) setList:(NSMutableArray*) newList
{
    if ( list != newList )
    {
        [list release];
        list = [newList mutableCopy];
    }
}

- (void) updateTitle:(NSString*)newTitle forIndex:(NSString*)theIndex
{
    int i = [theIndex intValue] - 1;
    [self.list replaceObjectAtIndex:i withObject:newTitle];
}

- (void) dealloc
{
    [list release];
    [super dealloc];
}

@end

This cleans up various issues, but note that updateTitle is mostly the same. If you drop all this in and the change still doesn't survive, you are definitely resetting list somewhere.

walkytalky
  • 9,453
  • 2
  • 36
  • 44
  • Thanks for the advice walkytalky. I've modified my code accordingly. Now, I've gone through my code and I can't find a single self.list = theOriginalList call anywhere. But I wonder if the 'setList:' method gets called when the 'replaceObjectAtIndex' is used perhaps. Any ideas? Thanks again :) – user339076 Jun 17 '10 at 18:37
  • 1
    `replaceObjectAtIndex` definitely should not call `setList` -- it's a method on the array itself, and it neither knows nor cares about the property accessors of the object(s) it happens to belong to. – walkytalky Jun 17 '10 at 23:04
  • can you tell me. about `replaceObjectAtIndex` method. if I call `list[i] = newTitle;` compile without errors or warnings. is there something wrong with this usage ? Thanks :) – hqt Aug 09 '14 at 19:34
  • @hqt The usage is fine, it just didn't exist at the time this answer was written -- that and related ObjC syntax for Cocoa arrays and dictionaries was added in about 2012 I think. – walkytalky Aug 12 '14 at 11:11
8

A more straight answer would be:

self.list[i] = newTitle;

This just works like

[self.list replaceObjectAtIndex:i withObject:newTitle];
antonio081014
  • 3,553
  • 1
  • 30
  • 37
0

Look at this line:

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

The copy means that whenever you access self.list, you don't get the "_list" instance variable of your object, but a copy of that list. If you write [self.list replaceObjectAtIndex... ] you replace an object in that copy of your list; the original _list is unchanged. Just use

@property (nonatomic, strong, readwrite) NSMutableArray *list;

And to avoid confusion, remove the "list" instance variable and the @synthesize statement, then use _list to access the instance variable.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • If I'm not mistaken the "copy" attribute means that only on "set" the instance creates a copy. But it doesn't happen on "get" like you mentioned. See https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/EncapsulatingData/EncapsulatingData.html – Oded Regev Dec 16 '15 at 13:41
  • @OdedRegev right, the `copy` attribute as the docs says is only applied for newly created properties. Getters, as I understand, will return the original object but won't allow you to modify it in the owner's class (it will simply stay in the original -unmodified- property). – Alejandro Iván Feb 15 '16 at 13:25
0

For Swift you could try:

 //if you have indexPath
   self.readArray.removeAtIndex((indexPath?.row)!)
   self.readArray.insert(tempDict, atIndex: (indexPath?.row)!)
 //tempDict is NSDictionary object.
Avijit Nagare
  • 8,482
  • 7
  • 39
  • 68
0

Finally Got Some Perfect Code,

let DuplicateArray: NSArray = array
let DuplicateMutableArray: NSMutableArray = []
DuplicateMutableArray.addObjectsFromArray(DuplicateArray as [AnyObject])
var dic = (DuplicateMutableArray[0] as! [NSObject : AnyObject])
dic["is_married"] = "false"
DuplicateMutableArray[self.SelectedIndexPath] = dic
array = []
array = (DuplicateMutableArray.copy() as? NSArray)!

//Output Will Be Like

array =  [
  {
    "name": "Kavin",
    "Age": 25,
    "is_married": "false"
  },
  {
    "name": "Kumar",
    "Age": 25,
    "is_married": "false"
  }
]
Kavin Kumar Arumugam
  • 1,792
  • 3
  • 28
  • 47