Вот почему я не использую фреймворки, которые абстрактно (т. Е. Скрывают) слишком много деталей основных данных. Он имеет очень сложные шаблоны использования, и иногда вам нужно знать детали того, как они взаимодействуют.
Во-первых, я ничего не знаю о магической записи, за исключением того, что многие люди используют ее, поэтому она должна быть очень хороша в том, что она делает.
Тем не менее, я сразу увидел несколько примеров неправильного использования параллелизма данных ядра в ваших примерах, поэтому я пошел и посмотрел файлы заголовков, чтобы понять, почему ваш код сделал предположения, что он делает.
Я не собираюсь вас шокировать, хотя это может показаться на первый взгляд красным. Я хочу помочь обучить вас (и я использовал это как возможность взглянуть на MR).
С очень быстрым взглядом на МР, я бы сказал, что у вас есть некоторые недоразумения в том, что делает MR, а также общие правила параллелизма основных данных.
Во-первых, вы говорите, это ...
Два управляемых контексты объектов создаются с помощью типа параллелизм == NSPrivateQueueConcurrencyType (пожалуйста, проверьте код MR_context из волшебной звукозаписывающей рамок). Оба контекста имеют родительский контекст с тип параллелизма = NSMainQueueConcurrencyType.
который не представляется правдой. Два новых контекста, действительно, являются контекстами частной очереди, но их родительский (в соответствии с кодом, который я смотрел на github) является магическим MR_rootSavingContext
, который сам также является контекстом частной очереди.
Давайте разберем ваш пример кода.
NSManagedObjectContext *childContext1 = [NSManagedObjectContext MR_context];
childContext1.name = @"childContext1";
NSManagedObjectContext *childContext2 = [NSManagedObjectContext MR_context];
childContext2.name = @"childContext2";
Итак, теперь у вас есть два МОС частных очередей (childContext1
и childContext2
), как дети другого анонимного MOC частного очереди (мы будем называть savingContext
).
NSArray *results = [self entityWithId:entityId inContext: childContext2];
Затем вы выполняете выборку на childContext1
. Этот код на самом деле ...
-(NSArray *) entityWithId:(int)entityId
inContext:(NSManagedObjectContext *)localContext
{
NSArray * results = [TestEntity MR_findByAttribute:@"id"
withValue:[NSNumber numberWithInt:entityId]
inContext:localContext];
return results;
}
Теперь мы знаем, что localContext
в этом методе, в этом случае, другой указатель на childContext2
который является MOC частной очереди. Это на 100% соответствует правилам параллелизма для доступа к MOC с частной очередью за пределами вызова performBlock
. Однако, поскольку вы используете другой API, а имя метода не помогает узнать, как осуществляется доступ к MOC, нам нужно взглянуть на этот API и посмотреть, скрывает ли он performBlock
, чтобы узнать, правильно ли вы обращаетесь к нему.
К сожалению, документация в файле заголовка не содержит никаких указаний, поэтому мы должны рассмотреть реализацию. Этот вызов заканчивается вызовом MR_executeFetchRequest...
, который не указывает в документации, как он обрабатывает параллелизм. Итак, мы рассмотрим его реализацию.
Теперь мы добираемся куда-то. Эта функция пытается безопасно получить доступ к MOC, но использует performBlockAndWait
, который будет блокироваться при его вызове.
Это чрезвычайно важная информация, потому что вызов этого из-за неправильного места может действительно вызвать тупик. Таким образом, вы должны быть в курсе, что performBlockAndWait
вызывается в любое время, когда вы выполняете запрос на выборку. Мое личное правило заключается в никогда использовать performBlockAndWait
, если нет абсолютно никакой другой опции.
Однако этот вызов здесь должен быть полностью безопасным ... если предположить, что он не вызывается из контекста родительского MOC.
Итак, давайте рассмотрим следующий фрагмент кода.
for(TestEntity *d in results)
{
NSLog(@"e from fetchRequest %@ with name = '%@'", d, d.name); /// this line is the reason of the hangup
}
Теперь это не ошибка MagicalRecord, потому что MR даже не используется непосредственно здесь. Тем не менее, вас обучили использовать эти методы MR_
, которые не требуют знания модели параллелизма, поэтому вы либо забываете, либо никогда не изучаете правила параллелизма.
Объекты в массиве results
являются управляемыми объектами, которые живут в контексте частной очереди childContext2
. Таким образом, вы можете никогда не обращаться к ним, не отдавая должное правилам параллелизма. Это явное нарушение правил параллелизма. При разработке приложения вы должны включить параллельную отладку с аргументом -com.apple.CoreData.ConcurrencyDebug 1.
Этот фрагмент кода должен быть обернут либо performBlock
, либо performBlockAndWait
. Я почти никогда не использую performBlockAndWait
для чего-либо, потому что у него так много недостатков - одним из них является тупик. Фактически, только вид использования performBlockAndWait
является очень сильным признаком того, что ваш тупик происходит там, а не в строке кода, который вы указываете. Однако, в этом случае, по крайней мере, в безопасности, как предыдущая выборка, так давайте сделаем это немного безопаснее ...
[childContext2 performBlockAndWait:^{
for (TestEntity *d in results) {
NSLog(@"e from fetchRequest %@ with name = '%@'", d, d.name);
}
}];
Далее, вы посылаете в основной поток. Это потому, что вы просто хотите, чтобы что-то произошло в следующем цикле цикла событий, или это потому, что этот код уже запущен на каком-то другом потоке? Кто знает. Тем не менее, у вас есть те же проблемы здесь (я переформатировал ваш код для удобства чтения в качестве сообщения).
dispatch_async(dispatch_get_main_queue(), ^{
int entityId2 = 11;
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:@"id=%d", entityId2];
NSArray *a = [TestEntity MR_findAllWithPredicate:predicate2
inContext:childContext2];
for (TestEntity *d in a) {
NSLog(@"e from fetchRequest %@ with name = '%@'", d, d.name);
}
});
Теперь мы знаем, что код начинается работает в основном потоке, и поиск будет вызывать performBlockAndWait
, но ваш последующий доступ в течение петли снова нарушает основные правила данных параллелизма.
Основываясь на этом, только реальные проблемы, я вижу это ...
MR кажется чтить правила основных данных параллелизма в их API, но вы все равно должны следовать правилам основных данных параллелизма при доступе ваши управляемые объекты.
Мне действительно не нравится использование performBlockAndWait
, так как это просто проблема, ожидающая своего появления.
Теперь давайте посмотрим на скриншот вашей завивки. Хм ... это классический тупик, но это бессмысленно, потому что тупик происходит между основной нитью и потоком MOC. Это может произойти только в том случае, если MOC главной очереди является родителем этого MOC частной очереди, но код показывает, что это не так.
Хммм ... это не имеет смысла, поэтому я загрузил ваш проект и посмотрел исходный код в загруженном вами блоке. Теперь эта версия кода использует MR_defaultContext
как родительский элемент всех MOC, созданных с помощью MR_context
. Итак, MOC по умолчанию, действительно, MOC главной очереди, и теперь все это имеет смысл.
У вас есть MOC как ребенок главной очереди MOC. Когда вы отправляете этот блок в основную очередь, он теперь работает как блок в главной очереди. Затем код вызывает performBlockAndWait
в контексте, который является дочерним элементом MOC для этой очереди, который является огромным нет-нет, и вы почти гарантированно получите тупик.
Итак, похоже, что с тех пор MR сменил свой код на использование главной очереди в качестве родителя новых контекстов для использования частной очереди в качестве родителя новых контекстов (скорее всего, из-за этой точной проблемы). Итак, если вы обновляетесь до последней версии МР, вы должны быть в порядке.
Однако я все же предупреждаю вас, что если вы хотите использовать MR многопоточными способами, вы должны точно знать, как они обрабатывают правила параллелизма, и вы также должны быть уверены, что выполняете их в любое время, когда получаете доступ к каким-либо основным данным объекты, которые не проходят через MR API.
Наконец, я просто скажу, что я сделал тонны и тонны основных данных, и я никогда не использовал API, который пытается скрыть проблемы параллелизма у меня. Причина в том, что слишком много маленьких угловых случаев, и я бы скорее просто справился с ними прагматичным образом.
Наконец, вы почти никогда не должны использовать performBlockAndWait
, если не знаете точно, почему его единственный вариант. Если он будет использоваться как часть API под вами, вам еще страшнее ... по крайней мере, мне.
Я надеюсь, что эта небольшая прогулка просветила и помогла вам (и, возможно, некоторым другим). Это, безусловно, пролило немного света для меня и помогло восстановить некоторые из моих предыдущих необоснованных пугливости.
Редактировать
Это в ответ на пример «не-волшебно-рекорд» вы предоставили.
Проблема с этим кодом - это то же самое, что я описал выше, относительно того, что происходило с MR.
У вас есть контекст частной очереди, как дочерний элемент в контексте главной очереди.
Выполняется код в главной очереди, и вы вызываете performBlockAndWait
в дочерний контекст, который затем должен блокировать свой родительский контекст, когда он пытается выполнить выборку.
Это называется тупиком, но более описательный (и соблазнительный) термин является смертельным объятием.
Исходный код работает на основном потоке. Он вызывает детский контекст, чтобы что-то делать, и он ничего не делает до тех пор, пока этот ребенок не завершится.
Этот ребенок, для того, чтобы завершить, нуждается в основной теме, чтобы что-то сделать. Тем не менее, основная нить ничего не может сделать до тех пор, пока ребенок не будет сделан ... но ребенок ждет, чтобы основной поток что-то сделал ...
Ни один из них не может двигаться вперед.
Проблема, с которой вы сталкиваетесь, очень хорошо документирована и фактически упоминается несколько раз в докладах WWDC и нескольких документах.
Вы должны NEVER позвонить performBlockAndWait
по детскому контексту.
Тот факт, что вы ушли с ним в прошлом, - это просто «случайность», потому что он не должен работать таким образом вообще.
В действительности, вы вряд ли должны каждый раз звонить performBlockAndWait
.
Вы действительно должны привыкнуть к асинхронному программированию. Вот как я бы рекомендовал вам переписать этот тест, и как бы то ни было, это вызвало эту проблему.
Во-первых, переписать выборки, так что работает асинхронно ...
- (void)executeFetchRequest:(NSFetchRequest *)request
inContext:(NSManagedObjectContext *)context
completion:(void(^)(NSArray *results, NSError *error))completion
{
[context performBlock:^{
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
if (completion) {
completion(results, error);
}
}];
}
Затем вы меняете вам код, который вызывает выборки, чтобы сделать что-то вроде этого ...
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity: testEntityDescription ];
[request setPredicate: predicate2 ];
[self executeFetchRequest:request
inContext:childContext2
completion:^(NSArray *results, NSError *error) {
if (results) {
for (TestEntity *d in results) {
NSLog(@"++++++++++ e from fetchRequest %@ with name = '%@'", d, d.name);
}
} else {
NSLog(@"Handle this error: %@", error);
}
}];
Я считаю, что в недавних SDK для iOS 9 есть проблема или несовместимость, но чтобы помочь людям понять это, сделаем ваш пост более ясным. Прежде всего, можете ли вы загрузить свой пример в github? (Chrome не разрешил мне скачать). Второе: удалите все изменения в сделанных вами контейнерах. Удалите неиспользуемый код, сделайте пример чистым. Краткая история проблемы: диспетчерский блок с дочерним контекстом будет зависеть от него, если 1) отправлен из -viewWillAppear: и 2) перед отправкой использовался управляемый объект (с ошибкой) (как и в NSLog: d. name) Та же проблема с прямым CoreDa – MikeR