views:

445

answers:

4

This produces an immutable string object:

NSString* myStringA = @"A";  //CORRECTED FROM: NSMutableString* myStringA = @"A";

This produces a mutable string object:

NSMutableString* myStringB = [NSMutableString stringWithString:@"B"];

But both objects are reported as the same kind of object, "NSCFString":

NSLog(@"myStringA is type: %@, myStringB is type: %@", 
[myStringA class], [myStringB class]);

So what is distinguishing these objects internally, and how do I test for that, so that I can easily determine if a mystery string variable is immutable or mutable before doing something evil to it?

A: 

There's no (documented) way to determine if a string is mutable at runtime or not.

You would expect one of the following would work, but none of them work:

[[s class] isKindOfClass:[NSMutableString class]]; // always returns false
[s isMemberOfClass:[NSMutableString class]]; // always returns false
[s respondsToSelector:@selector(appendString)]; // always returns true

More info here, although it doesn't help you with the problem:

http://www.cocoabuilder.com/archive/cocoa/111173-mutability.html

Philippe Leybaert
Does not work: NSMutableString* lala = @"lala"; if ([lala isKindOfClass:[NSMutableString class]]) NSLog(@"Mutable class"); - shows log message, however object is immutable
Vladimir
Of course it doesn't work, because the code is wrong. **NSMutableString *lala = @"lala"** is illegal code.
Philippe Leybaert
Sorry guys -- NSMutableString* myStringA = @"A"; was a typo -- corrected now in original message.
StringSection
Yes, it is illegal, however -isKindOfClass does not allow you to check it - immutable object passes the check. Checking with -isMemberOfClass works though.
Vladimir
You're right, but -isKindOfClass doesn't work either. It returns false for myStringA and myStringB when tested against NSMutableString
Philippe Leybaert
I've distracted you with my typo... the main question is that (with properly-created variables), [myStringA class] and [myStringB class] both return "NSCFString" -- so how do I test to distinguish them? Philippe's code if ([myStringB isKindOfClass:[NSMutableString class]]) works and solves the practical issue. I'm not sure the syntax for Vladimir's isMemberOfClass. I'm still curious about how the difference between an immutable and mutable string is being represented internally, and if that can be detected directly (printed with NSLog).
StringSection
It seems there is no way to find out if the object is of type NSMutableString or NSString. See this discussion: http://www.cocoabuilder.com/archive/cocoa/111173-mutability.html
Philippe Leybaert
Yes I was incorrect two comments up -- the code I thought was working isn't. Thanks.
StringSection
A: 

If you want to check for debugging purposes the following code should work. Copy on immutable object is itself, while it's a true copy for mutable types, that's what the code is based on. Note that since it's calling copy it's slow, but should be fine for debugging. If you'd like to check for any other reasons than debugging see Rob answer (and forget about it).

BOOL isMutable(id object)
{
   id copy = [object copy];
   BOOL copyIsADifferentObject = (copy != object);
   [copy release];
   return copyIsADifferentObject;
}

Disclaimer: of course there is no guarantee that copy is equivalent with retain for immutable types. You can be sure that if isMutable returns NO then it's not mutable so the function should be probably named canBeMutable. In the real world however, it's a pretty safe assumption that immutable types (NSString,NSArray) will implement this optimization. There is a lot of code out including basic things like NSDictionary that expects fast copy from immutable types.

mfazekas
+6  A: 

The docs include a fairly long explanation on why Apple doesn't want you to do this and why they explicitly do not support it in Receiving Mutable Objects. The summary is:

So don’t make a decision on object mutability based on what introspection tells you about an object. Treat objects as mutable or not based on what you are handed at the API boundaries (that is, based on the return type). If you need to unambiguously mark an object as mutable or immutable when you pass it to clients, pass that information as a flag along with the object.

I find their NSView example the easiest to understand, and it illustrates a basic Cocoa problem. You have an NSMutableArray called "elements" that you want to expose as an array, but don't want callers to mess with. You have several options:

  1. Expose your NSMutableArray as an NSArray.
  2. Always make a non-mutable copy when requested
  3. Store elements as an NSArray and create a new array every time it mutates.

I've done all of these at various points. #1 is by far the simplest and fastest solution. It's also dangerous, since the array might mutate behind the caller's back. But Apple indicates it's what they do in some cases (note the warning for -subviews in NSView). I can confirm that while #2 and #3 are much safer, they can create major performance problems, which is probably why Apple has chosen not to use them on oft-accessed members like -subviews.

The upshot of all of this is that if you use #1, then introspection will mislead you. You have an NSMutableArray cast as an NSArray, and introspection will indicate that it's mutable (introspection has no way to know otherwise). But you must not mutate it. Only the compile-time type check can tell you that, and so it's the only thing you can trust.

The fix for this would be some kind of fast copy-on-write immutable version of a mutable data structure. That way #2 could possibly be done with decent performance. I can imagine changes to the NSArray cluster that would allow this, but it doesn't exist in Cocoa today (and could impact NSArray performance in the normal case, making it a non-starter). Even if we had it, there's probably too much code out there that relies on the current behavior to ever allow mutability introspection to be trusted.

Rob Napier
You can solve the performance problem with #3 by implementing and exposing mutating array accessors (http://developer.apple.com/documentation/Cocoa/Conceptual/ModelObjects/Articles/moAccessorMethods.html) and using them in other classes. That way, direct access to the mutable array remains private, but you're not creating and throwing away temporary arrays.
Peter Hosey
@PH This is true and I should have covered the KVC solution. The problem it poses is that there are many cases where the caller really needs a collection (usually to pass to something else that demands one). I've often solved this by exposing something like elementEnumerator, which the caller can turn into a snapshot if he wishes. The trouble with this approach is the amount of extra code required both for the caller and the called. It is enlightening that Apple, even in the new code for UIView, chose to just expose the NSMutableArray "subviews" as an NSArray and not provide full KVC.
Rob Napier
Hm? I'm not clear on what problem you're talking about or what your solution is. Blog post and/or pastebin?
Peter Hosey
A: 

(StringSection says: I don't have comment privileges anymore...)

@mfazekas -- StringSection says: that's clever, but have I read somewhere that you can't rely on how immutable objects are copied -- sometimes the runtime may just increment the retain count on the original object, sometimes an actual copy may be made?

@Rob Napier -- StringSection says: are you kind of turning this from a question (answer, no) into a "discussion forum" : ) I'd certainly like to have this introspection for troubleshooting -- and that could be implemented as the compiler at least is aware at compile time of whether a variable was created as immutable or mutable. If there is some secret runtime attribute for immutability that Apple could expose for us, I'm sure there are some legitimate situations where you would want to test that you aren't trying to assign an immutable object to an existing mutable string variable that is coming in dynamically from somewhere.

StringSection
You're correct that you should not rely on the implementation details of -copy. As far as answers and discussions, I think all the best answers (at least in the Cocoa community) include elaboration, so sure. :) The compiler doesn't know about mutability in ObjC. It isn't a deep concept in ObjC like it is in C++; it's just convention. We'd have to go back and decorate everything correctly with 'const' and it's too late for that. But of course there's a secret runtime attribute: __CFArrayGetType(arr) != __kCFArrayImmutable http://www.opensource.apple.com/source/CF/CF-550.13/CFArray.c
Rob Napier
BTW, you seem to have accidentally created two accounts. http://stackoverflow.com/users/253835/stringsection http://stackoverflow.com/users/254560/stringsection
Rob Napier
Of course copy can be a full copy for immutable types, but that's very unlikely - i've added a disclaimer about that to my answer.
mfazekas