CoreDataを使用したGrand Central Dispatch(GCD)
-
27-09-2019 - |
質問
私はアプリケーションでグランドセントラルディスパッチ(GCD)を使用して、重いリフティングを行います。アプリケーションは、データストレージの目的でCore-Dataを使用しています。これが私のシナリオです(関連する質問とともに):
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_queue_t request_queue = dispatch_queue_create("com.app.request", NULL);
dispatch_async(request_queue, ^{
MyNSManagedObject *mObject = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
// …
// <heavy lifting>
// …
// …
// <update mObject>
// …
[self saveManagedObjectContext];
});
結果として [self saveManagedObjectContext]
, fetchResultsController
委任メソッドは自動的に呼ばれます。その結果、UIの更新ロジックがキックします。
今私の質問は、私は使用する必要があるかです main_queue
にとって -saveManagedObjectContext
?すべての操作を実行する必要があります NSManagedObject
の main_queue
?更新する操作の一部 NSManagedObject
2〜3秒かかる場合があります。お知らせ下さい。
解決
おそらく知っているか、気づいたように、メインスレッドでUI操作を実行する必要があります。あなたが言及するように、それはあなたがUIアップデートを保存するときです。これを解決することで、これを解決できます dispatch_sync
メインスレッドで。
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_queue_t request_queue = dispatch_queue_create("com.app.request", NULL);
__block __typeof__(self) blockSelf = self;
dispatch_async(request_queue, ^{
MyNSManagedObject *mObject = [blockSelf.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
// update and heavy lifting...
dispatch_sync(main_queue, ^{
[blockSelf saveManagedObjectContext];
});
});
の用法 blockSelf
誤って参照サイクルの作成を避けることです。 (実用的なブロック)
他のヒント
コアデータに関しては、ゴールデンルールがあります - スレッドごとに1つの管理されたオブジェクトコンテキスト。管理されたオブジェクトのコンテキストはスレッドセーフではないため、バックグラウンドタスクで作業を行っている場合、メインスレッドを使用してUI操作との競合のスレッディングを回避するか、作業を行う新しいコンテキストを作成します。数秒で、後者を行い、UIがロックアップしないようにする必要があります。
これを行うには、新しいコンテキストを作成し、メインコンテキストと同じ永続的なストアを提供します。
NSManagedObjectContext *backgroundContext = [[[NSManagedObjectContext alloc] init] autorelease];
[backgroundContext setPersistentStoreCoordinator:[mainContext persistentStoreCoordinator]];
必要な操作を行うには、その新しいコンテキストを保存するときは、保存通知を処理し、変更をメインコンテキストにマージする必要があります。 mergeChangesFromContextDidSaveNotification:
メッセージ。コードは次のように見えるはずです:
/* Save notification handler for the background context */
- (void)backgroundContextDidSave:(NSNotification *)notification {
/* Make sure we're on the main thread when updating the main context */
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:NO];
return;
}
/* merge in the changes to the main context */
[self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
/* ... */
/* Save the background context and handle the save notification */
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:backgroundContext];
[backgroundContext save:NULL];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:syncContext];
保存通知とマージの処理は重要です。そうしないと、メインのUI/コンテキストでは変更が表示されません。マージすることにより、メインのfetchResultScontrollerなどが変更され、予想通りUIを更新します。
注意すべきもう1つの重要なことは、nsmanagedObjectインスタンスは、それらから取得されたコンテキストでのみ使用できるということです。操作がオブジェクトへの参照が必要な場合は、オブジェクトを渡す必要があります objectID
操作に、新しいコンテキストからnsmanagedObjectインスタンスを使用して再び取り戻します existingObjectWithID:
. 。だから:
/* This can only be used in operations on the main context */
MyNSManagedObject *objectInMainContext =
[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
/* This can now be used in your background context */
MyNSManagedObject *objectInBackgroundContext =
(MyNSManagedObject *) [backgroundContext existingObjectWithID:[objectInMainContext objectID]];
コアデータにはスレッドごとに1つの管理されたオブジェクトコンテキストが必要なため、可能なソリューションは、グローバルマネージャーのスレッドごとのコンテキストを追跡し、通知を保存してすべてのスレッドに伝播することです。
仮定:
@property (nonatomic, strong) NSDictionary* threadsDictionary;
マネージドオブジェクトを取得する方法(スレッドごと)は次のとおりです。
- (NSManagedObjectContext *) managedObjectContextForThread {
// Per thread, give one back
NSString* threadName = [NSString stringWithFormat:@"%d",[NSThread currentThread].hash];
NSManagedObjectContext * existingContext = [self.threadsDictionary objectForKey:threadName];
if (existingContext==nil){
existingContext = [[NSManagedObjectContext alloc] init];
[existingContext setPersistentStoreCoordinator: [self persistentStoreCoordinator]];
[self.threadsDictionary setValue:existingContext forKey:threadName];
}
return existingContext;
}
グローバルマネージャーのinitメソッドのある時点で(シングルトンを使用しました):
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:nil];
次に、[通知]を受信し、他のすべての管理されたコンテキストオブジェクトに伝播するために:
- (void)backgroundContextDidSave:(NSNotification *)notification {
/* Make sure we're on the main thread when updating the main context */
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
withObject:notification
waitUntilDone:NO];
return;
}
/* merge in the changes to the main context */
for (NSManagedObjectContext* context in [self.threadsDictionary allValues]){
[context mergeChangesFromContextDidSaveNotification:notification];
}
}
(明確にするために他のいくつかの方法が削除されました)