I have extended both NSString and NSMutableString with some convenience methods using categories. These added methods have the same name, but have different implementations. For e.g., I have implemented the ruby "strip" function that removes space characters at the endpoints for both but for NSString it returns a new string, and for NSMutableString it uses the "deleteCharactersInRange" to strip the existing string and return it (like the ruby strip!).
Here's the typical header:
@interface NSString (Extensions)
-(NSString *)strip;
@end
and
@interface NSMutableString (Extensions)
-(void)strip;
@end
The problem is that when I declare NSString *s and run [s strip], it tries to run the NSMutableString version and raises an extension.
NSString *s = @" This is a simple string ";
NSLog([s strip]);
fails with:
Terminating app due to uncaught
exception
'NSInvalidArgumentException', reason:
'Attempt to mutate immutable object
with deleteCharactersInRange:'
Best Answer
You've been bitten by an implementation detail: Some NSString objects are instances of a subclass of NSMutableString, with only a private flag controlling whether the object is mutable or not.
Here's a test app:
If you compile and run this on Leopard (at least), you'll get this output:
As I said, the object has a private flag controlling whether it's mutable or not. Since I went through NSString and not NSMutableString, this object is not mutable. If you try to mutate it, like this:
you'll get (1) a well-deserved warning (which one could silence with a cast, but that would be a bad idea) and (2) the same exception you got in your own application.
The solution I suggest is to wrap your mutating strip in a
@try
block, and call up to your NSString implementation (return [super strip]
) in the@catch
block.Also, I wouldn't recommend giving the method different return types. I would make the mutating one return
self
, likeretain
andautorelease
do. Then, you can always do this:without worrying about whether
unstripped
is a mutable string or not. In fact, this example makes a good case that you should remove the mutatingstrip
entirely, or rename the copyingstrip
tostringByStripping
or something (by analogy withreplaceOccurrencesOfString:…
andstringByReplacingOccurrencesOfString:…
).