Коллекция была изменена во время перечисления - данные архивируются и записываются в файл с помощью NSCoder

В моем приложении я периодически записываю набор динамических данных в файл. Объект данных обновляется примерно каждую секунду. Иногда я получаю исключение «Коллекция была изменена при изменении» в одной из строк моего метода encodeWithCoder :. Каждый объект кодируется следующим образом:

[aCoder encodeObject:self.speeds forKey:@"speeds"];

Где 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"];
    }
}

Это метод обновления (в методе больше кода и другие методы, вызываемые в методе обновления, но я опускаю все, что не ссылается на «живой» объект 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];
        }
    }
}

И, наконец, метод 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];
    }
});

где backgroundQueue создается следующим образом:

[sharedSingleton setBackgroundQueue:dispatch_queue_create("com.xxxx.xx", NULL)];

Я могу опубликовать больше кода, если необходимо, но они кажутся важными.

8
задан eric.mitchell 1 March 2012 в 23:14
поделиться