Что означает Apple, когда они говорят, что NSManagedObjectContext принадлежит потоку или очереди, которые его создали? - PullRequest
60 голосов
/ 26 января 2011

Похоже, что в ноябре Apple обновила документы NSManagedObjectContext и Core Data Programming Guide , чтобы явным образом благословить последовательные очереди отправки GCD и NSOperationQueues как приемлемые механизмы для синхронизации доступа до NSManagedObjectContext. Но их советы кажутся двусмысленными и, возможно, противоречивыми, и я хочу убедиться, что правильно их понял.

Ранее считалось, что NSManagedObjectContext может быть доступен только из потока, который его создал, и что использование последовательной очереди для синхронизации было недостаточно; хотя последовательные очереди выполняют только одну операцию за раз, эти операции могут потенциально планироваться в разных потоках, и MOC это не нравится.

Но теперь из руководства по программированию мы имеем:

Для параллелизма вы можете использовать потоки, очереди последовательных операций или очереди отправки. Для краткости, эта статья использует «нить» повсеместно для обозначения любого из них.

Пока все хорошо (хотя их смешение потоков и очередей бесполезно). Таким образом, я могу безопасно использовать один контекст на (последовательную) очередь вместо одного на операцию / блок, верно? Apple даже имеет визуальное представление об этом в сеансах Core Data WWDC.

Но ... где вы создаете контекст для очереди? В документации NSManagedObjectContext Apple заявляет:

[Контекст] предполагает, что владельцем по умолчанию является выделенный им поток или очередь - это определяется потоком, который вызывает его метод init. Поэтому не следует инициализировать контекст в одном потоке, а затем передавать его в другой поток.

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

Это правильно? Единственная причина, по которой я колеблюсь, это то, что в статье NSManagedObjectContext говорится:

Вместо этого вы должны передать ссылку на постоянный координатор хранилища и попросить принимающий поток / очередь создать новый контекст, полученный из этого. Если вы используете NSOperation, вы должны создать контекст в main (для последовательной очереди) или в start (для параллельной очереди).

Похоже, Apple теперь связывает операции с очередями, которые планируют их выполнение. Это заставляет меня задуматься и заставляет задуматься, действительно ли они хотят, чтобы вы просто создали новый MOC для каждой операции. Чего мне не хватает?

Ответы [ 2 ]

64 голосов
/ 27 января 2011

NSManagedObjectContext и любые связанные с ним управляемые объекты должны быть прикреплены к одному субъекту (поток, сериализованная очередь, NSOperationQueue с максимальным параллелизмом = 1).

Этот шаблон называется ограничением потока или изоляцией.Не существует хорошей фразы для (thread || serialized queue || NSOperationQueue с max concurrency = 1), поэтому в документации говорится: «Мы просто будем использовать« thread »для оставшейся части документа Core Data, когда мы имеем в видулюбой из этих трех способов получения сериализованного потока управления "

Если вы создаете MOC в одном потоке, а затем используете его в другом, вы нарушили ограничение потока, выставив ссылку на объект MOC двум потокам.Просто.Не делай этого.Не пересекайте потоки.

Мы явно вызываем NSOperation, потому что в отличие от потоков и GCD, у него есть странная проблема, когда -init запускается в потоке, создавая NSOperation, но -main запускается в потоке, выполняющем NSOperation.Это имеет смысл, если вы щурились на это правильно, но это не интуитивно понятно.Если вы создадите свой MOC в - [NSOperation init], то NSOperation будет услужливо нарушать ограничение потока до того, как ваш метод -main даже запустится, и вы заскочили.

Мы активно поощряем / осуждаем использование MOC и потоков любыми другими способами.Хотя теоретически возможно делать то, что упоминает Бом, никто никогда не понимал этого правильно.Все споткнулись, забыли необходимый вызов, чтобы -lock в 1 месте, «init run куда?», Или иначе превзошли сами себя.С пулами автоматического освобождения и циклом событий приложения, а также диспетчером отмены, привязками какао и KVO у одного потока есть очень много способов удержать ссылку на MOC после того, как вы попытались передать ее в другое место.Это гораздо сложнее, чем могут себе представить даже продвинутые разработчики Cocoa, пока они не начнут отлаживать.Так что это не очень полезный API.

Документация изменена, чтобы прояснить и подчеркнуть шаблон ограничения потока как единственный разумный путь.Вам следует подумать о том, чтобы попытаться сделать что-то более необычное, используя -lock и -unlock в NSManagedObjectContext, чтобы (a) было невозможно и (b) де-факто устарело.Это буквально не считается устаревшим, потому что код работает так же, как и раньше.Но ваш код, использующий его, неверен.

Некоторые люди создали MOC в одном потоке и передали их другому без вызова -lock.Это никогда не было законно.Поток, создавший MOC, всегда был владельцем MOC по умолчанию.Это стало более частой проблемой для MOC, созданных в главном потоке.MOC основного потока взаимодействуют с основным циклом событий приложения для отмены, управления памятью и некоторых других причин.В 10.6 и iOS 3 MOC более активно используют принадлежность к основному потоку.

Хотя очереди не привязаны к конкретным потокам, если вы создаете MOC в контексте очереди, правильные вещи будут происходить,Вы обязаны следовать общедоступному API.

Если очередь сериализована, вы можете совместно использовать MOC с последующими блоками, которые выполняются в этой очереди.

Поэтому не предоставляйте NSManagedObjectContext * более чем одному потоку (субъекту и т. Д.) Влюбое обстоятельство.Есть одна двусмысленность.Вы можете передать NSNotification * из уведомления didSave в MOC другого потока -mergeChangesFromContextDidSaveNotification: метод.

  • Бен
11 голосов
/ 26 января 2011

Звучит так, будто ты все правильно понял. Если вы используете потоки, поток, которому нужен контекст, должен его создать. Если вы используете очереди, очередь, которая хочет контекст, должна создать его, скорее всего, как первый блок, который будет выполнен в очереди. Похоже, единственная запутанная часть - немного о NSOperations. Я думаю, что путаница в том, что NSOperations не дает никаких гарантий относительно того, в каком основном потоке / очереди они работают, поэтому может быть небезопасно разделять MOC между операциями, даже если они все выполняются в одном NSOperationQueue. Альтернативное объяснение состоит в том, что это просто запутанная документация.

Подводя итог:

  • Если вы используете потоки, создайте MOC в нужном потоке
  • Если вы используете GCD, создайте MOC в самом первом блоке, выполняемом в вашей последовательной очереди
  • Если вы используете NSOperation, создайте MOC внутри NSOperation и не разделяйте его между операциями. Это может быть немного параноидально, но NSOperation не гарантирует, в каком основном потоке / очереди он работает.

Редактировать : Согласно bbum, единственное реальное требование - доступ должен быть сериализован. Это означает, что вы можете совместно использовать MOC между NSOperations, если все операции добавлены в одну и ту же очередь и очередь не допускает одновременных операций.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...