문제

I am attempting to load data from a text file on a background thread with a completion handler that will write out a plist after I munge the data using a regular expression. I get the "Collection was mutated while being enumerated" error but I am not sure why it's thrown in this case. I am using blocks and a background thread. So I assume that I've made a logic error but I cannot understand why:

  1. Counts of the mutable array are inconsistent at the break point (where error occurs)
  2. At one point, the process did complete correctly (populated the data.plist file)
  3. When I attempt to use a copy of the mutable array before archiving it I get an "attempt to insert nil object" error, but the original nsmutablearray count seems valid at this point (see update)

The thing that's interesting is that because of the inconsistent counts and (at one point) successful completion I think that my background processing may be the culprit.

Here's a partial of the init method of the DataManager class:

if ( !abcMutableArray )
        abcMutableArray = [[NSMutableArray alloc] init];

    NSFileManager *fileManager = [NSFileManager defaultManager];

    // If the plist file doesn't exist in the Documents Folder, create it from the text file
    if ( ![fileManager fileExistsAtPath:[self filePath]] ) {

        [self createDataFile];

    } 

And here are the helper methods

- (void) createDataFile
{
    __weak DataManager* weakSelf = self;

    [weakSelf loadData:^(BOOL completed){
        if(completed)
        {

            //Next line produces **Collection <__NSArrayM: 0x8fa28c0> was mutated while being enumerated**
            NSData* data = [NSKeyedArchiver archivedDataWithRootObject:[weakSelf abcMutableArray]];
            [data writeToFile:[weakSelf filePath] atomically:YES];
        }
    }];

}

This method performs the load and when finished, passes the completion call back to the caller

- (void) loadData:(void (^)(BOOL))completed
{

    __weak DataManager* weakSelf = self;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{

        NSError *localError = NULL;
        NSStringEncoding fileEncoding = NSUTF8StringEncoding;
        NSURL* fileURL = [[NSBundle mainBundle] URLForResource:@"data" withExtension:@"txt"];
        NSString* fh= [NSString stringWithContentsOfURL:fileURL usedEncoding:&fileEncoding error:&localError];

        for (NSString *line in [fh componentsSeparatedByString:@"\n"]) {

            if ( ![line hasPrefix:@"#"] )
            {
            //      [self parseLine:line];

                //parsing routine
                NSError *error = NULL;

                Thing* t = [[Thing alloc]init];

                NSString* pattern = @"(^.*)\\s(.*)\\s\\[(.*)\\]\\s\\/(.*)\\;\\s(.*)";

                NSRegularExpression *regEx = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error];

                NSArray *matches = [regEx matchesInString:line
                                                  options:0
                                                    range:NSMakeRange(0, [line length])];

                for (NSTextCheckingResult *match in matches) {

                    if ([match numberOfRanges] == 6) {

                        //The range at index 0 contains the entire string.
                        thing.a = (NSString*)[line substringWithRange:[match rangeAtIndex:1]];

                        thing.b = (NSString*)[line substringWithRange:[match rangeAtIndex:2]];

                        thing.c = (NSString*)[line substringWithRange:[match rangeAtIndex:3]];

                        thing.d = (NSString*)[line substringWithRange:[match rangeAtIndex:4]];

                        thing.e = (NSString*)[[[line substringWithRange:[match rangeAtIndex:5]]stringByReplacingOccurrencesOfString:@"/" withString:@". "] stringByReplacingOccurrencesOfString:@".." withString:@"."];

                        [[weakSelf abcMutableArray] addObject:thing];

                    } //end-if match

                } //end-for matches

            } //end-if line

        } //end-for lines

        //file load data process is completed
        completed(YES);
    });

}

Update

When attempting to add the copy method to the code as suggested:

NSData* data = [NSKeyedArchiver archivedDataWithRootObject:[[weakSelf abcMutableArray]copy]];

I get the following error at the same break point.

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[12]'

Note than when I break at the NSData line above I can see that the abcMutableArray contains a variable number of objects (different number of objects during each cycle, which leads me to believe that my background thread has not completed processing).

도움이 되었습니까?

해결책

Change:

NSData* data = [NSKeyedArchiver archivedDataWithRootObject:[weakSelf abcMutableArray]];

To:

NSData* data = [NSKeyedArchiver archivedDataWithRootObject:[[weakSelf abcMutableArray] copy]];

That will pass a copy of the array in, so that it will be impossible to mutate it.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top