2010-08-13 2 views
13

Я читал здесь несколько сообщений о NSManagedObjectContext и многопоточных приложениях. Я также рассмотрел пример CoreDataBooks, чтобы понять, как отдельные потоки требуют собственного NSManagedObjectContext и как операция сохранения объединяется с основным 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 используется для отдельных запросов в одном потоке. Это проблема?

+0

chris, я столкнулся с аналогичной проблемой многопоточности при использовании единого NSManagedObjectContext, созданного в основном потоке для всех операций в NSoperation Queue. Пробем приходит, когда каждый поток пытается сохранить контекст, приложение аварийно бросает исключение из основных данных. я думал заблокировать этот контекст во время использования во всех операциях, так что каждый из них будет иметь эксклюзивный доступ к контексту. я прочитал ur выше solution.sounds gud, couls u, пожалуйста, вставьте мне новый код, который вы использовали для слияния контекста, а также прокомментируйте использование блокировки для playin. – 2011-05-14 18:36:48

ответ

8

Через год после публикации этого вопроса я, наконец, создал структуру для обобщения и упрощения работы с Core Data. Это выходит за рамки первоначального вопроса и добавляет ряд функций, позволяющих значительно упростить взаимодействие с Core Data.Подробности здесь: https://github.com/chriscdn/RHManagedObject

0

Я нашел решение, после того, как окончательно понял проблему лучше. Мое решение напрямую не затрагивает вопрос выше, но решает проблему того, почему мне приходилось иметь дело с потоками в первую очередь.

Мое приложение использует библиотеку ASIHTTPRequest для асинхронных запросов. Я беру некоторые данные с сервера и использую функцию делегата requestFinished для добавления/изменения/удаления объектов ядра. Функция requestFinished работала в другом потоке, и я предположил, что это был естественный побочный эффект асинхронных запросов.

После копать глубже я обнаружил, что ASIHTTPRequest сознательно запускает запрос в отдельном потоке, но могут быть переопределены в моем подклассу ASIHTTPRequest:

+(NSThread *)threadForRequest:(ASIHTTPRequest *)request { 
    return [NSThread mainThread]; 
} 

Это небольшое изменение ставит requestFinished в основном потоке, который устранил мне нужно заботиться о потоках в моем приложении.

+0

Я не совсем уверен, что понял. ASIHTTPRequest использует отдельный поток (фактически NSOperationQueue) для асинхронных запросов, но в равной степени он всегда запускает requestFinished на mainthread. (В последнем коде эта функция находится в методе callSelectorOnMainThread.) Тем не менее я не вижу недостатка в вашем решении. – JosephH

+0

Я обнаружил, что requestFinished не запускался в основном потоке, пока я не добавил эти три строки выше. Возможно ли, что это изменилось с помощью ASIHTTPRequest? Я использую v1.7. – chris

+0

Вы правы. Делегат ASIHTTPRequest запускается в основном потоке, но я выполнял подкласс ASIHTTPRequest и помещал свой код в метод 'requestFinished:'. Это не обязательно вызывается в основном потоке. благодаря – chris

Смежные вопросы