Jak radzić sobie z instancjami Temporary NSManagedObject?

86

Muszę utworzyć NSManagedObjectinstancje, zrobić z nimi kilka rzeczy, a następnie wyrzucić je do kosza lub przechowywać w sqlite db. Problem polega na tym, że nie mogę tworzyć instancji NSManagedObjectniepołączonych z, NSManagedObjectContextco oznacza, że ​​muszę jakoś wyczyścić, gdy zdecyduję, że nie potrzebuję niektórych obiektów w mojej bazie danych.

Aby sobie z tym poradzić, utworzyłem magazyn w pamięci za pomocą tego samego koordynatora i umieszczam tam tymczasowe obiekty za pomocą assignObject:toPersistentStore.Now, jak mam się upewnić, że te tymczasowe obiekty nie dotrą do danych, które pobieram z wspólny dla kontekstu obu sklepów? A może muszę tworzyć osobne konteksty dla takiego zadania?


UPD:

Teraz myślę o stworzeniu osobnego kontekstu dla magazynu w pamięci. Jak przenosić obiekty z jednego kontekstu do drugiego? Używasz tylko [kontekstu insertObject:]? Czy to zadziała w tej konfiguracji? Jeśli wstawię jeden obiekt z wykresu obiektów, czy cały wykres również zostanie wstawiony w kontekst?

fspirit
źródło
To powinno być osobne pytanie, ponieważ oznaczyłeś je jako odpowiedź. Utwórz nowe pytanie i wyjaśnij, DLACZEGO uważasz, że potrzebujesz oddzielnego całego stosu danych podstawowych TYLKO do przechowywania w pamięci. Z przyjemnością omówię z Tobą to pytanie.
Marcus S. Zarra,
Sekcja UPD nie ma teraz znaczenia, ponieważ wybrałem inne podejście, zobacz mój ostatni komentarz do Twojej odpowiedzi.
fspirit

Odpowiedzi:

146

UWAGA: Ta odpowiedź jest bardzo stara. Zobacz komentarze do pełnej historii. Od tego czasu moje zalecenie uległo zmianie i nie zalecam już używania niepowiązanych NSManagedObjectinstancji. Obecnie zalecam używanie tymczasowych NSManagedObjectContextinstancji podrzędnych .

Oryginalna odpowiedź

Najłatwiej to zrobić, tworząc NSManagedObjectinstancje bez powiązanego pliku NSManagedObjectContext.

NSEntityDescription *entity = [NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:myMOC];
NSManagedObject *unassociatedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];

Następnie, gdy chcesz go zapisać:

[myMOC insertObject:unassociatedObject];
NSError *error = nil;
if (![myMoc save:&error]) {
  //Respond to the error
}
Marcus S. Zarra
źródło
6
Jeśli unassociatedObject ma odwołania do innych niepowiązanych obiektów, czy powinienem wstawiać je jeden po drugim, czy myMOC jest wystarczająco inteligentny, aby zebrać wszystkie odniesienia i wstawić je również?
fspirit
6
Jest wystarczająco sprytny, aby poradzić sobie również z relacjami.
Marcus S. Zarra
2
Podoba mi się, że to podejście pozwala traktować MO jak zwykłe obiekty danych, zanim zdecydujesz się je przechowywać, ale martwię się, jak „są obsługiwane” przez kontrakt CoreData, a zatem jak to jest przyszłościowe. Czy Apple wspomina o tym podejściu lub używa go gdzieś? Ponieważ jeśli nie, przyszła wersja systemu iOS może zmienić właściwości dynamiczne, aby zależały od MOC i przerwać to podejście. Dokumenty firmy apple nie są jasne w tej kwestii: podkreślają znaczenie kontekstu i wyznaczonego inicjatora, ale w dokumencie MO jest jedna wzmianka o treści „jeśli kontekst nie jest zerowa, to ...” sugerująca, że ​​zero może być w porządku
Rabarbar
41
Użyłem tego podejścia jakiś czas temu, ale zacząłem widzieć dziwne zachowanie i awarie, gdy zmodyfikowałem te obiekty i / lub utworzyłem dla nich relacje przed wstawieniem ich do MOC. Rozmawiałem o tym z inżynierem Core Data z WWDC i powiedział, że chociaż istnieje API dla niepowiązanych obiektów, zdecydowanie odradzał używanie go jako MOC w dużej mierze polegający na powiadomieniach KVO wysyłanych przez jego obiekty. Zasugerował użycie zwykłego obiektu NSObject do obiektów tymczasowych, ponieważ jest to znacznie bezpieczniejsze.
Adrian Schönig
7
Wydaje się, że nie działa to dobrze w systemie iOS 8, zwłaszcza w przypadku utrzymujących się relacji. Czy ktoś inny może to potwierdzić?
Janum Trivedi
40

