Я прочитал здесь несколько постов о NSManagedObjectContext и многопоточных приложениях. Я также рассмотрел пример CoreDataBooks, чтобы понять, как отдельным потокам требуется свой собственный NSManagedObjectContext, и как операция сохранения объединяется с основным NSManagedObjectContext. Я нашел пример, который был хорошим, но также и слишком специфичным для приложения. Я пытаюсь обобщить это, и задаюсь вопросом, является ли мой подход обоснованным.
Мой подход заключается в том, чтобы иметь универсальную функцию для извлечения NSManagedObjectContext для текущего потока. Функция возвращает NSManagedObjectContext для основного потока, но создаст новый (или извлечет его из кэша), если вызывается из другого потока. Это выглядит следующим образом:
+(NSManagedObjectContext *)managedObjectContext {
MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = delegate.managedObjectContext;
NSThread *thread = [NSThread currentThread];
if ([thread isMainThread]) {
return moc;
}
// a key to cache the context for the given thread
NSString *threadKey = [NSString stringWithFormat:@"%p", thread];
// delegate.managedObjectContexts is a mutable dictionary in the app delegate
NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts;
if ( [managedObjectContexts objectForKey:threadKey] == nil ) {
// create a context for this thread
NSManagedObjectContext *threadContext = [[[NSManagedObjectContext alloc] init] autorelease];
[threadContext setPersistentStoreCoordinator:[moc persistentStoreCoordinator]];
// cache the context for this thread
[managedObjectContexts setObject:threadContext forKey:threadKey];
}
return [managedObjectContexts objectForKey:threadKey];
}
Операции сохранения просты, если вызваны из основного потока. Операции сохранения, вызываемые из других потоков, требуют объединения в основном потоке. Для этого у меня есть общая функция commit
:
+(void)commit {
// get the moc for this thread
NSManagedObjectContext *moc = [self managedObjectContext];
NSThread *thread = [NSThread currentThread];
if ([thread isMainThread] == NO) {
// only observe notifications other than the main thread
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:moc];
}
NSError *error;
if (![moc save:&error]) {
// fail
}
if ([thread isMainThread] == NO) {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:moc];
}
}
В функции contextDidSave:
мы выполняем слияние, если оно вызывается уведомлением в commit
.
+(void)contextDidSave:(NSNotification*)saveNotification {
MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = delegate.managedObjectContext;
[moc performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
withObject:saveNotification
waitUntilDone:YES];
}
Наконец, мы очищаем кэш NSManagedObjectContext следующим образом:
+(void)initialize {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(threadExit)
name:NSThreadWillExitNotification
object:nil];
}
+(void)threadExit {
MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *threadKey = [NSString stringWithFormat:@"%p", [NSThread currentThread]];
NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts;
[managedObjectContexts removeObjectForKey:threadKey];
}
Это компилируется и, кажется, работает, но я знаю, что проблемы с потоками могут быть сложными из-за условий гонки. Кто-нибудь видит проблему с этим подходом?
Кроме того, я использую это из контекста асинхронного запроса (используя ASIHTTPRequest), который выбирает некоторые данные с сервера и обновляет и вставляет хранилище на iPhone. Кажется, NSThreadWillExitNotification не запускается после завершения запроса, и тот же поток затем используется для последующих запросов. Это означает, что тот же NSManagedObjectContext используется для отдельных запросов в том же потоке. Это проблема?
Я нашел решение после того, как наконец лучше понял проблему. Мое решение напрямую не решает поставленный выше вопрос, но решает проблему, почему мне вообще пришлось иметь дело с потоками.
Мое приложение использует библиотеку ASIHTTPRequest для асинхронных запросов. Я получаю некоторые данные с сервера и использую функцию делегата requestFinished
для добавления / изменения / удаления моих объектов основных данных. Функция requestFinished
выполнялась в другом потоке, и я предположил, что это естественный побочный эффект асинхронных запросов.
Покопавшись глубже, я обнаружил, что ASIHTTPRequest намеренно выполняет запрос в отдельном потоке, но его можно переопределить в моем подклассе ASIHTTPRequest:
+(NSThread *)threadForRequest:(ASIHTTPRequest *)request {
return [NSThread mainThread];
}
Это небольшое изменение помещает requestFinished
в основной поток, который имеет устранил мою потребность заботиться о потоках в моем приложении.