Какой способ передать набор основных данных, хранящихся в фоновом режиме, в основной поток? - PullRequest
3 голосов
/ 10 октября 2011

Часть моего проекта iOS опрашивает сервер на наличие наборов объектов, затем преобразует и сохраняет их в Core Data, чтобы затем обновить пользовательский интерфейс с результатами. Задачи сервера происходят в наборе классов NSOperation, которые я называю «сервисами», которые работают в фоновом режиме. Если бы NSManagedObject и его ~Context были поточно-безопасными, я бы использовал методы делегата вызова служб в основном потоке, как этот:

- (void)service:(NSOperation *)service retrievedObjects:(NSArray *)objects;

Конечно вы не можете обойти NSManagedObject s , как этот, поэтому этот метод делегата обречен. Насколько я вижу, есть два решения, чтобы добраться до объектов из основного потока. Но мне не нравится ни один из них, поэтому я надеялся, что большое сообщество StackOverflow поможет мне придумать третий.

  1. Я мог бы выполнить NSFetchRequest в главном потоке, чтобы получить вновь добавленные или измененные объекты. Проблема в том, что хранилище Core Data содержит еще много этих объектов, поэтому мне нужно добавить довольно много подробностей, чтобы сообщить правильный набор объектов. Одним из способов было бы добавить свойство к объекту, например batchID, которое я мог бы затем передать обратно делегату, чтобы он знал, что выбрать. Но добавление данных в хранилище для исправления моих ограничений параллелизма кажется неправильным.

  2. Я также мог бы собрать свойства objectID вновь добавленных объектов, поместить их в список и отправить этот список в метод делегата. К сожалению, я должен заполнить список после . Я сохраняю контекст, что означает, что я должен дважды зацикливаться на объектах в фоновом режиме, прежде чем у меня будет правильный список (первый раз, когда анализируется ответ сервера). Тогда у меня все еще есть список objectID с, который я должен по отдельности набрать с existingObjectWithID:error: из NSManagedObjectContext в главном потоке. Это просто кажется таким громоздким.

Какую информацию я пропускаю? Каково третье решение для переноса набора NSManagedObject s из фонового потока в основной поток без потери ограничения потока?

Ответы [ 3 ]

0 голосов
/ 11 октября 2011

После небольшого эксперимента я решил немного изменить предложенный мной метод № 2. Выполняя фоновые изменения в контексте, сохраняйте счет объектов, которые вы хотите делегировать, в основном потоке, скажем, вNSMutableArray *objectsOfInterest.В конечном итоге мы хотим получить ключи objectID всех объектов в этом массиве, но поскольку значение objectID изменяется при сохранении контекста, нам сначала нужно выполнить это [context save:&error].Сразу после сохранения используйте метод arrayFromObjectsAtKey: из категории NSArray ниже, чтобы сгенерировать список objectID экземпляров, например, так:

NSArray *objectIDs = [objectsOfInterest arrayFromObjectsAtKey:@"objectID"];

Этот массив вы можете безопасно вернуть обратно в основной поток черезделегат (убедитесь, что ваш контекст основного потока обновлен до mergeChangesFromContextDidSaveNotification, слушая NSManagedObjectContextDidSaveNotification).Когда вы будете готовы наложить объекты фоновой операции, используйте метод existingObjectsWithIDs:error: из категории, приведенной ниже, чтобы превратить массив идентификаторов объектов в список рабочих NSManagedObject s.

Любые предложениядля улучшения краткости или производительности этих методов приветствуется.

@implementation NSArray (Concurrency)

- (NSArray *)arrayFromObjectsAtKey:(NSString *)key {
   NSMutableArray *objectsAtKey = [NSMutableArray array];
   for (id value in self) {
       [objectsAtKey addObject:[value valueForKey:key]];
   }
   return objectsAtKey;
}

@end

@implementation NSManagedObjectContext (Concurrency)

- (NSArray *)existingObjectsWithIDs:(NSArray *)objectIDs error:(NSError **)error {
    NSMutableArray *entities = [NSMutableArray array];

    @try {
        for (NSManagedObjectID *objectID in objectIDs) {
            // existingObjectWithID might return nil if it can't find the objectID, but if you're not prepared for this,
            // don't use this method but write your own. 
            [entities addObject:[self existingObjectWithID:objectID error:error]];
        }
    }
    @catch (NSException *exception) {
        return nil;
    }

    return entities;
}

@end
0 голосов
/ 11 октября 2011

epologee,

Хотя у вас, очевидно, есть решение, которым вы довольны, позвольте мне предложить вам потерять некоторую ценную информацию, будь то элементы, которые были обновлены, удалены или вставлены, с помощью вашего механизма.В своем коде я просто перенес словарь userInfo в новый MOC.Вот процедура общего назначения, чтобы сделать это:

// Migrate a userInfo dictionary as defined by NSManagedObjectContextDidSaveNotification
// to the receiver context.
- (NSDictionary *) migrateUserInfo: (NSDictionary *) userInfo {

    NSMutableDictionary *ui = [NSMutableDictionary dictionaryWithCapacity: userInfo.count];

    NSSet        *  sourceSet = nil;
    NSMutableSet *migratedSet = nil;

    for (NSString *key in [userInfo allKeys]) {

        sourceSet   = [userInfo valueForKey: key];
        migratedSet = [NSMutableSet setWithCapacity: sourceSet.count];

        for (NSManagedObject *mo in sourceSet) {

            [migratedSet addObject: [self.moc objectWithID: mo.objectID]];
        }
        [ui setValue: migratedSet forKey: key];
    }
    return ui;

} // -migrateUserInfo:

Вышеупомянутая процедура предполагает, что это метод класса, который имеет @property NSManagedObjectContext *moc.

. Надеюсь, вы найдете это полезным.

Андрей

0 голосов
/ 10 октября 2011

Есть раздел Руководства по программированию базовых данных, в котором рассматривается Параллельность с базовыми данными . В двух словах, каждый поток должен иметь свой собственный контекст управляемого объекта, а затем использовать уведомления для синхронизации контекстов.

...