iOS5 zapewnia prostszą alternatywę dla odpowiedzi Mike'a Wellera. Zamiast tego użyj podrzędnego NSManagedObjectContext. Eliminuje potrzebę korzystania z trampoliny przez NSNotificationCenter

Aby utworzyć kontekst potomny:

NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
childContext.parentContext = myMangedObjectContext;

Następnie utwórz obiekty, używając kontekstu potomnego:

NSManagedObject *o = [NSEntityDescription insertNewObjectForEntityForName:@"MyObject" inManagedObjectContext:childContext];

Zmiany są stosowane tylko po zapisaniu kontekstu potomnego. Aby odrzucić zmiany, po prostu nie zapisuj.

Nadal istnieje ograniczenie dotyczące relacji. tj. nie możesz tworzyć relacji z obiektami w innych kontekstach. Aby obejść ten problem, użyj objectID, aby pobrać obiekt z kontekstu potomnego. na przykład.

NSManagedObjectID *mid = [myManagedObject objectID];
MyManagedObject *mySafeManagedObject = [childContext objectWithID:mid];
object.relationship=mySafeManagedObject;

Uwaga, zapisanie kontekstu podrzędnego powoduje zastosowanie zmian w kontekście nadrzędnym. Zapisanie kontekstu nadrzędnego utrwala zmiany.

Pełne wyjaśnienie można znaleźć w sesji 214 wwdc 2012 .

Railwayparade
źródło
1
Dzięki za zasugerowanie tego! Napisałem demo testujące tę metodę w porównaniu z używaniem kontekstu zerowego i przynajmniej na OSX, to działało podczas wstawiania kontekstu zerowego
Vivek Gani
Co znajduje się mocw trzecim fragmencie? Czy to jest childContextczy myMangedObjectContext?
bugloaf
To jest childContext
railwayparade
to rozwiązanie jest lepsze niż posiadanie kontekstu zerowego.
Will Y
Ponieważ NSManagedObjectjuż zapewnia odpowiednie NSManagedObjectContext, możesz zautomatyzować wybór kontekstu: NSManagedObject* objectRelatedContextually = [objectWithRelationship.managedObjectContext objectWithID:objectRelated.objectID];a następnie objectWithRelationship.relationship = objectRelatedContextually;.
Gary
9

Prawidłowym sposobem osiągnięcia tego typu rzeczy jest użycie nowego kontekstu obiektu zarządzanego. Tworzysz kontekst obiektu zarządzanego z tym samym magazynem trwałym:

NSManagedObjectContext *tempContext = [[[NSManagedObjectContext alloc] init] autorelease];
[tempContext setPersistentStore:[originalContext persistentStore]];

Następnie dodajesz nowe obiekty, mutujesz je itp.

Kiedy przychodzi czas na zapisanie, musisz wywołać [tempContext save: ...] w tempContext i obsłużyć powiadomienie o zapisie, aby scalić to z oryginalnym kontekstem. Aby odrzucić obiekty, po prostu zwolnij ten tymczasowy kontekst i zapomnij o nim.

Kiedy więc zapiszesz kontekst tymczasowy, zmiany zostaną utrwalone w sklepie i po prostu musisz je przenieść z powrotem do głównego kontekstu:

/* Called when the temp context is saved */
- (void)tempContextSaved:(NSNotification *)notification {
    /* Merge the changes into the original managed object context */
    [originalContext mergeChangesFromContextDidSaveNotification:notification];
}

// Here's where we do the save itself

