2013-01-11 14 views
24

L'app a volte inserisce oggetti nel contesto dell'oggetto gestito che non devono essere necessariamente salvati. Ad esempio, quando lancio una modal 'Aggiungi entità', creo un oggetto gestito e lo assegno alla modale. Se l'utente salva da quella modale, salvo il contesto. Se cancella, cancello l'oggetto e non è necessario salvare.Implementazione corretta di genitore/figlio NSManagedObjectContext

Ora ho introdotto una funzione di "importazione" che passa alla mia app (utilizzando uno schema URL) e aggiunge un'entità. Poiché una di queste modali potrebbe essere aperta, non è sicuro salvare il contesto a questo punto. L'oggetto temporaneo creato per il modale verrà salvato, anche se l'utente annulla, e non vi è alcuna garanzia che l'eliminazione (dall'operazione di annullamento) verrà salvata in un secondo momento - l'utente potrebbe uscire dall'app.

Analogamente, non posso semplicemente salvare ogni volta che la mia app si chiude. Se la modale è aperta in quel punto, l'oggetto temporaneo verrà salvato in modo errato.

Per risolvere questo problema, sto tentando di utilizzare un contesto figlio, come illustrato in here. Dopo aver letto tutto quello che ho potuto trovare su SO, ho davanzale avere un paio di domande:

  1. Quale tipo di concorrenza dovrei utilizzare per ogni contesto? Ricorda che non sto facendo questo per i vantaggi di prestazioni/threading. So che non posso usare NSConfinementConcurrencyType per il contesto principale se deve avere contesti secondari, ma non sono sicuro di quale delle altre due opzioni sia più adatta. Per il contesto del bambino, ha bisogno di corrispondere? O posso usare anche il tipo di confinamento qui? Ho provato una varietà di combinazioni e tutto sembra funzionare bene, ma mi piacerebbe sapere quale è appropriato per le mie esigenze.

  2. (side issue) Perché posso farlo funzionare solo se utilizzo un iVar di classe? Ho pensato che dovrei essere in grado di dichiarare il contesto temporaneo nel metodo in cui è stato creato e in seguito fare riferimento ad esso usando entity.managedObjectContext. Ma sembra essere nulla prima che io venga ad accedervi? Questo è corretto se invece utilizzo un iVar per mantenere il riferimento.

  3. Qual è il modo corretto o di propagare la modifica al contesto principale? Ho visto vari commenti utilizzando diverse implementazioni bloccate a blocchi su ciascuno dei contesti. Dipende dal mio tipo di concorrenza? La mia versione attuale è:

    //save the new entity in the temporary context 
    NSError *error = nil; 
    if (![myObject.managedObjectContext save:&error]) {NSLog(@"Error - unable to save new object in its (temporary) context");} 
    
    //propogate the save to the main context 
    [self.mainContext performBlock:^{ 
        NSError *error2 = nil; 
        if (![self.mainContext save:&error2]) {NSLog(@"Error - unable to merge new entity into main context");} 
    }]; 
    
  4. Quando il mio utente salva, invia il suo delegato (il mio principale controller di vista) un messaggio. Il delegato riceve l'oggetto che è stato aggiunto e deve individuare lo stesso oggetto nel contesto principale. Ma quando lo cerco nel contesto principale, non viene trovato. Il contesto principale contiene contiene l'entità - Posso registrare i suoi dettagli e confermare che è lì - ma l'indirizzo è diverso? Se questo dovrebbe accadere (perché?), Come posso individuare l'oggetto aggiunto nel contesto principale dopo il salvataggio?

Grazie per qualsiasi intuizione. Ci scusiamo per una lunga domanda in più parti, ma ho pensato che qualcuno avrebbe probabilmente affrontato tutti questi problemi in precedenza.

risposta

45

Il modello MOC padre/figlio è una funzionalità davvero potente di Core Data. Semplifica incredibilmente il vecchio problema di concorrenza con il quale abbiamo avuto a che fare. Tuttavia, come hai affermato, la concorrenza non è il tuo problema. Per rispondere alle vostre domande:

  1. Tradizionalmente, si utilizza la NSMainQueueConcurrencyType per la NSManagedObjectContext associato al thread principale, e NSPrivateQueueConcurrencyType s per contesti bambino. Il contesto figlio non ha bisogno di corrispondere al suo genitore. Il NSConfinementConcurrencyType è ciò che viene impostato automaticamente a tutti gli NSManagedObjectContext se non si specifica un tipo. È fondamentalmente il tipo "Io gestirò i miei thread per i dati di base".
  2. Senza vedere il tuo codice, la mia ipotesi sarebbe l'ambito entro il quale si crea il contesto secondario e viene ripulito.
  3. Quando si utilizza il modello di contesto genitore/figlio, è necessario utilizzare i metodi di blocco. Il più grande vantaggio dell'utilizzo dei metodi di blocco è che il sistema operativo gestirà l'invio delle chiamate al metodo ai thread corretti. È possibile utilizzare performBlock per l'esecuzione asincrona o performBlockAndWait per l'esecuzione sincrona.

si può usare questa come ad esempio:

- (void)saveContexts { 
    [childContext performBlock:^{ 
     NSError *childError = nil; 
     if ([childContext save:&childError]) { 
      [parentContext performBlock:^{ 
       NSError *parentError = nil; 
       if (![parentContext save:&parentError]) { 
        NSLog(@"Error saving parent"); 
       } 
      }]; 
     } else { 
      NSLog(@"Error saving child"); 
     } 
    }]; 
} 

Ora, è necessario tenere a mente che le modifiche apportate nel contesto del bambino (ad esempio entità inserite) non saranno disponibili al contesto genitore fino risparmi. Per il contesto figlio, il contesto genitore è l'archivio persistente. Quando si salva, si passa tali modifiche al genitore, che può quindi salvarle nell'archivio permanente effettivo. Salva le modifiche propogate di un livello.D'altra parte, il recupero in un contesto bambino tirerà dati verso il basso attraverso tutti i livelli (attraverso il genitore e nel bambino)

  1. È necessario utilizzare una qualche forma di objectWithID sul managedObjectContext . Sono il modo più sicuro (e davvero unico) per passare oggetti tra i contesti. Come ha detto Tom Harrington nei commenti, potresti voler usare existingObjectWithID:error: perché objectWithID: restituisce sempre un oggetto, anche se si passa un ID non valido (che può portare ad eccezioni). Per ulteriori dettagli: Link
+0

Sulla base della risposta di Andy sopra, ho testato una versione senza utilizzare i blocchi per il salvataggio e finora funziona (per non dire che è corretta). Qual è lo scopo di utilizzare l'implementazione del blocco e quali problemi potrebbero sorgere se continuo a chiamare semplicemente save nei contesti? –

+0

Per quanto riguarda il n. 2, che suppongo non dovrebbe appartenere alla domanda in realtà - ho ancora un riferimento valido al mio oggetto inserito - non mantiene il suo moc vivo? –

+1

Come ho già detto al punto 3, lo scopo dell'utilizzo dei metodi basati su blocchi è quello di garantire che i messaggi vengano inviati ai thread corretti. Questo è davvero importante solo se stai facendo un lavoro con i dati core multithread. Per inciso, puoi usare anche performBlock per lavorare in modo asincrono (se il MOC è su un thread diverso. Chiamare performBlock su un MOC sul thread principale invia solo il codice al thread principale) – jmstone617

5
  1. Se si utilizza il modello di padre/figlio, di solito si dichiara contesto genitore con NSMainQueueConcurrencyType ei contesti bambino con NSPrivateQueueConcurrencyType. NSConfinementConcurrencyType viene utilizzato per il modello di threading classico.

  2. Se si desidera mantenere il contesto, in qualche modo è necessario un forte riferimento ad esso.

  3. È sufficiente chiamare il metodo di salvataggio nel contesto figlio per inserire le modifiche nel contesto padre, se si desidera mantenere i dati, si chiama anche il salvataggio sul contesto padre. Non è necessario farlo entro un blocco.

  4. Esistono diversi metodi per ottenere un oggetto specifico da un contesto. I cant voi quale dico funzionerà nel tuo caso, provarli:

    - objectRegisteredForID:

    - objectWithID:

    - existingObjectWithID:error:

+0

Grazie - qualsiasi sfondo sul motivo per cui non è necessario utilizzare i blocchi? Informazioni contrastanti viste altrove. –

+1

Se si utilizza una delle opzioni di concorrenza basate sulla coda, è necessario utilizzare le chiamate di blocco per essere sicuri. Saltarli spesso funziona ma non sempre. L'unica eccezione è se si utilizza 'NSMainQueueConcurrencyType' ** e ** si sta effettuando la chiamata dalla coda principale. In tal caso, è facoltativo. –

+0

I contesti nop figlio non sono progettati per il threading – malhal

6

ho avuto problemi simili e qui ci sono le risposte ad alcune parti delle vostre domande- 1. Dovreste essere in grado di utilizzare il tipo di concorrenza NSPrivateQueueConcurrencyType o NSMainQueueConcurrencyType 2. Dire di aver creato d un contesto temporaneo tempContext con il contesto genitore mainContext (questo presuppone iOS5). In questo caso, si può semplicemente spostare l'oggetto gestito tempContext-mainContext by-

object = (Object *)[mainContext objectWithID:object.objectID]; 

È possibile quindi salvare la mainContext stesso.

Forse anche,

[childContext reset]; 

se si desidera ripristinare il contesto temporanea.

Problemi correlati