J.R.
J.R.

Reputation: 6049

Why can you sometimes cast a NSArray to NSMutableArray, and sometimes you can't?

Specifically:

self.words = (NSMutableArray*)[self.text componentsSeparatedByString:@" "];

works fine so long as there are separators. I see that the method returns the original string wrapped in an NSArray if there isn't, though. This single element NSArray stubbornly refuses to be cast as an NSMutableArray.

But, when I do:

self.words = [NSMutableArray arrayWithArray:self.words];

It works just fine.

Is there something I'm missing here? Is it bad practice to cast from NSArray to NSMutableArray?

Upvotes: 5

Views: 969

Answers (9)

Oleksandr Matrosov
Oleksandr Matrosov

Reputation: 27133

Use second variant in all cases because it is right solution and more clearly for users who will support your code:

NSArray *array = [self.text componentsSeparatedByString:@" "];
self.words = [NSMutableArray arrayWithArray:array];

Never do this one:

self.words = [NSMutableArray arrayWithArray:self.words];

It will totally confuse all devs =)

Upvotes: 1

foundry
foundry

Reputation: 31745

NSString* string1 = @"this thing";
NSString* string2 = @"this";

NSMutableArray* array1;
NSMutableArray* array2;
array1 = (NSMutableArray*)[string1 componentsSeparatedByString:@" "];
array2 = (NSMutableArray*)[string2 componentsSeparatedByString:@" "];

[array1 addObject:string1]; //(A) 
[array2 addObject:string1]; //(B)

This will break at (B) the last line with :
-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x1702257a0

But will not break at (A)1

An object that is publicly declared as immutable may have been privately created as mutable. In this case, if you cast from the immutable public type to the private mutable type, everything will work fine. Where the object was not created as mutable, such a cast will not get you what you want.

In the case of componentsSerparatedByString, this suggests that the method implementation creates a mutable array only if it needs to - i.e. if it has to add more than one object to the array. If it only finds one object, you get an NSArray, if it finds more than one, you get an NSMutableArray. This is an implementation detail that is deliberately hidden from you as the user.

The interface tells you to expect an NSArray in all cases, and in all cases this will work.

You should not rely on such details to get you what you want. Stick to the public API, that's what it is for.

1 rather, as Bryan Chen points out, it may not break now but could well do so in the future

Upvotes: 2

Andrey Chernukha
Andrey Chernukha

Reputation: 21808

When you do that:

self.words = (NSMutableArray*)[self.text componentsSeparatedByString:@" "];

the array still remains immutable and if you send it a message from NSMutableArray class it will crash. The trick (NSMutableArray*) is only good to make the compiler happy.

In the second case:

self.words = [NSMutableArray arrayWithArray:self.words];

you do not CAST, you CREATE a new array object.

And of course you don't need to cast the arrays this way. NSMutableArray object IS already NSArray just like any object of a derived class is an object of a base class at the same time

Upvotes: 1

debris
debris

Reputation: 503

Casting does nothing with an object. example:

NSString *mouse = (id)@[@"mouse"];

it will compile, but variable mouse is not NSString. it is NSArray. you can check it simply by writing

po mouse

in console.

The only way to create mutable copy of an object is to call 'mutableCopy' method on it:

NSArray *array = @[@"a"];
NSMutableArray *mutableCopy = [array mutableCopy];

Upvotes: 0

Duncan C
Duncan C

Reputation: 131408

You are confused.

This code:

self.words = (NSMutableArray*)[self.text componentsSeparatedByString:@" "];

...is wrong, and is setting you up for a crash later on. Casting just tells the compiler "trust me, I know what I'm doing." It does not change the type of the underlying object. The method componentsSeparatedByString returns an NSArray, not a mutable array. If you then try to mutate the resulting array, you will crash with an unrecognized selector message. With the cast, the compiler trusts you that your object will really be a mutable array at runtime, but it will not be.

This would crash:

self.words = (NSMutableArray*)[self.text componentsSeparatedByString:@" "];
[self.words addObject: @"new"];

However, this code:

self.words = [[self.text componentsSeparatedByString:@" "] mutableCopy];
[self.words addObject: @"new"];

...does the right thing. It doesn't cast a pointer, it is a method call to a method that takes an NSArray as input and returns a mutable array with the same contents. Thus the second line will work because the first line takes the immutable array it gets back from componentsSeparatedByString and uses it to create a mutable array.

Upvotes: 4

Catfish_Man
Catfish_Man

Reputation: 41801

Your misconception seems to be about the nature of casting. Casting doesn't change the object, it just tells the compiler to pretend that it's an object of that type. If the object really isn't an NSMutableArray, casting is not expected to make it become one.

Upvotes: 2

Tom Harrington
Tom Harrington

Reputation: 70946

Is it bad practice to cast from NSArray to NSMutableArray?

Yes. Bordering on nonsensical, really.

Typecasting does not change the object in any way at all, it just tells the compiler that it should regard the object as if it were an instance of the new type. At run time though, the object stays exactly the same. You may cast an NSArray to an NSMutableArray but it doesn't make the object transform into an NSMutableArray. It's still the same object of the same type.

Upvotes: 2

Gavin
Gavin

Reputation: 8200

In the first case, the componentsSeparatedByString: method is specifically returning an NSArray, which can't just be cast to the mutable type. If you wanted that to be mutable, you would have to do this:

self.words = [[self.text componentsSeparatedByString:@" "] mutableCopy];

The second one is calling the arrayWithArray: method on the NSMutableArray class, meaning it is making an instance of NSMutableArray. That's why it works. You can cast an NSMutableArray to an NSArray, but not the other way around, because an NSMutableArray is a subclass of NSArray.

Upvotes: 0

Bryan Chen
Bryan Chen

Reputation: 46588

It is bad practice to cast from NSArray to NSMutableArray. It may works if you are lucky because the array are constructed using NSMutableArray, but you can't rely on it.

If you want NSMutableArray from NSArray, use mutableCopy

self.words = [[self.text componentsSeparatedByString:@" "] mutableCopy];

Upvotes: 2

Related Questions