Основные данные - разрыв цикла сохранения родительского контекста - программирование
Подтвердить что ты не робот

Основные данные - разрыв цикла сохранения родительского контекста

Скажем, у нас есть две сущности в модели Core Data: департаменты и сотрудники.
Отдел имеет отношение "один ко многим" к сотрудникам.

У меня есть следующие ManagedObjectContexts:
- Корень: подключен к координатору постоянных магазинов
- Главная: контекст с родительским корнем

Когда я хочу создать Работника, я делаю следующее:
- У меня есть отдел в главном контексте
- Я создаю Работника в Главном контексте
- Я назначаю Департамент собственности отдела сотрудников
- Я сохраняю главный контекст
- Я сохраняю контекст корня

Это создает цикл сохранения как в основном контексте, так и в корневом контексте.

Если бы я сделал это без дочернего контекста (все в корневом контексте), я мог бы сломать цикл сохранения, вызвав refreshObject:mergeChanges в Employee. В моей ситуации с двумя контекстами я все еще мог использовать этот метод для разрыва цикла в главном контексте, но как я могу сломать цикл в корневом контексте?

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

Обновление 15/04: NSPrivateQueueConcurrencyType vs NSMainQueueConcurrencyType
После сохранения обоих контекстов я могу выполнить refreshObject:mergeChanges в главном контексте с помощью объекта "Департамент". Это, как и ожидалось, приведет к повторному отказу объекта "Департамент", нарушит цикл сохранения и освободит объекты отдела и сотрудника в этом контексте.

Следующим шагом будет разрыв цикла сохранения, который существует в корневом контексте (сохранение основного контекста передало сущности в корневой контекст). Я могу сделать тот же трюк здесь и использовать refreshObject:mergeChanges в корневом контексте с объектом отдела.

Странно: это прекрасно работает, когда мой корневой контекст создается с помощью NSMainQueueConcurrencyType (все распределения перехватываются и освобождаются), но не работает, когда мой корневой контекст создается с помощью NSPrivateQueueConcurrencyType (все распределения перезапускаются, но не освобожден).

Боковое примечание: все операции для корневого контекста выполняются в вызове executeBlock (AndWait)

Обновление 15/04: Часть 2
Когда я делаю другое (бесполезное, потому что нет изменений) сохранение или откат в корневом контексте с помощью NSPrivateQueueConcurrencyType, объекты, как представляется, освобождены. Я не понимаю, почему это не ведет себя так же, как NSMainQueueConcurrencyType.

Обновление 16/04: демонстрационный проект
Я создал демонстрационный проект: http://codegazer.com/code/CoreDataTest.zip

Обновление 21/04: Как добраться
Спасибо, Джоди Хаггинс за вашу помощь! Я пытаюсь переместить refreshObject:mergeChanges из моих методов ManagedObject didSave.

Не могли бы вы объяснить мне разницу между:

[rootContext performBlock:^{
    [rootContext save:nil];
    for (NSManagedObject *mo in rootContext.registeredObjects)
        [rootContext refreshObject:mo mergeChanges:NO];
}];

и

[rootContext performBlock:^{
    [rootContext save:nil];
    [rootContext performBlock:^{
        for (NSManagedObject *mo in rootContext.registeredObjects)
            [rootContext refreshObject:mo mergeChanges:NO];
    }];
}];

Верхняя часть не освобождает объекты, нижняя делает.

4b9b3361

Ответ 1

Я посмотрел ваш образец проекта. Престижность публикации.

Во-первых, поведение, которое вы видите, не является ошибкой... по крайней мере, не в Core Data. Как вы знаете, отношения вызывают циклы сохранения, которые должны быть разбиты вручную (описано здесь: https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/Articles/cdMemory.html).

Ваш код делает это в didSave:. Там могут быть лучшие места для разрыва цикла, но это другое дело.

Обратите внимание, что вы можете легко увидеть, какие объекты зарегистрированы в MOC, просмотрев свойство registeredObjects.

Ваш пример, однако, никогда не выпустит ссылки в корневом контексте, потому что processPendingEvents никогда не вызывается в этом MOC. Таким образом, зарегистрированные объекты в MOC никогда не будут выпущены.

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

Однако для MOC, не входящих в основной поток, вы несете ответственность за правильность обработки пользовательских событий. Смотрите эту документацию: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html, в частности последний абзац раздела под названием Track Changes in Other Threads Using Notifications.

