Jednoczesne i niezawodne korzystanie z podstawowych danych
Buduję swoją pierwszą aplikację na system iOS, która teoretycznie powinna być całkiem prosta, ale mam trudności z uczynieniem jej wystarczająco kuloodporną, aby czuć się pewnie przesyłając ją do App Store.
W skrócie, główny ekran ma widok tabeli, po wybraniu wiersza wskazuje na inny widok tabeli, który wyświetla informacje istotne dla wybranego wiersza w sposób mistrzowsko-szczegółowy. Dane podstawowe są pobierane jako dane JSON z usługi internetowej raz dziennie, a następnie buforowane w magazynie danych podstawowych. Dane poprzedzające ten dzień są usuwane, aby uniemożliwić nieograniczony rozwój pliku bazy danych SQLite. Wszystkie operacje utrwalania danych są wykonywane przy użyciu danych podstawowych, za pomocąNSFetchedResultsController
podstawa widoku tabeli szczegółów.
Problem, który widzę, polega na tym, że jeśli kilka razy szybko przełączasz się między ekranami głównym i szczegółowym, podczas gdy świeże dane są pobierane, analizowane i zapisywane, aplikacja całkowicie zawiesza się lub zawiesza. Wydaje się, że istnieje jakiś rodzaj wyścigu, może z powodu importowania danych Core Data w tle, podczas gdy główny wątek próbuje wykonać pobieranie, ale spekuluję. Miałem problem z przechwyceniem jakichkolwiek znaczących informacji o awariach, zazwyczaj jest to SIGSEGV głęboko w stosie danych podstawowych.
Poniższa tabela pokazuje rzeczywistą kolejność zdarzeń, które mają miejsce, gdy załadowany jest kontroler widoku tabeli szczegółów:
Main Thread Background Thread viewDidLoad Get JSON data (using AFNetworking) Create child NSManagedObjectContext (MOC) Parse JSON data Insert managed objects in child MOC Save child MOC Post import completion notification Receive import completion notification Save parent MOC Perform fetch and reload table view Delete old managed objects in child MOC Save child MOC Post deletion completion notification Receive deletion completion notification Save parent MOC
Po wyzwoleniu bloku zakończenia AFNetworking, gdy nadejdą dane JSON, zagnieżdżonyNSManagedObjectContext
jest tworzony i przekazywany do obiektu „importera”, który analizuje dane JSON i zapisuje obiekty w magazynie danych podstawowych. Importer wykonuje za pomocą nowegoperformBlock
metoda wprowadzona w iOS 5:
NSManagedObjectContext *child = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[child setParentContext:self.managedObjectContext];
[child performBlock:^{
// Create importer instance, passing it the child MOC...
}];
Obiekt importera obserwuje własne MOCNSManagedObjectContextDidSaveNotification
a następnie publikuje własne powiadomienie obserwowane przez kontroler widoku tabeli szczegółów. Gdy to powiadomienie jest zaksięgowane, kontroler widoku tabeli wykonuje własny zapis (MOC).
Używam tego samego podstawowego wzoru z obiektem „deleter” do usuwania starych danych po zaimportowaniu nowych danych na dany dzień. Dzieje się to asynchronicznie po pobraniu nowych danych przez pobrany kontroler wyników, a widok tabeli szczegółów został ponownie załadowany.
Jedną rzeczą, której nie robię, jest obserwowanie wszelkich powiadomień o scalaniu lub blokowanie dowolnych kontekstów obiektów zarządzanych lub koordynatora trwałego sklepu. Czy powinienem to robić? Nie jestem pewien, jak poprawnie to zaprojektować, więc byłbym wdzięczny za każdą radę.