背景情報
アプリをほぼ完成させました。すべてが完璧に機能していました。次に、クライアントはアプリにログインするように要求しました (つまり、何が行われたか、どのような応答があったかなどを記録する必要があるさまざまなポイント)。
アプリを使用すると、ユーザーはコアデータに保存される「メッセージ」を作成できます。その後、メッセージは個別にサーバーにアップロードされます。NSOperation
メッセージはメイン スレッドで作成され、バックグラウンド スレッドのサブクラスにアップロードされます。
NSOperation
これは、以前に使用したサブクラスの同じテンプレートであり、機能します。マルチスレッドコアデータのベストプラクティスをすべて行っています。
アプリのこちら側はすべて正常に動作します。
アプリのロギング部分を追加しました。というシングルトンMyLogManager
と というCoreData
エンティティを作成しましたLogEntry
。date
エンティティは非常に単純で、としかありませんtext
。
コード
MyLogManager 内の関数は...
- (void)newLogWithText:(NSString*)text
{
NSLog(@"Logging: %@", text);
NSManagedObjectContext *context = [self context];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"LogEntry" inManagedObjectContext:context];
LogEntry *logEntry = [[LogEntry alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
logEntry.text = text;
logEntry.date = [NSDate date];
[self saveContext:context];
}
それが実行されます...
- (NSManagedObjectContext*)context
{
AppDelegate *appDelegate = (ThapAppDelegate*)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[managedObjectContext setPersistentStoreCoordinator:appDelegate.persistentStoreCoordinator];
return managedObjectContext;
}
と
- (void)saveContext:(NSManagedObjectContext*)context
{
MyAppDelegate *appDelegate = (MyAppDelegate*)[[UIApplication sharedApplication] delegate];
[[NSNotificationCenter defaultCenter] addObserver:appDelegate
selector:@selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:context];
NSError *error = nil;
if ([context hasChanges] && ![context save:&error]) {
NSLog(@"Unhandled error %@, %@", error, [error userInfo]);
abort();
}
[[NSNotificationCenter defaultCenter] removeObserver:appDelegate name:NSManagedObjectContextDidSaveNotification object:context];
}
NSOperation メイン スレッド (よくある部分)...
- (void)main
{
//create context and retrieve NSManagedObject using the NSManagedObjectID passed in as a parameter to operation
self.message.lastSendAttempt = [NSDate date];
[self startUpload];
[self completeOperation]; //This doesn't get run because the startUpload method never returns
}
- (void)startUpload
{
[[MyLogManager sharedInstance] logSendingMessageWithURLParameters:[self.event URLParameters]]; //this is a convenience method. It just appends some extra info on the string and runs newLogWithText.
//Do some uploading stuff here...
//The operation stops before actually doing the upload when logging to CoreData.
}
問題
NSOperation
(バックグラウンド スレッドで) メッセージをアップロードするサブクラスは、この関数を呼び出しますがnewLogWithText
、アップロードしているメッセージも更新します。はNSOperation
同じメソッドを使用してコアデータ コンテキストを取得および保存します。(つまり、最後に送信された日付を更新し、送信が成功した場合も更新します)。
コアデータへの書き込みと保存を同時に処理しようとしたのはこれが初めてです。
エラーは発生せず、アプリは引き続き「機能」しています。しかし、操作は決して完了しません。ブレークポイントを使用してデバッグしようとしましたが、ブレークポイントを使用すると機能します。ブレークポイントがないと、操作が終了せず、アップロードも行われません。そして、それが置かれているキューをブロックしてそこに留まり、他のメッセージを送信できなくなります。
私のappDelegate
場合(これが理想的な場所ではないことはわかっていますが、新しいプロジェクトのデフォルトであり、変更していません)、mergeChanges
メソッドはただ...
- (void)mergeChanges:(NSNotification *)notification
{
[self.managedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:NO];
}
「newLogWithText」関数を別のスレッドに、さらにはメイン スレッドに投げてみましたが、うまくいきませんでした。
私はちょうどそれを試してみようとしていますが、マージの「waitUntilDone」を YES に変更します。(ちょうどこれに気づいた)。(これはうまくいきませんでした)。
私がこれに対処したのは初めてなので、これが異なるコンテキストへの同時書き込みと競合の解決にかかっていることは 90% 確信しています。関数をコメントアウトするとnewLogWithText
、すべてが正常に機能します。
現時点での唯一の代替手段は、LogEntry
コア データからログを削除し、内部の配列にログを保存することですNSUserDefaults
が、それは適切ではありません。別の方法はありますか?
編集
私は今それを変更したので、ユーザーNSUserDefaults
は問題なく動作します。ハッキーなソリューションのように感じます。