0

I have a problem creating an NSMutableArray from an array of custom objects to an array of arrays of dictionaries and lone dictionaries (not in an array).

I currently have an NSMutableArray of Person objects. I iterate through these Person objects to create NSDictionaries of each. These dictionaries have three keys:

NSString *firstName;
NSString *lastName;
Person *id;

Some of these dictionaries will have the exact same first and last name, but the Person id will always be different. I'd like to convert this NSMutableArray of Person objects to an NSMutableArray of NSMutableArrays and NSDictionaries - the NSMutableArrays would be NSDictionaries that have the same firstName and lastName, and the lone NSDictionaries would be objects that do not have a firstName and lastName in common with any other Person in the original NSMutableArray.

A visual of the end result I desire is below:

                  |                    |--   NSDictionary (john, smith, id)
                  |                    |--   NSDictionary (john, smith, id)
                  |--  NSMutableArray  |--   NSDictionary (john, smith, id)
                  |                    |--   NSDictionary (john, smith, id)
                  |                    |--   NSDictionary (john, smith, id)
                  |
                  |                    |--   NSDictionary (bill, jones, id)
     people       |--  NSMutableArray  |--   NSDictionary (bill, jones, id)
(NSMutableArray)  |                    |--   NSDictionary (bill, jones, id)
                  |
                  |--  NSDictionary (mike, smith, id)
                  |
                  |--  NSDictionary (mike, jones, id)
                  |
                  |--  NSDictionary (john, jones, id)

My current implementation is below:

- (NSMutableArray*)organizeForSearchResults:(NSMutableArray*)c {

  NSMutableArray *returnArray = [NSMutableArray alloc] init];

  // iterate through every person in the incoming crate
  @autoreleasepool {
    for (Person *person in c) {

      // create a new person dictionary
      NSDictionary *personDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:
                                        person, @"person",
                                        person.firstName, @"firstName",
                                        record.lastName, @"lastName",
                                        nil];

      // if there are already objects in the top level c array
      if (returnArray.count > 0) {
        for (id object in returnArray) {

          // the object in returnArray is an array of record dictionaries
          if ([object isKindOfClass:[NSMutableArray class]]) {

            // if the first dictionary object in the array has the same firstName
            // and lastName, add the new person dict to the existing array of dicts
            NSString *firstName = [(NSDictionary*)object[0] objectForKey:@"firstName"];
            NSString *lastName = [(NSDictionary*)object[0] objectForKey:@"lastName"];
            if ([firstName isEqualToString:person.firstName] && [lastName isEqualToString:person.lastName]) {
              [object addObject:personDictionary];
            }
          }
          // if the object is a person dictionary, see if it matches the current dictionary
          else if ([object isKindOfClass:[NSDictionary class]]) {
            NSString *firstName = [(NSDictionary*)object objectForKey:@"firstName"];
            NSString *lastName = [(NSDictionary*)object objectForKey:@"lastName"];

            // if the current object matches the new person dict, we need to
            // create a new array with both the existing person dict and the
            // new person dict, add that array to returnArray, and remove the
            // old person dict
            if ([firstName isEqualToString:record.firstName] && [lastName isEqualToString:record.lastName]) {
              NSMutableArray *newArray = [[NSMutableArray alloc] initWithCapacity:2];
              [newArray addObject:object];
              [newArray addObject:personDictionary];
              [returnArray addObject:newArray];
              [returnArray removeObject:object];
            }
            // the new person dict does not match an existing array, so add it as
            // a single item in personArray
            else {
              [personArray addObject:personDictionary];
            }
          }
        }
      }
      // there are no objects in personArray, so just add this person dict
      else {
        [personArray addObject:personDictionary];
      }
    }
  }

  return personArray;
}

The implementation that I have so far is functionally correct, I believe, but I am obviously getting *** Collection <__NSArrayM:> was mutated while being enumerated. errors, as I am modifying the NSMutableArray while it is being enumerated. I feel that I am completely lost on how to implement this correctly - does anyone have another strategy on how to accomplish this?

1 Answer 1

0

The implementation that I have so far is functionally correct, I believe,

Sorry, but I think you will find it is not. However that is simple debugging and I'm sure you'll sort it out.

but I am obviously getting *** Collection <__NSArrayM:> was mutated while being enumerated. errors

Observation 1: You can take the problem as arising from mutating an array you are enumerating - that is processing with a for(a in b) style iteration. If you instead use a for(a; b; c) to iterate through the indices of the array you can mutate the array as you go, and you can also use the - replaceObjectAtIndex:withObject: method rather than an addObject:/removeObject: pair.

Making such a change to the iteration style is not required to fix your problem, but it might make sense to do so.

Observation 2: Your current iteration goes through the whole array, however for each execution of the iteration at most one change is made - you add an item to an array, or you replace a dictionary element with an array element. Once you've made the change you can terminate the loop - the remaining iterations will not do anything useful.

However if you are using a for in style those redundant iterations will do something which isn't useful - they cause the error you see as you attempt to continue the iteration after you have mutated the array.

The break statement can be used to terminate an iteration. If you use one of these after making a mutation control transfers to the statement after the iteration. This statement will work with either style of iteration.

That should be enough to get you past this issue, but you should check the rest of your logic - it may not yet do what you expect.

BTW You might wish to change to modern Objective-C syntax for dictionary construction and key-indexing, it will shorten your code and make it more readable.

HTH

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.