Когда вы вызываете performBlock блок, который вы его передаете, завершается внутри полного пользовательского события. Однако это не относится к performBlockAndWait. Таким образом, MOC частного контекста будет хранить эти объекты в своей коллекции registeredObjects до тех пор, пока не будет вызван processPendingChanges.

В вашем примере вы можете увидеть выпущенные объекты, если вы вызываете processPendingChanges внутри performBlockAndWait или меняете его на performBlock. Любой из них будет следить за тем, чтобы MOC завершил текущее пользовательское событие и удалил объекты из коллекции registeredObjects.

Edit

В ответ на ваше редактирование... Не то, чтобы первый не удалял объекты. Это то, что MOC все еще имеет объекты, зарегистрированные как ошибки. Это произошло после сохранения, в течение того же события. Если вы просто выпустите блок no-op [context performBlock:^{}], вы увидите объекты, удаленные из MOC.

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

В общем, вы не хотите просто обновлять все объекты. Однако, если вы хотите удалить все объекты после сохранения, ваша оригинальная концепция, сделанная в didSave:, является разумной, поскольку это происходит во время процесса сохранения. Тем не менее, это будет ошибка объектов во всех контекстах (которых вы, вероятно, не хотите). Вероятно, вам нужен только этот драконовский подход для фонового MOC. Вы можете проверить object.managedObjectContext на didSave:, но это не очень хорошая идея. Лучше было бы установить обработчик для уведомления DidSave...

id observer = [[NSNotificationCenter defaultCenter]
    addObserverForName:NSManagedObjectContextDidSaveNotification
                object:rootContext
                 queue:nil
            usingBlock:^(NSNotification *note) {
    for (NSManagedObject *mo in rootContext.registeredObjects) {
        [rootContext refreshObject:mo mergeChanges:NO];
    }
}];

Вы увидите, что это, вероятно, дает вам то, что вы хотите... хотя только вы можете определить, что вы действительно пытаетесь выполнить.

Ответ 2

Этапы, описанные выше, являются обычными задачами, выполняемыми в Core Data. И побочные эффекты четко описаны Apple в Руководство по программированию основных данных: Управление жизненным циклом объектов.

Когда у вас есть отношения между управляемыми объектами, каждый объект поддерживает сильную ссылку на объект или объекты, к которым он Связанный. Это может привести к сильным опорным циклам. Обеспечить, чтобы контрольные циклы ломаются, когда вы закончите с объектом, которым вы может использовать метод контекста управляемого объекта refreshObject: mergeChanges: чтобы превратить его в неисправность.

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

Что касается области памяти в целом:

Если вы чувствуете, что распределения растут из-под контроля, вы можете попытаться структурировать свой код, чтобы выполнять задачи, которые вызывают сбои в работе большого количества объектов, в отдельном контексте, которые вы отбрасываете, когда вы закончите с задача.

Кроме того, если вы используете менеджер отмены,

Менеджер отмены, связанный с контекстом, сохраняет сильные ссылки на любые измененные управляемые объекты. По умолчанию в OS X контексты отменены менеджер сохраняет неограниченный стек отмены/повтора. Чтобы ограничить области памяти приложения, вы должны убедиться, что вы очищаете (используя removeAllActions) контекст отменяет стек как и когда подходящее. Если вы не будете ссылаться на отмену контекста менеджер, он освобождается от контекста.

Обновление # 1:

После экспериментов с инструментами выделения и фрагментом кода, написанным специально для проверки этого, я могу подтвердить, что корневой контекст не освобождает память. Либо это ошибка структуры, либо она предназначена по дизайну. Я нашел сообщение здесь, описывающее ту же проблему.

Вызов [context reset] после [context save:] действительно освободил память. Также я заметил, что до сохранения корневой контекст имел все объекты, которые я ввел с помощью дочернего контекста в его набор [context insertedObjects]. Итерация над ними и выполнение [context refreshObject:mergeChanges:NO] сделали повторную ошибку объектов.

Так что, похоже, не так много обходных решений, но будет ли это ошибкой и будет исправлено в какой-либо предстоящей версии, или она останется такой же, как и по дизайну, я не знаю.

Ответ 3

При сохранении в корневом контексте единственная, содержащая сильную ссылку на объекты, это сам корневой контекст, поэтому, если вы просто reset, объекты будут освобождены в корневом контексте.
Вы сохраняете поток:
-save main
-save root
- reset root

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