Domanda

I read in Cocoa and Objective C: Up and Running that -copy will always return an immutable object and -mutableCopy will always return a mutable object:

It’s important to know that calling -copy on a mutable object returns an immutable version. If you want to copy a mutable object and maintain mutability in the new version, you must call -mutableCopy on the original. This is useful, though, because if you want to “freeze” a mutable object, you can just call -copy on it.

So I have something like this:

NSMutableURLRequest *req = [[NSMutableURLRequest alloc] init];
NSLog( @"%@", [req className] );               // NSMutableURLRequest
NSLog( @"%@", [[req copy] className] );        // NSMutableURLRequest
NSLog( @"%@", [[req mutableCopy] className] ); // NSMutableURLRequest

According to this previous answer:

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.

This seems to be somewhat isolated to NSURLRequest, since NSArray acts as intended:

NSArray *arr = [[NSMutableArray alloc] init];
NSLog( @"%@", [arr className] );                 // __NSArrayM
NSLog( @"%@", [[arr copy] className] );          // __NSAraryI
NSLog( @"%@", [[array mutableCopy] className] ); // __NSArrayM

So...

  1. When does -copy return an immutable object (as expected) and when does it return a mutable object?
  2. How do I achieve the intended effect of getting a "frozen" copy of a mutable object that refuses to be "frozen"?
È stato utile?

Soluzione

I think you've uncovered a great rift between documentation and reality.

The NSCopying protocol documentation claims:

The copy returned is immutable if the consideration “immutable vs. mutable” applies to the receiving object; otherwise the exact nature of the copy is determined by the class.

But this is clearly wrong in some cases, as you've shown in your examples (and I've sent feedback to them about this via that documentation page).

But(#2) in my opinion, it doesn't actually matter and you shouldn't care.

The point of -copy is that it will return an object you can use with the guarantee that it will behave independently of the original. This means if you have a mutable object, -copy it, and change the original object, the copy will not see the effect. (In some cases, I think this means that -copy can be optimized to do nothing, because if the object is immutable it can't be changed in the first place. I may be wrong about this. (I'm now wondering what the implications are for dictionary keys because of this, but that's a separate topic...))

As you've seen, in some cases the new object may actually be of a mutable class (even if the documentation tells us it won't). But as long as you don't rely on it being mutable (why would you?), it doesn't matter.

What should you do? Always treat the result of -copy as immutable, simple as that.

Altri suggerimenti

1) When does -copy return an immutable object (as expected) and when does it return a mutable object?

you should always treat it as the immutable variant. the mutable interface of the returned type should not be used. apart from optimizations, the answer should not matter and should be considered an implementation detail unless documented.

the obvious case: for a number of reasons, objc class clusters and class designs can be complex. returning a mutable copy could simply be for convenience.

2) How do I achieve the intended effect of getting a "frozen" copy of a mutable object that refuses to be "frozen"?

using the copy constructor of the immutable class is a good way (similar to St3fan's answer). like copy, it's not a guarantee.

the only reason i can think of as to why you would want to enforce this behaviour is for performance or to enforce a restricted interface (unless it's academic). if you want performance or a restricted interface, then you can simply encapsulate an instance of the type which copies on creation and exposes only the immutable interface. then you implement copy via retain (if that's your intent).

alternatively, you can write your own subclass and implement your own variant of copy.

final resort: many of the cocoa mutable/immutable classes are purely interface - you could write your own subclass if you need to ensure a particular behaviour -- but that's quite unusual.

perhaps a better description of why this should be enforced would be good - the existing implementations work just fine for the vast majority of developers/uses.

Bear in mind that there is not one copy implementation -- each class implements its own. And, as we all know, the implementation of the Objective C runtime is a little "loosey goosey" in spots. So I think we can say that mostly copy returns an immutable version, but some exceptions exist.

(BTW, what does this do:

NSArray *arr = [[NSMutable array] init];

?)

The best way to turn an object into an mutable one is to use the mutable 'constructor'. Like for example:

NSArray* array = ...;
NSMutableArray* mutableArray = [NSMutableArray arrayWithArray: array];

Copy is used to make a copy of an object. Not to change it's mutability.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top