How do copy and mutableCopy apply to NSArray and NSMutableArray?

68,704

Solution 1

copy and mutableCopy are defined in different protocols (NSCopying and NSMutableCopying, respectively), and NSArray conforms to both. mutableCopy is defined for NSArray (not just NSMutableArray) and allows you to make a mutable copy of an originally immutable array:

// create an immutable array
NSArray *arr = [NSArray arrayWithObjects: @"one", @"two", @"three", nil ];

// create a mutable copy, and mutate it
NSMutableArray *mut = [arr mutableCopy];
[mut removeObject: @"one"];

Summary:

  • you can depend on the result of mutableCopy to be mutable, regardless of the original type. In the case of arrays, the result should be an NSMutableArray.
  • you cannot depend on the result of copy to be mutable! copying an NSMutableArray may return an NSMutableArray, since that's the original class, but copying any arbitrary NSArray instance would not.

Edit: re-read your original code in light of Mark Bessey's answer. When you create a copy of your array, of course you can still modify the original regardless of what you do with the copy. copy vs mutableCopy affects whether the new array is mutable.

Edit 2: Fixed my (false) assumption that NSMutableArray -copy would return an NSMutableArray.

Solution 2

I think you must have misinterpreted how copy and mutableCopy work. In your first example, myArray_COPY is an immutable copy of myArray. Having made the copy, you can manipulate the contents of the original myArray, and not affect the contents of myArray_COPY.

In the second example, you create a mutable copy of myArray, which means that you can modify either copy of the array, without affecting the other.

If I change the first example to try to insert/remove objects from myArray_COPY, it fails, just as you'd expect.


Perhaps thinking about a typical use-case would help. It's often the case that you might write a method that takes an NSArray * parameter, and basically stores it for later use. You could do this this way:

- (void) doStuffLaterWith: (NSArray *) objects {
  myObjects=[objects retain];
}

...but then you have the problem that the method can be called with an NSMutableArray as the argument. The code that created the array may manipulate it between when the doStuffLaterWith: method is called, and when you later need to use the value. In a multi-threaded app, the contents of the array could even be changed while you're iterating over it, which can cause some interesting bugs.

If you instead do this:

- (void) doStuffLaterWith: (NSArray *) objects {
  myObjects=[objects copy];
}

..then the copy creates a snapshot of the contents of the array at the time the method is called.

Solution 3

The "copy" method returns the object created by implementing NSCopying protocols copyWithZone:

If you send NSString a copy message:

NSString* myString;

NSString* newString = [myString copy];

The return value will be an NSString (not mutable)


The mutableCopy method returns the object created by implementing NSMutableCopying protocol's mutableCopyWithZone:

By sending:

NSString* myString;

NSMutableString* newString = [myString mutableCopy];

The return value WILL be mutable.


In all cases, the object must implement the protocol, signifying it will create the new copy object and return it to you.


In the case of NSArray there is an extra level of complexity regarding shallow and deep copying.

A shallow copy of an NSArray will only copy the references to the objects of the original array and place them into the new array.

The result being that:

NSArray* myArray;

NSMutableArray* anotherArray = [myArray mutableCopy];

[[anotherArray objectAtIndex:0] doSomething];

Will also affect the object at index 0 in the original array.


A deep copy will actually copy the individual objects contained in the array. This done by sending each individual object the "copyWithZone:" message.

NSArray* myArray;

NSMutableArray* anotherArray = [[NSMutableArray alloc] initWithArray:myArray
                                                       copyItems:YES];

Edited to remove my wrong assumption about mutable object copying

Solution 4

NSMutableArray* anotherArray = [[NSMutableArray alloc] initWithArray:oldArray
                                                           copyItems:YES];

will create anotherArray which is a copy of oldArray to 2 levels deep. If an object of oldArray is an Array. Which is generally the case in most applications.

Well if we need a True Deep Copy we could use,

NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
    [NSKeyedArchiver archivedDataWithRootObject: oldArray]];

This would ensure that all levels are actually copied retaining the mutability of the original object at each level.

Robert Clarence D'Almeida, Bangalore, India.

Solution 5

You're calling addObject and removeObjectAtIndex on the original array, rather than the new copy of it you've made. Calling copy vs mutableCopy only effects the mutability of the new copy of the object, not the original object.

Share:
68,704
fuzzygoat
Author by

fuzzygoat

Apple Development @ Fuzzygoat, A digital nomad adventuring in one possible future.

Updated on July 08, 2022

Comments

  • fuzzygoat
    fuzzygoat almost 2 years

    What is the difference between copy and mutableCopy when used on either an NSArray or an NSMutableArray?

    This is my understanding; is it correct?

    // ** NSArray **
    NSArray *myArray_imu = [NSArray  arrayWithObjects:@"abc", @"def", nil];
    
    // No copy, increments retain count, result is immutable
    NSArray *myArray_imuCopy = [myArray_imu copy];
    
    // Copys object, result is mutable 
    NSArray *myArray_imuMuta = [myArray_imu mutableCopy];
    
    // Both must be released later
    

    // ** NSMutableArray **
    NSMutableArray *myArray_mut = [NSMutableArray arrayWithObjects:@"A", @"B", nil];
    
    // Copys object, result is immutable
    NSMutableArray *myArray_mutCopy = [myArray_mut copy];
    
    // Copys object, result is mutable
    NSMutableArray *myArray_mutMuta = [myArray_mut mutableCopy];
    
    // Both must be released later
    
  • csakii
    csakii over 14 years
    Sending -copy to an NSMutableArray does not normally return another mutable array. It certainly could do so, but in the usual case, it returns an immutable, constant-time access, array.
  • Asher Dunn
    Asher Dunn over 14 years
    Oops, you are right. I misread the OP's code and thought he was modifying the result of -copy. It would make just as much sense for NSMutableArray's -copy to return another mutable array, so I (incorrectly) assumed that was the case.
  • fuzzygoat
    fuzzygoat over 14 years
    Am I missing something as I can't find any reference to [mut remove: @"one"]; should this not be [mut removeObjectIdenticalTo: @"one"]; I just want to clarify for those reading this and trying to learn?
  • Asher Dunn
    Asher Dunn over 14 years
    Oops, that should have been [mut removeObject: @"one"];. removeObject uses -isEqual to determine matches, and two strings are considered equal if they have the same content. removeObjectIdenticalTo only removes the exact object passed (it compares object addresses, not contents). [mut removeObjectIdenticalTo: @"one"] creates a new temporary NSString object, which is not in the array, and therefore cannot be removed from the array.
  • user102008
    user102008 over 13 years
    "copying an NSMutableArray may return an NSMutableArray" No, it NEVER returns an NSMutableArray. See developer.apple.com/library/mac/documentation/Cocoa/Conceptu‌​al/… "Where the concept “immutable vs. mutable” applies to an object, NSCopying produces immutable copies whether the original is immutable or not."
  • Nirav Gadhiya
    Nirav Gadhiya almost 10 years
    Your concept of "True Deep Copy" Is really good.. Exactly what I want... Thanks a lot and +1 for you.....
  • Abdul Yasin
    Abdul Yasin over 5 years
    nicely explained and a very impressive answer. Upvote for you... Good job