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"];
Related
Which to use when?
What if I am looping the dictionary and removing key-values from it, will the enumerator work for that? Documentation seem to answered that question:
If you use this method with instances of mutable subclasses of
NSDictionary, your code should not modify the entries during
enumeration. If you intend to modify the entries, use the allKeys
property to create a “snapshot” of the dictionary’s keys. Then use
this snapshot to traverse the entries, modifying them along the way.
Performance?, because people always want to know.
Iteration through NSDictionary could be achieved at least in two ways: using NSArray with [NSDictionary allKeys] or NSEnumerator.
NSArray *keyArray = [bigUglyDictionary allKeys];
int count = [keyArray count];
for (int i=0; i < count; i++) {
NSDictionary *tmp = [bigUglyDictionary objectForKey:[ keyArray objectAtIndex:i]];
}
NSEnumerator *enumerator = [bigUglyDictionary keyEnumerator];
id key;
while ((key = [enumerator nextObject])) {
NSDictionary *tmp = [bigUglyDictionary objectForKey:key];
}
Second way is a little bit faster, so if you work with huge dictionaries and have no need of array with their keys
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]]]
I need help with the following:
I have an NSArray with NSStrings, I want to loop thru these strings and find a matching string, when match is found the strings after this match will be extracted into an NSDictionary until a certain other match is hit.
Here is an example:
NSArray *array = #[#"Fruit",#"Apple",#"Vegtable",#"Tomato",#"Fruit",#"Banana",#"Vegtable",#"Cucumber"];
So I want to loop thru this array and split it in 2 arrays one for fruit and one for vegetable.
Anyone can help with the logic?
Thanks
This is probably the simplest way to solve the problem:
NSArray *array = #[#"Chair",#"Fruit",#"Apple",#"Orange",#"Vegetable",#"Tomato",#"Fruit",#"Banana",#"Vegetable",#"Cucumber"];
NSMutableArray *fruitArray = [NSMutableArray array];
NSMutableArray *vegetableArray = [NSMutableArray array];
NSMutableArray *currentTarget = nil;
for (NSString *item in array)
{
if ([item isEqualToString: #"Fruit"])
{
currentTarget = fruitArray;
}
else if ([item isEqualToString: #"Vegetable"])
{
currentTarget = vegetableArray;
}
else
{
[currentTarget addObject: item];
}
}
In one iteration over the array, you just keep adding items to a result array using a pointer to one of two result arrays according to the last occurrence of the #"Fruit" or #"Vegetable" string.
This algorithm ignores all items before the first occurrence of the #"Fruit" or #"Vegetable" string, because the currentTarget is initialized to nil, which ignores the addObject: messages. If you want different behaviour, just change the initialization.
You said you wanted the results in a NSDictionary, but didn't specify what should be the key. If you want one NSDictionary with two keys, Fruit and Vegetable, and values NSArrays containing the items, just use the arrays previously created:
NSDictionary *dict = #{ #"Fruit": fruitArray, #"Vegetable": vegetableArray };
PS: You have a typo in your example, Vegtable instead of Vegetable. I corrected it in my code, so keep it in mind.
If I completely understand you:
NSArray *array = #[#"Fruit",#"Apple",#"Vegtable",#"Tomato",#"Fruit",#"Banana",#"Vegtable",#"Cucumber"];
NSMutableArray *fruits = [NSMutableArray array];
NSMutableArray *vegtables = [NSMutableArray array];
for (NSInteger i = 0; i < array.count; ++i){
if ([array[i] isEqualToString:#"Fruit"]){
++i;
[fruits addObject:array[i]];
}
else if ([array[i] isEqualToString:#"Vegtable"]){
++i;
[vegtables addObject:array[i]];
}
}
Here is the situation:
I have a request on AFNetworking that retrieves me a JSON with an NSArray.
My goal is to mutate the NSDictionaries inside it. I already made a mutableCopy of the array, but I want to know if I can easily mutate all the content. Will I have to iterate through the array manually?
NSJSONSerialization has options to allow you to control the mutability of the resulting data structure. Just pass the appropriate ones (probably NSJSONReadingMutableContainers) and there you go.
You cannot mutate NSDictionary, just because only NSMutableDictionary has method setObject:forKey:
So you should create mutableCopy of each dictionary and empty mutable array. Then with a forloop fill that array. Your code should be so:
- (NSMutableArray *)mutatedArrayFromArray:(NSArray *)array
{
NSMutableArray *resultArray = [NSMutableArray new];
if([array count] > 0)
{
for(int i = 0; i < count; i++)
{
NSMutableDictionary *mutatedItem = [[array objectAtIndex:i] mutableCopy];
[resultArray addObject:mutatedItem];
[mutatedItem release]; // only with ARC disabled
}
}
return [result autorelease]; // if ARC enabled : return result;
}
I am familiar with getting a string count from a known array
int numberOfWords = [self.wordArray count];
but I have an unknown number of strings in an unknown number of arrays, all referenced by a dictionary. This works - good.
NSMutableDictionary *eqClasses = [[NSMutableDictionary alloc] init];
The arrays and strings are added at runtime (with help of this board):
NSMutableArray* array = [eqClasses objectForKey:wordPattern];
if(!array) {
// create new array and add to dictionary if wordPattern not found
array = [NSMutableArray array];
[eqClasses setObject:array forKey:wordPattern];
}
[array addObject:tempWordStr];
Now I need to iterate through the dictionary and get the array with the largest word count. Is there a way to scroll through all the arrays in the dictionary without using a key (I won't know all the word patterns as they are generated dynamically), AND once I find the array with the most words, get that array/value and key/wordpattern?
Well, there is a way to get all the keys within a dictionary:
NSArray *keyArray = [myDict allKeys];
And then you just go through the array and get the object for each key.
A fast enumeration should work nicely.
for (NSString *string in NSArray){
...
} //Assuming your keys are strings!
You can save each string to a temporary string, and when encountering a new string, compare to find the longer one. If it's longer, replace the old string with the longer one.
Hope this helped! ^_^
^_^
Okay, so now that you have an array full of all the keys in the dictionary,
you can iterate through the entire array and get the corresponding value (the string) for each key.
NSArray *keyArray = [myDict allKeys]; //This gets all the keys
NSString *tempString = #""; //This is the string you will save the longest string in. It gets updated when a longer string is found in the following loop.
for (NSString *string in keyArray){
NSString *stringFromCurrentKey = [myDict objectForKey:string];
if(stringFromCurrentKey.length > tempString.length){
tempString = stringFromCurrentKey;
}
} //By the end, you should be left with the longest string contained in tempString!
^_^ Hope this made sense and helped!
Try this code:
NSArray *largestArray = nil;
for (NSString *key in dictionary)
{
NSArray *array = [dictionary objectForKey:key];
if (array.count > largestArray.count) // largestArray.count is 0 if largestArray is nil
{
largestArray = array;
}
}