2015-03-27 25 views
6

Quello che sto cercando di fare:risparmio NSManagedObjectContext senza colpire il thread principale

  • eseguire sincronizzazione in background con un'API web senza congelare l'interfaccia utente. Sto usando MagicalRecord ma non è proprio specifico.
  • assicurarsi che sto utilizzando contesti & come correttamente

Che la mia domanda è in realtà: è la mia comprensione corretta? Più un paio di domande alla fine.

Così, i contesti messi a disposizione dalla MagicalRecord sono:

  • MR_rootSavingContext di PrivateQueueConcurrencyType che viene utilizzato a persistere i dati al negozio, che è un processo lento
  • MR_defaultContext di MainQueueConcurrencyType
  • e per lo sfondo si vorrebbe lavorare con un contesto generi Ted da MR_context(), che è un figlio di MR_defaultContext ed è di PrivateQueueConcurrencyType

Ora, per salvare in modo asincrono, abbiamo due opzioni:

  • MR_saveToPersistentStoreWithCompletion() che salverà fino a MR_rootSavingContext e scrivere sul disco
  • MR_saveOnl ySelfWithCompletion() che salverà solo fino al contesto genitore (io? MR_defaultContext per un contesto creato con MR_context)

Da lì, ho pensato che avrei potuto fare quanto segue (chiamiamolo Tentativo # 1) senza congelare l'interfaccia utente:

let context = NSManagedObjectContext.MR_context() 
for i in 1...1_000 { 
    let user = User.MR_createInContext(context) as User 
    context.MR_saveOnlySelfWithCompletion(nil) 
} 
// I would normally call MR_saveOnlySelfWithCompletion here, but calling it inside the loop makes any UI block easier to spot 

Ma, la mia ipotesi era sbagliato. I looked into MR_saveOnlySelfWithCompletion e ho visto che si basa su

[self performBlock:saveBlock]; 

che according to Apple Docs

esegue in modo asincrono un dato blocco sulla coda del ricevitore.

Quindi ero un po 'perplesso, dal momento che mi aspetterei di non bloccare l'interfaccia utente a causa di ciò.

Poi ho provato (chiamiamolo Tentativo # 2)

let context = NSManagedObjectContext.MR_context() 
for i in 1...1_000 { 
    let user = User.MR_createInContext(context) as User 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {() -> Void in 
     context.MR_saveOnlySelfWithCompletion(nil) 
    } 
} 

E questo fa il lavoro, ma non mi sembra giusto.

Poi ho trovato qualcosa nel release notes of iOS 5.0

Quando l'invio di messaggi a un contesto creato con un'associazione di coda , è necessario utilizzare il performBlock: o performBlockAndWait: metodo se il codice non è già in esecuzione su quel coda (per il tipo di coda principale ) o nell'ambito di un'attivazione PerformBlock ... (per il tipo di coda privata). All'interno dei blocchi passati ai metodi , è possibile utilizzare liberamente i metodi di NSManagedObjectContext.

Quindi, sto supponendo che:

  • Tentativo # 1 si blocca l'interfaccia utente in quanto realtà sto chiamando dalla coda principale e non nell'ambito di un performBlock
  • Tentativo # 2 opere, ma sto creando ancora un altro thread mentre il contesto ha già un proprio thread in background

Così of course che cosa devo fare è l'uso saveWithBlock:

01.235.164,106174 millions
MagicalRecord.saveWithBlock { (localContext) -> Void in 
    for i in 1...1_000 { 
     User.MR_createInContext(context) 
    } 
} 

Esegue l'operazione on a direct child of MR_rootSavingContext che è di PrivateQueueConcurrencyType. Grazie a rootContextChanged, qualsiasi modifica che risale a MR_rootSavingContext sarà disponibile per MR_defaultContext.

così sembra che:

  • MR_defaultContext è il contesto perfetto quando si tratta di visualizzazione dei dati
  • modifiche sono preferibilmente fatte in un MR_context (figlio di MR_defaultContext)
  • attività in esecuzione lunghi come ad esempio un la sincronizzazione del server viene preferibilmente eseguita utilizzando saveWithBlock

Ciò che ancora non si ottiene è come lavorare con MR_save [...] WithCompletion(). Lo userei su MR_context ma visto che ha bloccato il thread principale nei miei casi di test, non vedo quando diventa rilevante (o cosa mi è mancato ...).

Grazie per il vostro tempo :)

risposta

5

Ok, sto raramente utilizzo dei record magici ma dato che hai detto che domanda è più generale io tenterò una risposta.

qualche teoria: quando si crea un contesto si passa un indicatore come se si desidera essere vincolato sulla principale o un thread in background

let context = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType) 

