收集在被枚举时发生变异 - 存档数据并使用NSCoder写入文件

eri*_*ell 8 serialization asynchronous objective-c nscoder ios

在我的应用程序中,我定期将一组动态数据写入文件.数据对象每秒都会更新一次.偶尔我会在我的encodeWithCoder:方法中的一行上得到"Collection is mutated in mutated"异常.每个对象的编码如下:

[aCoder encodeObject:self.speeds forKey:@"speeds"];
Run Code Online (Sandbox Code Playgroud)

self.speeds是一个NSMutableArray.我假设问题是数据在编码时正在更新.我尝试在编码保存块中使用@synchronize,我也尝试将属性设置为原子而不是非原子,但都没有工作.节约发生在后台.有关如何在更新时在后台保存此数据的任何想法?我想制作副本然后保存副本会起作用,但不会出现同样的问题吗?谢谢!


编辑1:

应用程序中的想法是我打开一个地图视图,它定期更新包含数据对象数组的单例类,每个数据对象都是用户的地图信息.在每个数据对象,用户的位置,速度,高度,距离等每三次外景经理更新用户的位置,我更新当前数据对象(这是刚刚创建跟踪此脱扣的"活动"的数据对象使用新信息只能有一个"实时"数据对象.

我想每隔x分钟将整个单例写入一个文件,但有时写入和更新同时发生,我得到这个错误(或者至少这是我认为导致这种崩溃的原因).这是我的代码或我的设计模式的问题吗?


这是我的自定义类中的编码方法:

- (void)encodeWithCoder:(NSCoder*)aCoder {
    @synchronized([SingletonDataController sharedSingleton]) {
        [aCoder encodeObject:[[lineLats copy] autorelease] forKey:@"lineLats"];
        [aCoder encodeObject:[[lineLongs copy] autorelease] forKey:@"lineLongs"];
        [aCoder encodeObject:[[horizontalAccuracies copy] autorelease] forKey:@"horAcc"];
        [aCoder encodeObject:[[verticalAccuracies copy] autorelease] forKey:@"vertAcc"];
        [aCoder encodeObject:[[speeds copy] autorelease] forKey:@"speeds"];
        [aCoder encodeObject:[[overlayColors copy] autorelease] forKey:@"colors"];
        [aCoder encodeObject:[[annotationLats copy] autorelease] forKey:@"annLats"];
        [aCoder encodeObject:[[annotationLongs copy] autorelease] forKey:@"annLongs"];
        [aCoder encodeObject:[[locationManagerStartDate copy] autorelease] forKey:@"startDate"];
        [aCoder encodeObject:[[locationManagerStartDateString copy] autorelease] forKey:@"locStartDateString"];
        [aCoder encodeObject:[[mapTitleString copy] autorelease] forKey:@"title"];
        [aCoder encodeObject:[[shortDateStringBackupCopy copy] autorelease] forKey:@"backupString"];

        [aCoder encodeFloat:pathDistance forKey:@"pathDistance"];
        [aCoder encodeFloat:linearDistance forKey:@"linearDistance"];
        [aCoder encodeFloat:altitudeChange forKey:@"altitudeChange"];
        [aCoder encodeFloat:averageSpeedWithFilter forKey:@"avWithFilter"];
        [aCoder encodeFloat:averageSpeedWithoutFilter forKey:@"avWithoutFilter"];

        [aCoder encodeInt:totalTripTimeInSeconds forKey:@"totalTimeInSecs"];
    }
}
Run Code Online (Sandbox Code Playgroud)

这是更新方法(方法中有更多的代码和更新方法中调用的其他方法,但我省略了所有不引用'live' dataObject对象的东西;正在更新的对象:

- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation {
    @synchronized([SingletonDataController sharedSingleton]) {
        //create temporary location for last logged location
        CLLocation* lastLocation;
        if([dataObject.lineLats lastObject] && [dataObject.lineLongs lastObject]) {
            lastLocation = [[CLLocation alloc] initWithLatitude:[[dataObject.lineLats lastObject] floatValue] longitude:[[dataObject.lineLongs lastObject] floatValue]];
        } else {
            lastLocation = [oldLocation retain];
        }

        //.....

        //periodically add horizontal/vertical accuracy
        if(iterations > 0 && iterations % 4 == 0) {
            [dataObject.horizontalAccuracies addObject:[NSNumber numberWithFloat:[newLocation horizontalAccuracy]]];
            [dataObject.verticalAccuracies addObject:[NSNumber numberWithFloat:[newLocation verticalAccuracy]]];
        }

        //.....

        //accumulate some speed data
        if(iterations % 2 == 0) {
            NSNumber* speedNum = [[NSNumber alloc] initWithFloat:[newLocation speed]];
            [dataObject.speeds addObject:speedNum];
            [speedNum release];
        }

        //.....

        //add latitude and longitude
        NSNumber* lat = [[NSNumber alloc] initWithFloat:[newLocation coordinate].latitude];
        NSNumber* lon = [[NSNumber alloc] initWithFloat:[newLocation coordinate].longitude];
        if(fabs([lat floatValue]) > .0001 && fabs([lon floatValue]) > .0001) {
            [dataObject.lineLats addObject:lat];
            [dataObject.lineLongs addObject:lon];
        }

        if(iterations % 60 == 0) {
            [[SingletonDataController sharedSingleton] synchronize];
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

终于synchronize在方法SingletonDataController类(更新,现在发生同步异步块按汤米的答案):

dispatch_async(self.backgroundQueue, ^{
    @synchronized([SingletonDataController sharedSingleton]) {
        NSLog(@"sync");
        NSData* singletonData = [NSKeyedArchiver archivedDataWithRootObject:
            [SingletonDataController sharedSingleton]];

        if(!singletonData) {
            return;
        }

        NSString* filePath = [SingletonDataController getDataFilePath];
        [singletonData writeToFile:filePath atomically:YES];
    }
});
Run Code Online (Sandbox Code Playgroud)

其中backgroundQueue的创建方式如下:

[sharedSingleton setBackgroundQueue:dispatch_queue_create("com.xxxx.xx", NULL)];
Run Code Online (Sandbox Code Playgroud)

如果需要,我可以发布更多代码,但这些似乎是重要的部分.

Tom*_*mmy 5

dispatch_async在其中一个人中执行@synchronize.那里的东西不受同步中内置的隐式锁的影响; 所有这一切都发生在你获得锁定,调度块然后释放锁定.所以块很容易在锁外发生(事实上,你通常会期望它会发生).

要坚持使用同步路径,您需要@synchronize在块内而不是在块外.但是,您可能会尝试采用一种不那么有力的方法,例如在单个串行调度队列上执行所有更新,并允许它们将相关的新值推送到主队列.