Ответ 1
Вы правы в том, что все, что вы хотите сделать с MOC, должно выполняться либо в performBlock
, либо performBlockAndWait
. Обратите внимание, что сохранение/освобождение является потокобезопасным для управляемых объектов, поэтому вам не обязательно находиться внутри одного из этих блоков, чтобы удерживать/освобождать ссылки на управляемые объекты.
Оба используют синхронную очередь для обработки сообщений, что означает, что за один раз будет выполняться только один блок. Ну, это почти так. См. Описания performBlockAndWait
. В любом случае доступ к MOC будет сериализован таким образом, чтобы только один поток обращался к MOC за раз.
tl; dr Не беспокойтесь о различии и всегда используйте performBlock
.
Фактические различия
Существует несколько отличий. Я уверен, что есть больше, но вот те, которые, как мне кажется, наиболее важны для понимания.
Синхронный и асинхронный
performBlock
является асинхронным, поскольку он немедленно возвращается, и блок выполняется в будущем в некотором нераскрытом потоке. Все блоки, переданные MOC через performBlock
, будут выполняться в том порядке, в котором они были добавлены.
performBlockAndWait
является синхронным, поскольку вызывающий поток будет ждать, пока блок не выполнит перед возвратом. Независимо от того, работает ли блок в каком-то другом потоке или работает в вызывающем потоке, это не так важно, и это деталь реализации, которой нельзя доверять.
Обратите внимание, однако, что это может быть реализовано как "Эй, какой-то другой поток, запустите этот блок. Я собираюсь сидеть здесь, ничего не делая, пока вы не скажете мне об этом". Или это может быть реализовано как "Привет, Основные данные", дайте мне блокировку, которая предотвратит запуск всех этих других блоков, чтобы я мог запустить этот блок в моем собственном потоке ". Или это может быть реализовано каким-то другим способом. Опять же, детали реализации, которые могут измениться в любое время.
Я скажу вам это, хотя в последний раз, когда я его протестировал, performBlockAndWait
выполнил блок на вызывающем потоке (что означает второй вариант в предыдущем абзаце). Это только настоящая информация, которая поможет вам понять, что происходит, и ни в коем случае нельзя полагаться.
Реентерабельность
performBlock
является всегда асинхронным и, следовательно, не является реентерабельным. Ну, некоторые могут считать его реентерабельным, поскольку вы можете вызвать его из блока, который был вызван с помощью performBlock
. Однако, если вы это сделаете, все вызовы performBlock
вернутся немедленно, и блок не будет выполняться, пока, по крайней мере, текущий исполняемый блок полностью не завершит свою работу.
[moc performBlock:^{
doSomething();
[moc performBlock:^{
doSomethingElse();
}];
doSomeMore();
}];
Эти функции всегда будут выполняться в следующем порядке:
doSomething()
doSomeMore()
doSomethingElse()
performBlockAndWait
синхронно всегда. Кроме того, он также реентерабелен. Множественные вызовы не будут заторможены. Таким образом, если вы вызываете performBlockAndWait
, когда находитесь внутри блока, который запускался в результате другого performBlockAndWait
, тогда это ОК. Вы получите ожидаемое поведение, поскольку второй вызов (и любые последующие вызовы) не приведет к тупиковой ситуации. Кроме того, второй будет полностью выполнен, прежде чем он вернется, как и следовало ожидать.
[moc performBlockAndWait:^{
doSomething();
[moc performBlockAndWait:^{
doSomethingElse();
}];
doSomeMore();
}];
Эти функции всегда будут выполняться в следующем порядке:
doSomething()
doSomethingElse()
doSomeMore()
FIFO
FIFO означает "First In First Out", что означает, что блоки будут выполняться в том порядке, в котором они были помещены во внутреннюю очередь.
performBlock
всегда оценивает структуру FIFO внутренней очереди. Каждый блок будет вставлен в очередь и запускаться только при его удалении в порядке FIFO.
По определению performBlockAndWait
ломает порядок FIFO, потому что он перескакивает очередь блоков, которые уже были помещены в очередь.
Блоки, отправленные с помощью performBlockAndWait
, не должны ждать других блоков, запущенных в очереди. Существует несколько способов увидеть это. Один простой - это.
[moc performBlock:^{
doSomething();
[moc performBlock:^{
doSomethingElse();
}];
doSomeMore();
[moc performBlockAndWait:^{
doSomethingAfterDoSomethingElse();
}];
doTheLastThing();
}];
Эти функции всегда будут выполняться в следующем порядке:
doSomething()
doSomeMore()
doSomethingAfterDoSomethingElse()
doTheLastThing()
doSomethingElse()
В этом примере очевидно, поэтому я использовал его. Однако учтите, что если ваш MOC получает материал, вызываемый из нескольких мест. Может быть немного запутанным.
Следует помнить, что performBlockAndWait
является превентивным и может переходить в очередь FIFO.
Тупик
Вы никогда не получите тупик, вызывающий performBlock
. Если вы делаете что-то глупо внутри блока, тогда вы можете зайти в тупик, но вызов performBlock
никогда не будет заторможен. Вы можете вызвать его из любого места, и он просто добавит блок в очередь и выполнит его некоторое время в будущем.
Вы можете легко получить взаимоблокировки, вызывающие performBlockAndWait
, особенно если вы вызываете его из метода, который внешний объект может вызывать или без разбора в вложенных контекстах. В частности, вы почти наверняка затормозите свои приложения, если родитель называет performBlockAndWait
для ребенка.
Пользовательские события
Core Data рассматривает "пользовательское событие" как нечто между вызовами processPendingChanges
. Вы можете прочитать сведения о том, почему этот метод важен, но то, что происходит в "пользовательском событии", имеет последствия для уведомлений, отмены управления, удаления распространения, изменения коалесценции и т.д.
performBlock
инкапсулирует "пользовательское событие" , что означает, что блок кода автоматически выполняется между различными вызовами на processPendingChanges
.
performBlockAndWait
не инкапсулирует "пользовательское событие" . Если вы хотите, чтобы блок обрабатывался как отдельное пользовательское событие, вы должны сделать это самостоятельно.
Пул автоматического выпуска
performBlock
обертывает блок в свой собственный autoreleasepool.
performBlockAdWait
не предоставляет уникальный autoreleasepool. Если вам это нужно, вы должны предоставить его сами.
Личное мнение
Лично я не считаю, что существует очень много веских причин использовать performBlockAndWait
. Я уверен, что у кого-то есть прецедент, который не может быть выполнен каким-либо другим способом, но я его еще не видел. Если кто-нибудь знает об этом случае использования, пожалуйста, поделитесь им со мной.
Самый близкий вызов performBlockAndWait
в родительском контексте (никогда не делайте этого на MOC NSMainConcurrencyType
, потому что он может заблокировать ваш пользовательский интерфейс). Например, если вы хотите убедиться, что база данных полностью сохранена на диске до того, как текущий блок вернется, а другие блоки получат возможность запуска.
Таким образом, некоторое время назад я решил рассматривать Core Data как полностью асинхронный API. В результате у меня есть много кода основных данных, и у меня нет одного вызова performBlockAndWait
, вне тестов.
Жизнь намного лучше. У меня гораздо меньше проблем, чем я, когда думал, что "это должно быть полезно, или они не предоставили бы его".
Теперь я больше не нуждаюсь в performBlockAndWait
. В результате, возможно, это изменило некоторые, и я просто пропустил это, потому что это меня больше не интересует... но я в этом сомневаюсь.