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]]]
Related
I have an NSMutableArray called myMutbaleArray that looks like this when I log it
2015-12-08 17:04:21.679 APP[3342:573379] (
{
id = 19;
"num_test" = 100000;
url = "http://fsad.com";
},
{
id = 20;
"num_test" = 100001;
url = "http://teeeet.com";
}
)
And I want to add an object that looks like this
{
id = 21;
"num" = 100002;
url = "http://example.com";
}
So I am trying this
[myMutbaleArray addObject:#{ #"id":#"23",#"num_test":#"100000", #"url":mainDict[#"website_url"],#"website_name":#"website"}];
But when I do this I get
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object'
I initialize the array like this
#interface {
NSMutableArray *socailArray;
}
//inside connectionDidFinishLoading
socailArray = [dataDict valueForKey:#"socail"];
Why can I add another dictionary to the MutableArray?
Thanks
If you see this, your array is actually not a mutable array. Here is the hint:
-[__NSCFArray insertObject:atIndex:]
^^^^^^^^^^^
The object is of type __NSCFArray, which is an internal counterpart of NSArray.
Even if you declare your variable as NSMutableArray the pointer can point to an object of any type (event for example NSRegularExpression). Important is, how it is created.
This happens to most people if they serialise an array either using NSUserDefaults, NSJSONSerialization or what ever.
The key is to create a mutable copy when the array gets deserialised using
-[NSArray mutableCopy]
Note that this is not deep-copy. This means an array contained in the root array will not be mutable copied and needs to be replaced separately.
Deep copying can be achieved using this:
// array
NSArray *originalArray = #[#"a", #"b", #[#1, #2]];
NSMutableArray *mutableArray = (NSMutableArray *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFArrayRef)originalArray, kCFPropertyListMutableContainers);
// dictionary
NSDictionary *originalDictionary = #{#"a": #"b", #"c": #[#{#"abc": #123}]};
NSMutableDictionary *mutableDictionary = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDictionary, kCFPropertyListMutableContainers);
You should change init to:
//inside connectionDidFinishLoading
socailArray = [NSMutableArray arrayWithArray:[dataDict valueForKey:#"socail"]];
Because: [dataDict valueForKey:#"socail"] is a NSArray.
With socailArray = [dataDict valueForKey:#"socail"];, the type of [dataDict valueForKey:#"socail"] is NSArray, so it auto cast socailArray into NSArray, that's why you can not insert thing into this.
To avoid this, you must be hold socailArray as NSMutableArray using:
socailArray = [[dataDict valueForKey:#"socail"] mutableCopy];
Hope this could help.
i'm trying to achieve the following structure:
NSMutableDictionary *dict = [#{} mutableCopy];
NSDictionary *key1 = #{#"id_format": #(1), #"date": #"2014-08-01"};
NSDictionary *key2 = #{#"id_format": #(2), #"date": #"2014-08-02"};
// This runs perfect and can be checked in llvm debugger
// data1 & data2 are NSArray that contain several NSDictionary
[dict setObject:data1 forKey:key1];
[dict setObject:data2 forKey:key2];
// Later, if i try to access dict using another key, returns empty NSArray
NSDictionary *testKey = #{#"id_format": #(1), #"date": #"2014-08-01"}; // Note it's equal to "key1"
for(NSDictionary *dictData in dict[testKey]){
// dictData is empty NSArray
}
// OR
for(NSDictionary *dictData in [dict objectForKey:testKey]){
// dictData is empty NSArray
}
So the question is if is there possible to use NSDictionary as key, or not.
An object can be used as a key if it conforms to NSCopying, and should implement hash and isEqual: to compare by value rather than by identity.
Dictionaries follow the array convention of returning [self count] for hash. So it's a pretty bad hash but it's technically valid. It means your outer dictionary will end up doing what is effectively a linear search but it'll work.
Dictionaries implement and correctly respond to isEqual:. They also implement NSCopying.
Therefore you can use a dictionary as a dictionary key.
In my application i am getting data from the server.i parsed the data and added to individual arrays. Here i am having 2 arrays.
For example
Array A : #"1",#"2",#"3",#"2",#"3",#"4",etc..
Array B : #"A",#"B",#"C",#"D",#"E",#"F",etc..
Now i want to create a Dictionary with Array A as keys and Array B as Values.
i am trying to create Dictionary like this:
dataDict = [NSDictionary dictionaryWithObjects:B forKeys:A];
But it is giving only single value for a single Key. here how can i store multiple values for a single key.
For Different keys its working. But my problem is Storing multiple values for single key.
You can't store multiple values for a single key directly -- dictionaries can only have one value per key. What you can do is store an array as the value. So, you could create a mutable dictionary and add the keys and values one at a time. Make the values all mutable arrays, and check for an existing value for the given key before setting it. If you find one, add the new value to the array.
Try this,
Assuming dataDict is a NSMutableDictionary and initialised.
- (void)addValueInDataDict:(id)value forKey:(NSString *)key {
if ([dataDict objectForKey:key] != nil) {
//Already exist a value for the key
id object = [dataDict objectForKey:key];
NSMutableArray *objectArray;
if ([object isKindOfClass:[NSMutableArray class]]) {
objectArray = (NSMutableArray *)object;
} else {
NSMutableArray *objectArray = [[NSMutableArray alloc] init];
}
[objectArray addObject:value];
[dataDict setObject:objectArray forKey:key];
} else {
//No value for the key
[dataDict setObject:value forKey:key];
}
}
I have the following code:
for (NSMutableDictionary *aDict in array)
{
// do stuff
[aDict setObject:myTitle forKey:#"title"];
}
My question is, if the array is filled with NSDictionary objects, will this for loop code as written automatically convert them into NSMutableDictionary objects?
Or do I need to do something more specific here to ensure that I don't get an unrecognized selector sent to instance error on setObject:forKey: in the loop?
Currently that will give you the error you mentioned. Whilst the loop is setup with mutable dictionaries, the underlying object is still immutable. You'd need to create a new dictionary out of it. Try this
NSMutableArray *newArray = [NSMutableArray array];
for (NSDictionary *aDict in array)
{
NSMutableDictionary *mutable = [aDict mutableCopy];
// do stuff
[mutable setObject:myTitle forKey:#"title"];
[newArray addObject:mutable];
}
No it will not automatically convert them. You have to do that yourself. You'll definitely get the unrecognized selector sent to instance exception.
based on the fact that you cannot edit mutable Collections while enumerating them, this is the best solution i could come up with to edit a Array of NSMutableDictionaries:
__block NSMutableDictionary *tempDict = [NSMutableDictionary dictionaryWithCapacity:1];
__block NSUInteger idx;
[_myArray enumerateObjectsUsingBlock:^(NSMutableDictionary* obj,
NSUInteger indx, BOOL *stop) {
if (// some condition is met) {
tempDict = [obj mutableCopy];
idx = indx;
}
}];
[tempDict setObject:[NSNumber numberWithInt:thisQueryResults] forKey:#"resultsNum"];
[_myArray replaceObjectAtIndex:idx withObject:rowSelected];
this seems way too complicated (even for a language like obj-c).. and since it's involving two data types (NSMutableArray and NSMutableDictionary), it doesn't seem like I can cleanly put them into a category.. advice?
update: one comment asked why do I create a mutablecopy (as opposed to just a copy.. since it's copying a mutable object)..
suppose I just used copy.. if i put a break on tempDict this is what I get:
// tempDict = [obj copy]
po tempDict
$0 = 0x0b28cc10 <__NSArrayI 0xb28cc10>(
1
)
// tempDict = [obj mutableCopy]
po tempDict
$0 = 0x0b28cc10 <__NSArrayM 0xb28cc10>( //notice the M in __NSArrayM as opposed to I above
1
)
in case of copy.. if I follow it with a line like this:
[tempDict setObject:[NSNumber numberWithInt:thisQueryResults] forKey:#"resultsNum"];
I get this error:
[__NSDictionaryI setObject:forKey:]: unrecognized selector sent to instance 0xb245100
I get the same above error with this code:
for (NSUInteger idx = 0; idx < [_myMutableArray count]; idx++) {
NSMutableDictionary* myMutableDict = _myMutableArray[idx];
[myMutableDict setObject:obj forKey:key];
}
update 2:
the origin of the problem was instantiating non mutable arrays and dictionaries.. I'm new to the whole new obj-c literals, so I didn't know that to create a NSMutableArray and NSDictionary, you gotta do this, respectively:
[#[..] mutableCopy]
[#{..} mutableCopy]
So in your case, I don't quite follow why you call tempDict = [obj mutableCopy]; when from the conditions you write the dictionary is already writable.
You can use several tricks. Like using
for (NSUInteger idx = 0; idx < _myArray.count: idx++_ {
NSMutableDictionary *obj = _myArray[idx];
// modify
}
For NSDictionaries you can get allKeys and iterate over that copy. This is a bit slower than using fast enumeration, but still faster than doing workarounds like boxing integers to replace later :)
In your case you are NOT modifying the array at all only the dictionaries within the array. There are no contstraits on how you modify the objects within the array. Here is a bit of equivalent code:
for (NSMutableDictionary *dict in _myArray) {
if (someCondition)
[dict setObject:[NSNumber numberWithInt:thisQueryResults] forKey:#"resultsNum"]
}
You would have a problem if you absolutely needed to replace the object in your array. In that case, if the array is not huge I would suggest the same as #Markus. Iterate over a copy and modify the original.
Maybe you can use KVC and do :
NSArray<NSMutableDictionary *> *result = [[_myArray filteredArrayUsingPredicate:[NSPredicate withFormat:#"{YOUR CONDITION}"]] valueForKey:#"mutableCopy"];