Обнаружение изменений в определенном атрибуте 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. Тем не менее, я все еще остаюсь с проблемой, что уведомление об изменении из слияния в основном потоке не обязательно будет содержать все изменения, сделанные в фоновом потоке.
Ответы
Ответ 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
.