// Add the notification handler
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(tempContextSaved:)
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:tempContext];

// Save
[tempContext save:NULL];
// Remove the handler again
[[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:NSManagedObjectContextDidSaveNotification
                                              object:tempContext];

Jest to również sposób, w jaki należy obsługiwać wielowątkowe podstawowe operacje na danych. Jeden kontekst na wątek.

Jeśli chcesz uzyskać dostęp do istniejących obiektów z tego tymczasowego kontekstu (w celu dodania relacji itp.), Musisz użyć identyfikatora obiektu, aby uzyskać nową instancję, taką jak ta:

NSManagedObject *objectInOriginalContext = ...;
NSManagedObject *objectInTemporaryContext = [tempContext objectWithID:[objectInOriginalContext objectID]];

Jeśli spróbujesz użyć NSManagedObjectw złym kontekście, podczas zapisywania wystąpią wyjątki.

Mike Weller
źródło
Tworzenie drugiego kontekstu tylko w tym celu jest bardzo marnotrawne, ponieważ wstawanie NSManagedObjectContextjest kosztowne zarówno pod względem pamięci, jak i procesora. Zdaję sobie sprawę, że pierwotnie było to w niektórych przykładach Apple, ale zaktualizowali i poprawili te przykłady.
Marcus S. Zarra
2
Firma Apple nadal używa tej techniki (tworzy drugi kontekst obiektu zarządzanego) dla przykładowego kodu CoreDataBooks.
nevan king
1
Uwaga Firma Apple zaktualizowała CoreDataBooks, w rzeczywistości nadal używa dwóch kontekstów, ale teraz drugi kontekst jest dzieckiem pierwszego. Ta technika jest omawiana (i zalecana) w prezentacji 303 WWDC 2011 (co nowego w danych podstawowych w iOS) i jest wspomniana tutaj (z dużo, DUŻO prostszym kodem do scalania zmian w górę) stackoverflow.com/questions/9791469/ ...
Rabarbar
4
„Tworzenie drugiego kontekstu tylko w tym celu jest bardzo marnotrawne, ponieważ tworzenie NSManagedObjectContext jest kosztowne zarówno pod względem pamięci, jak i procesora”. . Nie, nie jest. Zależności koordynatora magazynu trwałego (model obiektu zarządzanego i konkretne magazyny) nie są kontekstem. Konteksty są lekkie.
quellish
3
@quellish Agreed. Apple oświadczył w swoich ostatnich rozmowach dotyczących wydajności danych na WWDC, że tworzenie kontekstów jest bardzo lekkie.
Jesse
9

Tworzenie obiektów tymczasowych z kontekstu zerowego działa dobrze, dopóki nie spróbujesz stworzyć związku z obiektem, którego kontekst! = Nil!

upewnij się, że zgadzasz się z tym.

user134611
źródło
Nie zgadzam się z tym
Charlie
8

To, co opisujesz, jest dokładnie tym, do czego NSManagedObjectContextsłuży.

Z przewodnika programowania podstawowych danych: Podstawowe informacje o danych

Kontekst obiektu zarządzanego można traktować jako inteligentny notatnik. Kiedy pobierasz obiekty z trwałego magazynu, przenosisz tymczasowe kopie do notatnika, gdzie tworzą wykres obiektów (lub zbiór grafów obiektów). Następnie możesz dowolnie modyfikować te obiekty. Jeśli jednak faktycznie nie zapiszesz tych zmian, trwały magazyn pozostanie niezmieniony.

Oraz przewodnik programowania podstawowych danych: walidacja zarządzanych obiektów

To również stanowi podstawę idei kontekstu obiektu zarządzanego reprezentującego „notatnik” - generalnie można przenosić zarządzane obiekty do notatnika i edytować je w dowolny sposób, zanim ostatecznie zatwierdzisz zmiany lub je odrzucisz.

NSManagedObjectContextsą zaprojektowane tak, aby były lekkie. Możesz je tworzyć i odrzucać w dowolnym momencie - to stały koordynator sklepów i jego zależności są „ciężkie”. Z jednym stałym koordynatorem magazynu może być skojarzonych wiele kontekstów. W starszym, przestarzałym modelu ograniczania wątków oznaczałoby to ustawienie tego samego stałego koordynatora magazynu w każdym kontekście. Dziś oznaczałoby to połączenie zagnieżdżonych kontekstów z kontekstem głównym, który jest powiązany z trwałym koordynatorem magazynu.

Utwórz kontekst, utwórz i zmodyfikuj obiekty zarządzane w tym kontekście. Jeśli chcesz je utrwalić i zakomunikować te zmiany, zapisz kontekst. W przeciwnym razie odrzuć.

Próba stworzenia zarządzanych obiektów niezależnie od tego NSManagedObjectContextjest prowokacją kłopotów. Pamiętaj, że dane podstawowe to ostatecznie mechanizm śledzenia zmian dla wykresu obiektowego. Z tego powodu obiekty zarządzane są w rzeczywistości częścią kontekstu obiektu zarządzanego . Kontekst obserwuje ich cykl życia , a bez kontekstu nie wszystkie funkcje zarządzanego obiektu będą działać poprawnie.

quellish
źródło
6

W zależności od tego, jak korzystasz z tymczasowego obiektu, istnieją pewne zastrzeżenia dotyczące powyższych zaleceń. Mój przypadek użycia polega na tym, że chcę utworzyć obiekt tymczasowy i powiązać go z widokami. Gdy użytkownik zdecyduje się zapisać ten obiekt, chcę ustawić relacje z istniejącymi obiektami i zapisać. Chcę to zrobić, aby uniknąć tworzenia tymczasowego obiektu do przechowywania tych wartości. (Tak, mógłbym po prostu poczekać, aż użytkownik zapisze, a następnie pobrać zawartość widoku, ale umieszczam te widoki wewnątrz tabeli, a logika, aby to zrobić, jest mniej elegancka).

Opcje dla obiektów tymczasowych to:

1) (Preferowane) Utwórz obiekt tymczasowy w kontekście podrzędnym. To nie zadziała, ponieważ wiążę obiekt z interfejsem użytkownika i nie mogę zagwarantować, że metody dostępu do obiektów zostaną wywołane w kontekście podrzędnym. (Nie znalazłem dokumentacji, która mówi inaczej, więc muszę założyć.)

