私のアプリでは、一連の動的データを定期的にファイルに書き込んでいます。データ オブジェクトは約 1 秒ごとに更新されます。時折、encodeWithCoder: メソッドの行の 1 つで、「変異中にコレクションが変異しました」という例外が発生します。各オブジェクトは次のようにエンコードされます。
[aCoder encodeObject:self.speeds forKey:@"speeds"];
self.speeds は NSMutableArray です。問題は、エンコード中にデータが更新されていることだと思います。エンコーディングブロックと保存ブロックで @synchronize を使用してみました。また、プロパティを非アトミックではなくアトミックにしようとしましたが、どちらも機能しませんでした。保存はバックグラウンドで行われています。更新中にこのデータをバックグラウンドで保存する方法のアイデアはありますか? コピーを作成して保存すればうまくいくような気がしますが、同じ問題は発生しませんか? ありがとう!
編集1:
アプリのアイデアは、マップ ビューを開くことです。マップ ビューは、データ オブジェクトの配列を含むシングルトン クラスを定期的に更新します。各データ オブジェクトはユーザーのマップ情報です。各データ オブジェクトには、ユーザーの位置、速度、高度、距離などがあります。ロケーション マネージャーがユーザーの位置を 3 回更新するたびに、現在のデータ オブジェクト (この旅行を追跡するために作成されたばかりの「ライブ」データ オブジェクト) を更新します。新しい情報を持つ「ライブ」データ オブジェクトは常に 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 メソッドで呼び出される他のメソッドもありますが、「ライブ」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)];
必要に応じてさらにコードを投稿できますが、それらは重要な部分のようです。