Managing arrays - mutating method sent to immutable object - ios

I try to populate existing array with other array:
if (!self.photos){
self.photos = [responseDictionary valueForKeyPath:#"data"];
} else {
NSArray *newPhotosFromFeed = [responseDictionary valueForKeyPath:#"data"];
[self.photos addObjectsFromArray:newPhotosFromFeed];
}
Compiler throw me an error - mutating method sent to immutable object .
Photos declared like this:
#property (nonatomic) NSMutableArray *photos;
Method declaration (from Apple.developer) is following:
- (void)addObjectsFromArray:(NSArray *)otherArray
So, how am i violate the rules?
I add NON-mutable array NSArray *newPhotosFromFeed to MUTABLE array self.photos.
So why am i get that error??

If this line is called first:
self.photos = [responseDictionary valueForKeyPath:#"data"];
then you will be storing an immutable array in a mutable property. The compiler can't tell it's wrong and the runtime doesn't check so you don't know it's happening. Then, later, when you call:
[self.photos addObjectsFromArray:newPhotosFromFeed];
you get the crash.
So, you should always ensure that the object being set to photos is mutable, the easiest way it by taking the mutableCopy as that will work on both mutable and immutable source objects:
self.photos = [[responseDictionary valueForKeyPath:#"data"] mutableCopy];

Here's the fix :
self.photos = [responseDictionary valueForKeyPath:#"data"].mutableCopy;
and
NSArray *newPhotosFromFeed = [responseDictionary valueForKeyPath:#"data"].mutableCopy;
You're attempting to set an NSMutableArray to a NSArray. That doesn't work without using mutableCopy. Use mutableCopy to create a mutable copy of the NSArray and that object becomes an NSMutableArray.

Related

Objective-c replaceObjectAtIndex not working because of array in array structure

i have an array and i get value with this codes:
NSString *status = [[Sweetresponse objectAtIndex:path.row] objectAtIndex:9];
i wanna change this value :
[[Sweetresponse objectAtIndex:path.row] replaceObjectAtIndex:9 withObject:#"liked"];
But its not working because this structure is array in array. how can i fix this problem ?
This is because the NSArray is immutable. You have to make it mutable.
NSMutableArray *mutableResponse = [Sweetresponse mutableCopy];
NSMutableArray *mutableResponseItems = [[mutableResponse objectAtIndex:path.row] mutableCopy];
// replace at the index
[mutableResponseItems replaceObjectAtIndex:9 withObject:#"liked"];
// create immutables and replace it with our new array
mutableResponse[path.row] = [mutableResponseItems copy];
// set `Sweetresponse` (assuming it is an NSArray)
Sweetresponse = [mutableResponse copy];
edit: I have no idea what Sweetresponse really is, here I'm assuming it's a NSArray
#trojanfoe has a point with parsing this response into custom objects would lead to cleaner code and also simpler object modification.

Add object to array within NSMutableDictionary loaded from NSUserDefaults

I have an array inside a NSMutableDictionary and i want to add objects to it. With my current approach I get an error saying that the array is immutable.
I think the problem lies when I´m saving the dictionary to NSUserDefaults. I´m retrieving the is it a NSDictionary but I am at the same time creating a new NSMutableDictionary with the contents.
However, the array seems to be immutable. How do I replace an array inside of a dictionary?
My dictionary looks like this:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:[NSNumber numberWithInt:0], nil];
NSDictionary *dict = #{
#"firstKey": #{
#"theArray":array,
}
};
NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:dict];
I am trying to add objects like this:
[[[mutDict objectForKey:#"firstKey"] objectForKey:#"theArray"] addObject:[NSNumber numberWithInt:5]];
I am able to add objects to the array inside mutDict before its saved to NSUserDefaults
The error message I get when I try to add to the array inside the dictionary after loading it from NSUserDefaults:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object'
Here's what the documentation for dictionaryForKey: says on NSUserDefaults:
Special Considerations
The returned dictionary and its contents are immutable, even if the values you >originally set were mutable.
So when you retrieve your dictionary from NSUserDefaults the dictionary itself and all of the collections inside it are immutable. You can make the top level dictionary mutable (which I assume you are doing), but that won't propagate down into the now immutable NSArrays which are values in the dictionary.
The only way to get around this is to go through the dictionary that's returned and replace the immutable NSArrays with their mutable counterparts. It might look something like this.
- (NSMutableDictionary *)deepMutableCopyOfDictionary:(NSDictionary *)dictionary
{
NSMutableDictionary *mutableDictionary = [dictionary mutableCopy];
for (id key in [mutableDictionary allKeys]) {
id value = mutableDictionary[key];
if ([value isKindOfClass:[NSDictionary class]]) {
// If the value is a dictionary make it mutable and call recursively
mutableDictionary[key] = [self deepMutableCopyOfDictionary:dictionary[key]];
}
else if ([value isKindOfClass:[NSArray class]]) {
// If the value is an array, make it mutable
mutableDictionary[key] = [(NSArray *)value mutableCopy];
}
}
return mutableDictionary;
}
To be honest though it sounds like you're using NSUserDefaults for something a lot more complex then it is intended for. If you want to persist complex data structures then you should look into something like Core Data, or if that looks to be a bit overkill take a look at NSKeyedArchiver.
You can add object directly to the array:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:[NSNumber numberWithInt:0], nil];
NSDictionary *dict = #{
#"firstKey": #{
#"theArray":array,
}
};
NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:dict];
//Since Objective-C objects are always passed by reference (using pointers) you can add object to the array
[array addObject:[NSNumber numberWithInt:55]];
Swift example of adding object to array which is part of a dictionary.
let arr = [0] // note that initial array may be immutable
var dict = ["fK": ["a":arr]] // even if "arr" will be mutable, but "dict" immutable
dict["fK"]!["a"]!.append(3) // this will not work. "dict" must be mutable
println(dict) //[fK: [a: [0, 3]]]
Another approach
var arr = [0] // initial array must be mutable
var dict = ["fK": ["a":arr]] // in both cases dictionary must be mutable
arr.append(3)
let newArr = arr
dict["fK"]!["a"]! = newArr // because we change it's content
println(dict) //[fK: [a: [0, 3]]]

Initializing NSMutableArray but the class of an initialized object displays as a NSArray

I have initialized a mutable array like NSMutableArray *mutableArray = [[NSMutableArray alloc]init];. I have printed the object class in NSLogs like NSLog(#"object class is %#",[mutableArray class]);. I have initialized a mutable array but the class of the object is displayed as __NSArrayM.
I try to add items to mutableArray then application is crashed. I don't know where is the problem. Please tell me why it is considered as a NSArray instead of NSMutableArray.
Thanks In Advace
It is as expected, the M in __NSArrayM means mutable.
NSArray / NSMutableArray is a class cluster and you should never expect to see the class directly printed as NSArray or NSMutableArray.
For Immutable array it returns
__NSArrayI // Here I is for Immutable
And for mutable it returns
__NSArrayM // Here M is for Mutable
May be you are assigning a non mutable array to this mutable array or you are inserting nil objects. Could you post the exception details.
[mutableArray insertObject:object atIndex:0]; (for first)
[mutableArray insertObject:object atIndex:[mutableArray count]]; (for last)
or [mutableArray addObject:object];
How are you adding them?

Empty NSMutableArray read from plist becomes immutable object

Write an empty NSMutableArray to disk, then read it back, it becomes an immutable object.
But, if the NSMutableArray is not empty, it won't. How to explain that?
here are the codes:
NSMutableArray *testItems1 = [NSMutableArray array];
NSMutableDictionary *testList1 = [NSMutableDictionary dictionaryWithObjectsAndKeys:testItems1, #"list_items", #"list1", #"list_name", nil];
NSMutableArray *testItems2 = [NSMutableArray arrayWithObjects:#"item11", #"item22", nil];
NSMutableDictionary *testList2 = [NSMutableDictionary dictionaryWithObjectsAndKeys:testItems2, #"list_items", #"list2", #"list_name", nil];
NSMutableArray *testLists = [NSMutableArray arrayWithObjects:testList1, testList2, nil];
[testLists writeToFile:#"/tmp/testLists" atomically:YES];
NSMutableArray *testReadLists = [NSMutableArray array];
[testReadLists setArray:[NSArray arrayWithContentsOfFile:#"/tmp/testLists"]];
NSMutableDictionary *testReadList = [testReadLists objectAtIndex:0];
NSMutableArray *testReadItems = [testReadList objectForKey:#"list_items"];
[testReadItems addObject:#"item3"]; // Crashes here: "*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object'"
These two lines of code:
NSMutableArray *testReadLists = [NSMutableArray array];
[testReadLists setArray:[NSArray arrayWithContentsOfFile:#"/tmp/testLists"]];
give you a mutable array of immutable objects. You can add and remove objects from testReadLists but everything you get from this array (originally loaded from the plist) will be immutable.
Update - I was about to post info about the solution but the answer by Vivek describes what I was going to say.
Haven't tested this myself, but you probably want to first read the plist into an NSData, and then get the actual array by doing +[NSPropertyListSerialization propertyListWithData:options:format:error:], specifying NSPropertyListMutableContainers in the options argument (apple doc here)
Note this should give you a full hierarchy of mutable containers (an NSMutableArray containing NSMutableDictionaries, and so on). If all you want is an NSMutableArray at one particular level in the hierarchy, then the other posted solution/comments would probably be a more appropriate solution.
Objects read straight from property lists are always immutable. You might create a mutable object from them, but the objects themselves are immutable. These lines are the problem:
NSMutableDictionary *testReadList = [testReadLists objectAtIndex:0];
NSMutableArray *testReadItems = [testReadList objectForKey:#"list_items"];
testReadList is the first object in the mutable array testReadLists, but that object itself is still immutable despite the fact that the declared type of testReadList is NSMutable*. Likewise, the object you get back from the objectForKey: call is an instance of NSArray even though you're assigning it to testReadItems, which is declared as NSMutableArray*. You can avoid the problem by simply making a mutable copy before you add new items:
NSMutableArray *testReadItems = [[testReadList objectForKey:#"list_items"] mutableCopy];

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?
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.
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];
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.
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.
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
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
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 =)
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.
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];

Resources