Objective c 集合在枚举时发生了变异-存档数据并使用NSCoder写入文件

Objective c 集合在枚举时发生了变异-存档数据并使用NSCoder写入文件,objective-c,ios,serialization,asynchronous,nscoder,Objective C,Ios,Serialization,Asynchronous,Nscoder,在我的应用程序中,我定期将一组动态数据写入文件。数据对象大约每秒更新一次。有时,我会在encodeWithCoder:方法中的一行上出现“集合在发生变异时发生了变异”异常。每个对象的编码如下: [aCoder encodeObject:self.speeds forKey:@"speeds"]; [sharedSingleton setBackgroundQueue:dispatch_queue_create("com.xxxx.xx", NULL)]; 其中self.speeds是一个NS

在我的应用程序中,我定期将一组动态数据写入文件。数据对象大约每秒更新一次。有时,我会在encodeWithCoder:方法中的一行上出现“集合在发生变异时发生了变异”异常。每个对象的编码如下:

[aCoder encodeObject:self.speeds forKey:@"speeds"];
[sharedSingleton setBackgroundQueue:dispatch_queue_create("com.xxxx.xx", NULL)];
其中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"];
    }
}
这是update方法(方法中有更多的代码,update方法中调用了其他方法,但我省略了所有不引用'live'
dataObject
对象的内容;正在更新的对象):

最后是
SingletonDataController
类中的
synchronize
方法(根据Tommy的回答,现在同步发生在异步块中):

其中backgroundQueue是这样创建的:

[aCoder encodeObject:self.speeds forKey:@"speeds"];
[sharedSingleton setBackgroundQueue:dispatch_queue_create("com.xxxx.xx", NULL)];

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

如果您担心序列化需要足够长的时间才能影响下一次序列化,请复制对象,然后使用
dispatch\u async
对其进行序列化。这样,序列化将在异步队列中进行

但是,您可能想完全重新考虑这种方法。 核心数据不是一种选择吗?有了它,您只能更新实际更改的值,而且我很确定它可以处理您的锁定问题

编辑很抱歉,我误读了你最初的帖子。如果你不经常保存,你可能想考虑使用锁。看

但只有在不经常序列化的情况下才能这样做,因为这会显著降低性能


因此,锁定对象,复制它,解锁对象,序列化异步复制。

是的,序列化数组的副本而不是可变数组将确保数组在保存时不会更改,但你只是在转移问题:你仍然会遇到这样的情况,数组可能在一个线程上改变,而在另一个线程上复制。您可以将@synchronize块放在副本和数组变体周围(就像您所说的保存/更新操作一样..如果您对@synchronize参数使用相同的对象,那么这应该会起作用?@synchronize(self)是一种方便的方法)

同步复制操作的另一种方法是使用dispatch_sync()在主线程上进行复制:

__block NSArray* listCopy;

dispatch_sync(dispatch_get_main_queue(), ^{ listCopy = [self.speeds copy]; });

[aCoder encodeObject:listCopy forKey:@"speeds"];
[listCopy release];
这是一种粗粒度的复制,在主线程清除之前无法进行复制,而@synchronized copy可以在主线程脱离@synchronize块后立即运行,但它的优点是您只需将此代码放入保存线程中,不用担心在主线程中更改数组的位置


编辑:刚刚看到关于使用NSLock的另一个注释。使用@synchronize与使用NSLock(一篇关于它的好文章)几乎是一样的,但是您不必担心管理lock对象。同样,@synchronize应该对您有效,只要您没有几十个不同的位置需要同步,它就非常方便。

您可以在一个
@synchronize
内执行
异步调度。里面的东西不受内置在同步器中的隐式锁的约束;所有发生的事情都是你得到锁,分派块,然后释放锁。因此,块可以很容易地发生在锁之外(事实上,您可以预期它通常会发生)


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

我认为复制可能会起作用。试试简单吗?是的。碰撞很少发生,因此如果没有非常严格的测试,很难说问题是否已经解决。但是值得一试。我已经将保存移到主线程,我相信我仍然会遇到相同的崩溃,尽管iOS诊断报告有点含糊不清。
@synchronize
应该是一个可能的解决方案,您能展示一下使用它的代码吗?好的。我每隔几分钟才连续发布一次,所以我不认为性能是一个太大的问题。我来看看锁。问题是,这就是问题所在吗?序列化只需要几秒钟,即使我每分钟只序列化一次,我也会遇到这个错误。呃,如果是这样的话,为什么不在序列化时停止更新呢