Dlatego nie używam frameworków, które abstrakcyjne (to znaczy ukrywają) zbyt wiele szczegółów podstawowych danych. Ma bardzo skomplikowane wzorce korzystania, a czasami trzeba znać szczegóły dotyczące ich współdziałania.
Po pierwsze, nie wiem nic o magicznej płycie, z wyjątkiem tego, że wiele osób używa jej, więc musi być całkiem niezła w tym, co robi.
Jednak od razu zauważyłem kilka całkowicie błędnych zastosowań współbieżności danych w naszych przykładach, więc poszedłem i spojrzałem na pliki nagłówkowe, aby zobaczyć, dlaczego twój kod uczynił założenia, które on robi.
Nie mam zamiaru cię wkurzyć, choć może się to wydawać na pierwszy rzut oka. Chcę ci pomóc w nauce (i wykorzystałem to jako okazję, by rzucić okiem na MR).
Od bardzo szybkiego przyjrzenia się MR, powiedziałbym, że macie pewne nieporozumienia co do MR, a także ogólne zasady współbieżności danych podstawowych.
Po pierwsze, można powiedzieć to ...
dwa konteksty zarządzanych obiektów są tworzone typu współbieżności == NSPrivateQueueConcurrencyType (proszę sprawdzić kod MR_context z magicznym ramach rekordu). Oba konteksty mają kontekst nadrzędny z typem współbieżności = NSMainQueueConcurrencyType.
co nie wydaje się być prawdą. Dwa nowe konteksty są w rzeczywistości kontekstami prywatnej kolejki, ale ich rodzic (zgodnie z kodem, który oglądałem na githubie) jest magiczny MR_rootSavingContext
, który sam w sobie jest także kontekstem prywatnej kolejki.
Podzielmy przykład Twojego kodu.
NSManagedObjectContext *childContext1 = [NSManagedObjectContext MR_context];
childContext1.name = @"childContext1";
NSManagedObjectContext *childContext2 = [NSManagedObjectContext MR_context];
childContext2.name = @"childContext2";
Tak, masz teraz dwa MOCy prywatnego kolejki (childContext1
i childContext2
), zarówno dla dzieci innego anonimowego prywatnego kolejki MOC (będziemy nazywać savingContext
).
NSArray *results = [self entityWithId:entityId inContext: childContext2];
Następnie wykonać pobieranie na childContext1
. Ten kod jest w rzeczywistości ...
-(NSArray *) entityWithId:(int)entityId
inContext:(NSManagedObjectContext *)localContext
{
NSArray * results = [TestEntity MR_findByAttribute:@"id"
withValue:[NSNumber numberWithInt:entityId]
inContext:localContext];
return results;
}
Teraz wiemy, że localContext
w tej metodzie jest w tym przypadku inny wskaźnik do childContext2
który jest MOC prywatnego kolejka. Jest 100% przeciwko regułom współbieżności, aby uzyskać dostęp do prywatnego MOC-u poza wywołaniem performBlock
. Ponieważ jednak używasz innego interfejsu API, a nazwa metody nie oferuje pomocy w poznaniu sposobu uzyskiwania dostępu do MOC, musimy przejrzeć ten interfejs API i sprawdzić, czy ukrywa on performBlock
, aby sprawdzić, czy uzyskujesz do niego prawidłowy dostęp.
Niestety, dokumentacja w pliku nagłówkowym nie zawiera żadnych wskazówek, więc musimy spojrzeć na implementację. To wywołanie kończy się wywoływaniem MR_executeFetchRequest...
, co nie wskazuje w dokumentacji, jak obsługuje on również współbieżność. Więc przyjrzymy się jego realizacji.
Teraz coś docieramy. Ta funkcja próbuje bezpiecznie uzyskać dostęp do MOC, ale używa performBlockAndWait
, która blokuje, gdy zostanie wywołana.
Jest to niezwykle ważna informacja, ponieważ wywołanie jej z niewłaściwego miejsca może rzeczywiście spowodować impas. Dlatego musisz mieć świadomość, że performBlockAndWait
jest wywoływane za każdym razem, gdy wykonasz żądanie pobierania. Moja własna zasada brzmi: nigdy używać performBlockAndWait
, chyba że nie ma absolutnie żadnej innej opcji.
Jednak to wezwanie powinno być całkowicie bezpieczne ... zakładając, że nie jest on wywoływany z kontekstu macierzystego MOC.
Spójrzmy więc na następny fragment kodu.
for(TestEntity *d in results)
{
NSLog(@"e from fetchRequest %@ with name = '%@'", d, d.name); /// this line is the reason of the hangup
}
Nie jest to już wina MagicalRecord, ponieważ MR nie jest nawet używany bezpośrednio tutaj. Jednak zostali Państwo przeszkoleni w zakresie stosowania tych metod, które nie wymagają znajomości modelu współbieżności, więc można albo zapomnieć, albo nigdy nie nauczyć się reguł współbieżności.
Wszystkie obiekty w tablicy results
są obiektami zarządzanymi, które znajdują się w kontekście prywatnej kolejki childContext2
. W związku z tym użytkownik nie może uzyskać do nich dostępu bez obowiązku hołdowania regułom współbieżności. Jest to wyraźne naruszenie zasad współbieżności. Podczas rozwijania aplikacji należy włączyć debugowanie współbieżności za pomocą argumentu -com.apple.CoreData.ConcurrencyDebug 1.
Ten fragment kodu musi być zawinięty w: performBlock
lub performBlockAndWait
. Rzadko kiedy używam performBlockAndWait
, ponieważ ma tak wiele wad - jednym z nich są zakleszczenia. W rzeczywistości, po prostu zobaczenie użycia performBlockAndWait
jest bardzo silnym wskazaniem, że twój impas dzieje się tam, a nie na linii kodu, który wskażesz. Jednak w tym przypadku, to jest co najmniej tak samo bezpieczna jak poprzednie sprowadzić, więc zróbmy to nieco bezpieczniejsze ...
[childContext2 performBlockAndWait:^{
for (TestEntity *d in results) {
NSLog(@"e from fetchRequest %@ with name = '%@'", d, d.name);
}
}];
Następnie należy przesyłać do głównego wątku. Czy to dlatego, że chcesz po prostu coś zrobić w kolejnym cyklu pętli zdarzeń, czy też dlatego, że ten kod jest już uruchomiony w innym wątku? Kto wie. Masz jednak ten sam problem (sformatowałem Twój kod pod kątem czytelności jako postu).
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);
}
});
Teraz wiemy, że kod rozpoczyna się działa na głównym wątku, a wyszukiwarka zwróci performBlockAndWait
ale twój kolejny dostęp w pętli for ponownie narusza podstawowe zasady współbieżności danych.
podstawie, że jedyne prawdziwe problemy widzę to ...
MR wydaje się honorować zasady współbieżności podstawowe dane na ich API, ale nadal musisz przestrzegać zasad współbieżności podstawowe dane podczas uzyskiwania dostępu Twoje zarządzane obiekty.
Naprawdę nie podoba mi się użycie performBlockAndWait
, ponieważ jest to tylko problem czekający na zdarzenie.
Teraz rzućmy okiem na zrzut ekranu twojego zawieszenia. Hmmm ... to klasyczny impas, ale nie ma sensu, ponieważ impas dochodzi między głównym wątkiem a wątkiem MOC. Może się to zdarzyć tylko wtedy, gdy główna kolejka MOC jest rodzicem tej MOC prywatnej kolejki, ale kod pokazuje, że tak nie jest.
Hmmm ... to nie miało sensu, więc pobrałem twój projekt i spojrzałem na kod źródłowy w przesłanym przez ciebie poduście. Ta wersja kodu używa MR_defaultContext
jako elementu nadrzędnego wszystkich MOC-ów utworzonych za pomocą MR_context
. Tak więc domyślny MOC jest rzeczywiście główną kolejką MOC, a teraz wszystko ma sens.
Masz MOC jako dziecko głównej kolejki MOC. Po wysłaniu tego bloku do głównej kolejki, jest on teraz uruchomiony jako blok w głównej kolejce. Kod następnie wywołuje performBlockAndWait
w kontekście, który jest dzieckiem MOC dla tej kolejki, co jest ogromnym nie-nie, a twojemu prawie gwarantowane jest uzyskanie zakleszczenia.
Wygląda więc na to, że MR od tego czasu zmieniło swój kod z używania kolejki głównej jako rodzica nowych kontekstów na użycie kolejki prywatnej jako elementu nadrzędnego dla nowych kontekstów (najprawdopodobniej z powodu tego problemu). Tak więc, jeśli uaktualnisz do najnowszej wersji MR, powinieneś być w porządku.
Jednak nadal będę Cię ostrzegał, że jeśli chcesz używać MR w wielowątkowych metodach, musisz dokładnie wiedzieć, w jaki sposób radzą sobie z regułami współbieżności, i musisz także upewnić się, że ich przestrzegasz, gdy tylko uzyskasz dostęp do jakichkolwiek danych podstawowych obiekty, które nie przechodzą przez MR API.
Na koniec powiem, że zrobiłem mnóstwo ton danych podstawowych i nigdy nie korzystałem z interfejsu API, który próbuje ukryć problemy ze współbieżnością. Powodem jest to, że jest zbyt wiele drobnych spraw, a ja wolałbym zajmować się nimi w sposób pragmatyczny z góry.
Wreszcie, prawie nigdy nie należy używać performBlockAndWait
, chyba że wiesz dokładnie, dlaczego jest to jedyna opcja. Mając go jako część API pod Tobą jest jeszcze bardziej przerażająca ... przynajmniej dla mnie.
Mam nadzieję, że ta mała wyprawa oświeciła Cię i pomogła (i możliwe, że inni). Z pewnością rzuciło mi to trochę światła i pomogło mi odzyskać część mojej poprzedniej bezpodstawnej płochliwości.
Edit
To w odpowiedzi na "nie-magiczne-record" przykład, który podałeś.
Problem z tym kodem jest dokładnie tym samym problemem, który opisałem powyżej, w odniesieniu do tego, co działo się z MR.
Masz kontekst prywatnej kolejki, jako element potomny do kontekstu głównej kolejki.
Uruchamiasz kod w głównej kolejce i wywołujesz performBlockAndWait
w kontekście podrzędnym, który musi następnie zablokować kontekst nadrzędny podczas próby pobrania.
Nazywa się impasem, ale bardziej opisowy (i uwodzicielski) termin jest śmiertelnie objąć.
Oryginalny kod działa na głównym wątku. Wzywa dziecko do kontekstu dziecka, aby coś zrobić, i nie robi nic innego, dopóki to dziecko nie ukończy.
To dziecko, aby dokończyć, potrzebuje głównego wątku, aby coś zrobić. Jednak główny wątek nie może nic zrobić, dopóki dziecko nie zostanie ukończone ... ale dziecko czeka, aż główny wątek coś zrobi ...
Żadne z nich nie może zrobić żadnego postępu.
Problem, przed którym stoisz, jest bardzo dobrze udokumentowany, a w rzeczywistości był wielokrotnie wspomniany w prezentacjach WWDC i wielu dokumentach.
Powinieneś NIGDY zadzwonić pod numer performBlockAndWait
w kontekście podrzędnym.
Fakt, że uszło ci to na sucho w przeszłości, jest tylko "przypadkiem", ponieważ w ogóle nie powinien działać w ten sposób.
W rzeczywistości prawie nie trzeba dzwonić pod numer performBlockAndWait
.
Powinieneś naprawdę przyzwyczaić się do programowania asynchronicznego. Oto, jak zaleciłbym przepisanie tego testu, a cokolwiek to jest, wywołało ten problem.
Najpierw przepisać sprowadzić tak to działa asynchronicznie ...
- (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);
}
}];
}
Wtedy również zmienić kod, który wywołuje sprowadzić do czegoś takiego ...
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);
}
}];
Wydaje mi się, że w najnowszych pakietach SDK dla systemu iOS 9 występuje problem lub niekompatybilność, ale aby pomóc użytkownikom w zrozumieniu tego problemu, sprawmy, aby Twój post był bardziej przejrzysty. Po pierwsze, czy mógłbyś przesłać swój przykład do githuba? (Chrome nie pozwolił mi pobrać). Drugi: usuń wszystkie zmiany w strąkach zrobione przez ciebie. Usuń nieużywany kod, załóż przykład wyczyść. Krótka historia problemu polega na tym, że: blok wysyłki z kontekstem podrzędnym zablokuje go, jeśli 1) zostanie wywołany z -viewWillAppear: i 2) użyty obiekt zarządzany został użyty (wywołana usterka) przed wysłaniem (tak jak w NSLog: d. nazwa) Ten sam problem z bezpośrednim CoreDa – MikeR