Con "Bound" intendiamo che un thread fa riferimento a il contesto internamente. Nell'esempio sopra un nuovo thread è creato e di proprietà del contesto. Questo thread non viene utilizzato automaticamente, ma deve essere chiamato esplicitamente come:

context.performBlock({() -> Void in 
    context.save(nil) 
    return 
}); 

Così il vostro codice con 'dispatch_async' è sbagliato, perché il filo del contesto è destinato a può fare riferimento solo dal contesto stesso (è un thread privato).

Quello che devi dedurre da quanto sopra è che se il contesto è legato al thread principale, chiamando performBlock dal thread principale sarà non fare nulla di diverso che chiamare i metodi di contesto dritto.

Per commentare i tuoi punti elenco, alla fine:

  • MR_defaultContext è il contesto perfetto quando si tratta di visualizzazione dei dati: Un NSManagedObject deve essere accessibile dal contesto si creato così è in realtà il contesto solo da cui è possibile alimentare l'interfaccia utente .

  • modifiche sono preferibilmente fatte in un MR_context (figlio di MR_defaultContext): Le modifiche non sono costosi e si dovrebbe seguire la regola di cui sopra. Se stai chiamando una funzione che modifica le proprietà di un NSManagedObject dal thread principale (come ad esempio toccando un pulsante) devi aggiornare il contesto principale. D'altro canto, i risparmi sono costosi e questo è il motivo per cui il tuo contesto principale non deve essere collegato direttamente a un archivio persistente , ma è sufficiente spingerne le modifiche su un contesto root con uno sfondo concorrente proprietario di un archivio persistente.

  • attività di lunga durata come una sincronizzazione del server vengono preferibilmente eseguite utilizzando saveWithBlock Sì.

Ora, Nel tentativo 1

for i in 1...1_000 { 
    let user = User.MR_createInContext(context) as User 
} 
context.MR_saveOnlySelfWithCompletion(nil) 

Non c'è bisogno di risparmiare per ogni creazione di oggetti. Anche se l'interfaccia utente non è bloccata, è uno spreco.

Informazioni su MR_context. Nella documentazione per i record magici non riesco a vedere un 'MR_context', quindi mi chiedo se sia un metodo rapido per accedere al contesto principale. Se è così, bloccherà.

+0

Grazie, risposta interessante. "Non è necessario salvare per ogni creazione di oggetti." - Effettivamente, l'ho fatto per assicurarmi che qualsiasi blocco sul thread principale fosse facile da individuare, avrei dovuto dirlo. MR_saveOnlySelfWithCompletion chiama internBlock internamente, ecco perché non capisco perché l'interfaccia utente sia bloccata. – Nycen

+0

performBlock NON garantisce che verrà eseguito su un thread in background ma sul thread il contesto è vincolato. Se questo è il contesto principale, bloccherà normalmente. Fondamentalmente, pensa che mentre sul thread principale, chiamare un metodo di contesto all'esterno o all'interno di performBlock è lo stesso! Quindi assicurati che il tuo contesto non sia il principale. (Ho modificato la mia risposta) –

+1

Mentre la tua risposta è fantastica, non sarei d'accordo con "Non c'è bisogno di risparmiare per ogni creazione di oggetti". Nella maggior parte dei casi questo può essere vero, ma se quel particolare contesto che stai modificando deve spingere le modifiche frequentemente in altri contesti, potrebbe esserci una necessità. Così come altri, che non puoi prevedere :) – Tim

Problemi correlati