处理后台线程时出现“集合发生变异...”的异常情况

Posted

技术标签:

【中文标题】处理后台线程时出现“集合发生变异...”的异常情况【英文标题】:Unusual case of "collection was mutated..." while processing background thread 【发布时间】:2014-03-26 17:50:56 【问题描述】:

我正在尝试使用完成处理程序从后台线程上的文本文件加载数据,该完成处理程序将在我使用正则表达式处理数据后写出一个 plist。我收到“枚举时集合发生突变”错误,但我不确定为什么在这种情况下会抛出它。我正在使用块和后台线程。所以我假设我犯了一个逻辑错误,但我不明白为什么:

    可变数组的计数在断点处不一致(发生错误的地方) 在某一时刻,该过程确实正确完成(填充了 data.plist 文件) 当我在归档之前尝试使用可变数组的副本时,我 收到“尝试插入 nil 对象”错误,但此时原始 nsmutablearray 计数似乎有效(请参阅更新)

有趣的是,由于计数不一致和(在某一时刻)成功完成,我认为我的后台处理可能是罪魁祸首。

下面是 DataManager 类的 init 方法的一部分:

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];

     

这里是辅助方法

- (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];
        
    ];


此方法执行加载,完成后将完成调用传回给调用者

- (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);
    );


更新

尝试按照建议将复制方法添加到代码中时:

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

我在同一断点处收到以下错误。

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

请注意,当我在上面的 NSData 行中断时,我可以看到 abcMutableArray 包含可变数量的对象(每个周期中的对象数量不同,这使我相信我的后台线程尚未完成处理)。

【问题讨论】:

【参考方案1】:

变化:

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

收件人:

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

这将传入数组的副本,因此不可能对其进行变异。

【讨论】:

好建议,但是,当反复测试时,有时会抛出 *** Terminating app due to unaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: 尝试插入来自对象[2443]'的零对象' |我的测试步骤是执行,见data.plist,删除data.plist,执行|想知道我的后台任务有时是否未完成? 我最终使用了这种方法,我确定我的其他问题与线程有关。

以上是关于处理后台线程时出现“集合发生变异...”的异常情况的主要内容,如果未能解决你的问题,请参考以下文章

关闭表单时出现异常(线程+调用)

Java多线程问题--线程运行时出现异常的解决办法

对象转换json对象中存在Date,json转换对象时出现转换异常情况

对象转换json对象中存在Date,json转换对象时出现转换异常情况

在矩阵乘法中使用 C++2011 线程而不是 OpenMP 时出现异常加速

在另一个线程中填充 BufferedWaveProvider 时出现“缓冲区已满”异常