How do copy and mutableCopy apply to NSArray and NSMutableArray? - cocoa

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

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.

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.

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

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.

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.

To state it simply,
copy returns an immutable (can't be modified) copy of the array,
mutableCopy returns a mutable (can be modified) copy of the array.
Copy (in both cases) means that you get a new array "populated" with object references to the original array (i.e. the same (original) objects are referenced in the copies.
If you add new objects to the mutableCopy, then they are unique to the mutableCopy. If you remove objects from the mutableCopy, they are removed from the original array.
Think of the copy in both cases, as a snapshot in time of the original array at the time the copy was created.

Assume
NSArray *A = xxx; // A with three NSDictionary objects
NSMutableArray *B = [A mutableCopy];
B's content is NSDictionary object not NSMutableDictionary, is it right?

-(id)copy always returns a immutable one & -(id)mutableCopy always returns a mutable object,that's it.
You have to know the return type of these copying stuff and while declaring the new object which one will be assigned the return value must be of immutable or mutable one, otherwise compiler will show you error.
The object which has been copied can not be modified using the new one,they are totally two different objects now.

Related

NSMutableArray to NSDictionary and then add to an NSArray

My app stores images in an NSMutableArray. I then call those objects and then send them through email in the mailSender.parts section of the code below. The problem is it only adds the first objectatindex when I need to add all objects. I am confused on how to make each image in the self.arrSlidshowImg NSMutableArray add to the vcfPart2 NSDictionary and then add it as an array object so the mailSender.parts will send all images. Any thoughts? I should also note that I did an NSLog to see the results adding this code NSLog(#"VCF: %#", vcfPart2);. The log file showed each value in vcfPart2. So the code is calling each response.
NSDictionary *vcfPart2;
for (int i = 0; i < self.arrSlidshowImg.count; i++) {
NSData *vcfData = [self.arrSlidshowImg objectAtIndex:i];
vcfPart2 = [JFMailSender partWithType:PartTypeFilePart
Message:[vcfData base64EncodedStringWithOptions:0]
ContentType:#"image/jpeg"
ContentTransferEncoding:#"base64"
FileName:[NSString stringWithFormat:#"Individual_%d", i]];
}
mailSender.parts = [NSArray arrayWithObjects:plainPart2,vcfPart2,nil];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[mailSender sendMail];
});
});
The following comes from a simple reading of your code, and among other things I have not looked up JFMailSender:
You declare a variable vcfPart2 to hold a reference to a dictionary
You enter a loop
In the loop you assign a value to vcfPart2, this value is presumably a reference to a dictionary as you report no warnings
Step 3 is executed self.arrSlidshowImg.count times, ice per iteration of the loop
You exit the loop, at this point vcfPart2 will hold the last reference assigned to it, the previous self.arrSlidshowImg.count - 1 having been overwritten
Your concern appears to be that when you then use vcfPart2 it only references one dictionary - but that is all it can ever do, that is it's type.
Maybe you intended to create a dictionary in your loop and then add that dictionary to a mutable array so that after the loop the array contains all the dictionaries?
HTH

NSTreeController - Retrieving selected node

I added Book object in bookController (NSCreeController). Now i want to get stored Book object when i select the row.
- (IBAction)addClicked:(id)sender {
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
// NSTimeInterval is defined as double
NSUInteger indexArr[] = {0,0};
Book *obj = [[Book alloc] init];
NSString *dateString = [NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterNoStyle timeStyle:NSDateFormatterLongStyle];
obj.title = [NSString stringWithFormat:#"New %#",dateString];
obj.filename = [NSString stringWithFormat:#"%d",arc4random()%100000];
[self.booksController insertObject:obj atArrangedObjectIndexPath:[NSIndexPath indexPathWithIndexes:indexArr length:2]];
}
I concede there perhaps could be a better solution--
I am unfamiliar with how NSTreeController works, but I looked a the class reference and noticed that it has a content property, similar to an NSArrayController (Which I am familiar with grabbing specific objects from).
I believe that if the content property is actually of type of some kind of tree data structure, my answer here probably won't work. The class reference says this about content:
The value of this property can be an array of objects, or a
single root object. The default value is nil. This property is
observable using key-value observing.
So this is what I historically have done with the expected results:
NSString *predicateString = [NSString stringWithFormat:NEVER_TRANSLATE(#"(filename == %#) AND (title == %#)"), #"FILENAME_ARGUMENT_HERE", #"TITLE_ARGUMENT_HERE"];
NSArray *matchingObjects = [[self content] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:predicateString]];
Then simply calling -objectAtIndex: will grab you your object. Note that the NSArray will be empty if the object doesn't exist, and if you have duplicate objects, there will be multiple objects in the array.
I also searched for an answer to your question, and found this SO thread:
Given model object, how to find index path in NSTreeController?
It looks pretty promising if my solution doesn't work, the author just steps through the tree and does an isEqual comparison.
If you could (if it's not too much trouble), leave a comment here to let me know what works for you, I'm actually curious :)

nsdictionary copy vs mutable copy

I have
NSMutableDictionary *myDictionary = [[NSMutableDictionary alloc] init];
id dict = [myDictionary copy];
but is dict now just a regular NSDictionary? Or is it a copy of the NSMutableDictionary?
Also, is there any way to go from mutable to non-mutable?
There are two methods involved here; -copy and -mutableCopy.
If the class holds a distinction; -copy always creates an immutable copy; and -mutableCopy always creates a mutable copy.
If the class holds no distinction; -copy always creates a true copy.
So yes, dict is now an NSDictionary, containing the objects in the dictionary.

Is alloc+initWithString: same as copy?

Basically, the question is - are the following essentially the same?
NSString *value1 = ...;
NSString *value2 = [[NSString alloc] initWithString:value1];
and
NSString *value1 = ...;
NSString *value2 = [value1 copy];
Conceptually, yes. However, there is one difference: alloc always creates a new string, whereas copy may return the same string.
In particular, immutable objects, such as immutable strings, are likely respond to copy by returning themselves rather than creating and returning a copy. (After all, if you can't change anything about the original, why would you really need a copy?) Mutable strings will respond to it by creating and returning a copy, as you'd expect.
initWithString: is in the middle: It may release the receiver and return the string you gave it, similar to how copy may return the receiver. However, if that happens, it means you wasted the creation of the string you created with alloc. With copy, you may not need to create any additional objects at all.
About the only reason to use alloc and initWithString: is if you have your own subclass of NSString and want to make an instance of it from an existing string. copy won't use your desired subclass. Since subclassing NSString is practically never warranted in Cocoa, the same is true of using initWithString: (or stringWithString:).
So the bottom line is, just use copy (or mutableCopy). It's shorter, clearer about your intent, and can be faster.
Non-mutable strings are treated a bit special, compared to ordinary objects, so in this case, yes, the two operations are the same.
To wit:
NSString *str1 = #"string";
NSString *str2 = [str1 copy];
NSString *str3 = [[NSString alloc] initWithString: str1];
NSLog(#"str1: %p, str2: %p, str3: %p", str1, str2, str3);
Which gives me the following output:
str1: 0x108a960b0, str2: 0x108a960b0, str3: 0x108a960b0
Since the pointer addresses are the same, we are talking about the same object.

Mutable to immutable object

Is there a way to convert mutable object converted to immutable one in cocoa?
I have used NSMutableDictionary *mut=[[NSMutableDictionary alloc] initWithDictionary: copyItems:];
But this dictionary is used in many other places without the mutable thing.
Best Regards,
Subrat
NSDictionary dictionaryWithDictionary:
This may be an overly simplistic answer, but:
NSMutableDictionary * mutableDictionary = [NSMutableDictionary dictionaryWithStuff....];
NSDictionary * dictionary = mutableDictionary;
//from this point on, only use dictionary
While dictionary is technically (internally) mutable, you won't be able to access the set methods, since those are methods on NSMutableDictionary.
If I understand your question correctly (given your later comment), you want to convert an immutable copy of an mutable object back to being mutable again.
The problem seems to be this:
NSMutableString *foo = [NSMutableString stringWithString:#"a mutable object"];
NSMutableDictionary *dictionary1, *dictionary2;
dictionary1 = [NSMutableDictionary dictionaryWithObject:foo forKey:#"foo"];
dictionary2 = [[NSMutableDictionary alloc]initWithDictionary:dictionary1
copyItems: YES];
[[dictionary1 objectForKey:#"foo"] appendString:#", mutated"];
[[dictionary2 objectForKey:#"foo"] appendString:#", mutated"];
we can alter the object in dictionary1 just fine, but doing the same to dictionary2 throws an exception.
This is because although NSMutableDictionary's initWithDictionary:copyItems: method makes a mutable copy of the dictionary object, it makes immutable copies of its contents.
Classes that distinguish between immutable and immutable versions (such as cocoa's basic string, array & dictionary classes) are supposed to implement a copyWithZone: and mutableCopyWithZone: method. Since not all classes implement an mutableCopyWithZone: method, NSMutableDictionary's initWithDictionary:copyItems: method copies each of dictionary1's contents immutably, meaning that dictionary2 contains immutable objects.
You can make an mutable copy of an immutable object by sending it a mutableCopy message. But probably a better solution for you would be to add an initWithDictionary:mutableCopyItems: method to NSMutableDictionary with a category:
- (id) initWithDictionary:(NSDictionary *)otherDictionary
mutableCopyItems:(BOOL)flag
{
if (flag) {
self = [self init];
if (self)
for (id key in otherDictionary){
id object = [otherDictionary objectForKey:key];
if ([object respondsToSelector:#selector(mutableCopyWithZone:)])
[self setObject:[object mutableCopy] forKey:key];
else
[self setObject:[object copy] forKey:key];
}
}
else
self = [self initWithDictionary:otherDictionary];
return self;
}
Read these if you want to know the difference between copy, mutableCopy, copyWithZone: and mutableCopyWithZone:
http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Protocols/NSMutableCopying_Protocol/Reference/Reference.html#//apple_ref/occ/intf/NSMutableCopying
http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Protocols/NSCopying_Protocol/Reference/Reference.html#//apple_ref/occ/intf/NSCopying

Resources