Подтвердить что ты не робот

Обнаружение изменений в определенном атрибуте NSManagedObject

Как я могу обнаружить изменения для определенного атрибута NSManagedObject? В моей модели данных основных данных у меня есть объект Product, который представляет продукт для продажи. Объект Product имеет несколько атрибутов: price, sku, weight, numberInStock и т.д. Всякий раз, когда изменяется атрибут price для Product, мне нужно выполнить длительный расчет. Следовательно, я хотел бы знать, когда изменяется атрибут price любого Product, [edit], даже если это изменение связано с объединением контекста, сохраненного в другом потоке. Что такое хороший способ сделать это? У меня есть тысячи объектов Product в моем магазине; очевидно, не представляется возможным отправить каждому сообщение addObserver.

Я использовал NSManagedObjectContextObjectsDidChangeNotification для обнаружения изменений, но он только уведомляет меня о том, что управляемый объект изменился, а не какой атрибут этого объекта изменился. Я мог бы повторить вычисление всякий раз, когда есть какое-либо изменение в Product, но это приводит к бесполезным перерасчетам всякий раз, когда неактуальный атрибут изменился. Я рассматриваю возможность создания объекта price (который содержит только атрибут price) и использования отношения "один к одному" между Product и price. Таким образом, я могу обнаружить изменения объектов price, чтобы начать вычисление. Мне кажется, это слишком глупо. Есть ли лучший способ?

Update:

@railwayparade указал, что я могу использовать метод changedValues NSManagedObject, чтобы определить, какие свойства изменились для каждого обновленного объекта. Я полностью пропустил этот метод, и это полностью решит мою проблему, если бы изменения не выполнялись в фоновом потоке и не сливались в контексте основного потока. (См. Следующий параграф.)

Я полностью упустил тонкость в отношении того, как работает NSManagedObjectContextObjectsDidChangeNotification. Насколько я могу судить, когда контекст управляемого объекта, сохраненный в другом потоке, объединяется в контекст основного потока (используя mergeChangesFromContextDidSaveNotification:), результирующий NSManagedObjectContextObjectsDidChangeNotification содержит информацию об изменении объектов, которые в настоящее время находятся в главном контекст, управляемый потоком. Если измененный объект не находится в контексте основного потока, он не будет частью уведомления. Это имеет смысл, но я не ожидал этого. Поэтому моя мысль об использовании взаимного отношения вместо атрибута для получения более подробной информации об изменении фактически требует изучения фонового потока NSManagedObjectContextDidSaveNotification, а не основного потока NSManagedObjectContextObjectsDidChangeNotification. Разумеется, было бы гораздо разумнее использовать метод changedValues NSManagedObject, как объяснил @railwayparade. Тем не менее, я все еще остаюсь с проблемой, что уведомление об изменении из слияния в основном потоке не обязательно будет содержать все изменения, сделанные в фоновом потоке.

4b9b3361

Ответ 1

Этот тип обстоятельств - это то, где вам нужен пользовательский подкласс NSManagedObject. Вам нужен подкласс, потому что вы добавляете поведение, реагирующее на изменение цены, на управляемый объект.

В этом случае вы должны переопределить аксессор для атрибута price. Создайте собственный подкласс, используя всплывающее меню в редакторе модели данных. Затем выберите атрибут price и выберите "Копировать реализацию Obj-C 2.0 в буфер обмена". Это даст вам много чего, но ключевой бит будет выглядеть так:

- (void)setPrice:(NSNumber *)value 
{
    [self willChangeValueForKey:@"price"];
    [self setPrimitivePrice:value];
    [self didChangeValueForKey:@"price"];
}

Просто добавьте код, чтобы иметь дело с изменением цены, и все готово. В любое время, когда изменяется конкретная цена продукта, код будет работать.

Ответ 2

Одна точка в отношении этого потока,

NSManagedObjectContextObjectsDidChangeNotification, сгенерированная Core Data, указывает, что управляемый объект изменился, но не указывает, какой атрибут был изменен.

На самом деле. Метод "changedValues" может использоваться для запроса того, какие атрибуты изменились.

Что-то вроде,

 if([updatedObjects containsKindOfClass:[Config class]]){
    //if the config.timeInterval changed
    NSManagedObject *obj = [updatedObjects anyObject];
    NSDictionary *dict=[obj changedValues];
    NSLog(@"%@",dict);
    if([dict objectForKey:@"timeInterval"]!=nil){
      [self renderTimers];
    }
  }

Ответ 3

Вы можете взглянуть на KVO (Key Value Observing). Не уверен, что в Core Data API есть обертки, но я знаю, что это часть Objective-C.

Ответ 4

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

Во-первых, я начну с короткого и, надеюсь, более ясного, повторения проблемы:

В моем приложении я хочу обнаружить изменения для определенного атрибута (price) управляемого объекта (Product). Кроме того, я хочу знать об этих изменениях независимо от того, сделаны они на основном или фоновом потоке. Наконец, я хочу знать об этих изменениях, даже если основной поток в настоящее время не имеет измененного объекта Product в контексте управляемого объекта.

NSManagedObjectContextObjectsDidChangeNotification, сгенерированный Core Data, указывает, что управляемый объект изменился, но не указывает, какой атрибут изменился. Моим kludgy решением было создать управляемый объект price, содержащий единственный атрибут price, и заменить атрибут price в Product на взаимное отношение к управляемому объекту price. Теперь, всякий раз, когда происходит изменение управляемого объекта price, Core Data NSManagedObjectContextObjectsDidChangeNotification будет содержать этот объект price в своем наборе NSUpdatedObjectsKey. Мне просто нужно передать эту информацию в основной поток. Все это звучит неплохо, но есть заминка.

В хранилище My Core Data работают два потока. Это делается "обычным" способом - для каждого потока существует единый контекст объекта и один общий постоянный координатор хранилища. После того, как фоновый поток вносит изменения, он сохраняет свой контекст. Основной поток обнаруживает сохранение контекста через NSManagedObjectContextDidSaveNotification и объединяет изменения контекста с помощью mergeChangesFromContextDidSaveNotification:. (На самом деле, поскольку уведомления получены в том же потоке, в котором они размещены, NSManagedObjectContextDidSaveNotification получен в фоновом потоке и передается в основной поток через performSelectorOnMainThread: для слияния.) В результате слияния Core Data генерирует символ NSManagedObjectContextObjectsDidChangeNotification, указывающий измененные объекты. Однако, насколько я могу судить, NSManagedObjectContextObjectsDidChangeNotification включает только те объекты, которые в настоящее время представлены в принимающем контексте. Это имеет смысл с точки зрения обновления пользовательского интерфейса. Если управляемый объект не отображается, он, вероятно, не будет в контексте, поэтому нет необходимости включать его в уведомление.

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

Второй вариант, о котором я думал, - использовать NSManagedObjectContextDidSaveNotification, сгенерированный фоновым потоком, когда он сохраняет свой контекст. Это уведомление содержит информацию обо всех изменениях в управляемых объектах. Я уже обнаружил это уведомление и передал его в основной поток для слияния, так почему бы не заглянуть внутрь и не увидеть все измененные объекты управления? Вы помните, что управляемые объекты не предназначены для совместного использования в потоках. Следовательно, если я начну рассматривать содержимое NSManagedObjectContextDidSaveNotification в основном потоке, я получаю сбои. Хм... так как это сделать mergeChangesFromContextDidSaveNotification:? По-видимому, mergeChangesFromContextDidSaveNotification: специально разработан для ограничения ограничений "не разделять управляемые объекты по потокам".

Третий вариант, о котором я думал, заключался в регистрации на NSManagedObjectContextDidSaveNotification в фоновом потоке, а пока в фоновом потоке преобразовать его содержимое в специальный PriceChangeNotification содержащий идентификаторы объектов вместо управляемых объектов. В основном потоке я мог преобразовать идентификаторы объектов обратно в управляемые объекты. Этот подход по-прежнему требует отношения "один-два" price, так что изменения цен отражаются как изменения в управляемых объектах price.

Я основал свой четвертый вариант на предложении TechZen, чтобы переопределить установщик цены в управляемом объекте Product. Вместо того, чтобы использовать отношение "один к одному", чтобы заставить Core Data генерировать нужные мне уведомления, я вернулся к использованию атрибута price. В моем методе setPrice я размещаю пользовательский PriceChangeNotification. Это уведомление получено в фоновом потоке и используется для построения набора объектов Product с изменениями цен. После того, как фоновый поток сохранит свой контекст, он отправляет пользовательский PricesDidChangeNotification, который включает в себя идентификаторы объектов всех объектов Product, цены которых изменились. Это уведомление может быть безопасно перенесено в основной поток и проверено, потому что оно использует идентификаторы объектов вместо самих управляемых объектов. В основном потоке я могу получить объекты Product, на которые ссылаются эти идентификаторы объектов, и поставить в очередь операцию для выполнения большого вычисления "изменения цены" в новом фоновом потоке.

Ответ 5

Используете ли вы NSArrayController или какой-либо другой контроллер? Предположительно, вам нужен какой-то способ взаимодействия пользователя с моделью. Это эта точка взаимодействия, которая дает хороший крюк для этого типа вызова обновления. Возможно, подходящей стратегией является наблюдение соответствующих свойств контроллера массива arrangedObjects.