2) Utwórz obiekt tymczasowy bez kontekstu obiektu. To nie działa i powoduje utratę / uszkodzenie danych.

Moje rozwiązanie: rozwiązałem to, tworząc tymczasowy obiekt z zerowym kontekstem obiektu, ale kiedy zapisuję obiekt, zamiast wstawiać go jako nr 2, kopiuję wszystkie jego atrybuty do nowego obiektu, który tworzę w głównym kontekście. Utworzyłem metodę pomocniczą w mojej podklasie NSManagedObject o nazwie cloneInto: która pozwala mi łatwo kopiować atrybuty i relacje dla dowolnego obiektu.

greg
źródło
Tego właśnie szukam. Ale mam wątpliwości, jak poradzisz sobie z atrybutami związku?
Mani
1

Dla mnie odpowiedź Marcusa nie zadziałała. Oto, co zadziałało dla mnie:

NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:myMOC];
NSManagedObject *unassociatedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];

wtedy, jeśli zdecyduję się go zapisać:

[myMOC insertObject:unassociatedObjet];
NSError *error = nil;
[myMoc save:&error];
//Check the error!

Nie możemy też zapomnieć o jej wydaniu

[unassociatedObject release]
Lucas
źródło
1

Przepisuję tę odpowiedź dla Swifta, tak jak wszystkie podobne pytania do szybkiego przekierowania na to pytanie.

Możesz zadeklarować obiekt bez ManagedContext przy użyciu następującego kodu.

let entity = NSEntityDescription.entity(forEntityName: "EntityName", in: myContext)
let unassociatedObject = NSManagedObject.init(entity: entity!, insertInto: nil)

Później, aby zapisać obiekt, możesz wstawić go do kontekstu i zapisać.

myContext.insert(unassociatedObject)
// Saving the object
do {
    try self.stack.saveContext()
    } catch {
        print("save unsuccessful")
    }
}
Mitul Jindal
źródło