Scrivere codice di dati di base con le prestazioni in mente aiuta a preparare la tua app per il futuro. Il database potrebbe essere piccolo all’inizio, ma può facilmente crescere, con conseguente query lente e diminuzione dell’esperienza per l’utente.
Da quando ho iniziato a scrivere l’app Collect by WeTransfer nel 2017, ho scritto un sacco di codice relativo ai dati principali, toccandolo quasi ogni giorno. Con milioni di utenti che aggiungono molti contenuti, l’esecuzione del codice relativo ai dati di base è diventata un’abilità importante nel nostro team.
Nel corso degli anni, abbiamo sviluppato un sacco di intuizioni, che sono felice di condividere con voi attraverso 5 suggerimenti che dovreste sapere.
- 1: Fai uso di un contesto oggetto gestito in background
- Importante: non passare le istanze NSManagedObject tra le code
- 2: Salva un contesto oggetto gestito solo se necessario
- Considera attentamente quando salvare le tue modifiche
- 3: Recupera solo ciò di cui hai bisogno
- 4: Utilizzare i limiti di recupero
- 5: Elimina molti oggetti contemporaneamente usando un NSBatchDeleteRequest
- 6: Sapere come eseguire il debug del codice Core Data
- Conclusione
1: Fai uso di un contesto oggetto gestito in background
Una cosa che non abbiamo fatto fin dall’inizio è fare uso di un contesto oggetto gestito in background. Abbiamo utilizzato il contesto della vista solo per eseguire qualsiasi attività relativa ai dati principali: inserimento di nuovi contenuti, eliminazione di contenuti, recupero di contenuti, ecc.
All’inizio, la nostra app era relativamente piccola. Fare solo uso del contesto di visualizzazione non era davvero un problema e non ha comportato alcuna penalità di prestazioni visibili relative ai dati principali. Ovviamente, una volta che la nostra app ha iniziato a crescere, ci siamo resi conto che il contesto della vista era associato alla coda principale. Le query lente hanno bloccato la nostra interfaccia utente e la nostra app è diventata meno rispondente.
In generale, la migliore pratica è eseguire l’elaborazione dei dati su una coda in background in quanto può essere intensiva della CPU. Esempi come l’importazione di JSON nei dati principali potrebbero altrimenti bloccare il contesto della vista e causare mancanza di risposta nell’interfaccia utente.
La soluzione consiste nell’utilizzare un contesto oggetto gestito in background. Le API più recenti semplificano la creazione di un nuovo contesto dal contenitore persistente:
let backgroundContext = persistentContainer.newBackgroundContext()
Consiglio questo metodo sull’inizializzatore NSManagedObjectContext(concurrenyType:)
poiché verrà automaticamente associato a NSPersistentStoreCoordinator
e verrà impostato per consumare anche le trasmissioni NSManagedObjectContextDidSave
. Ciò mantiene il contesto di sfondo sincronizzato con il contesto della vista.
È possibile salvare questo contesto in background su una sottoclasse contenitore persistente personalizzata. In questo modo, puoi riutilizzare il tuo contesto di sfondo e devi solo gestire due contesti. Ciò mantiene la struttura dei dati di base semplice da comprendere e impedisce di avere più contesti non sincronizzati.
Se devi solo utilizzare il contesto di sfondo in pochi punti, puoi anche decidere di utilizzare il metodo performBackgroundTask(_:)
che crea un contesto di sfondo sul posto:
persistentContainer.performBackgroundTask { (backgroundContext) in // .. Core Data Code}
Tuttavia, questo metodo crea un nuovo NSManagedObjectContext
ogni volta che viene richiamato. Potresti prendere in considerazione l’utilizzo del contesto di sfondo condiviso se stai inviando più spesso a un contesto di sfondo.
Importante: non passare le istanze NSManagedObject tra le code
La scrittura di codice dati Core multi-thread è molto più complessa rispetto all’utilizzo di un singolo contesto di visualizzazione. La ragione di ciò è che non puoi semplicemente passare un NSManagedObject
istanziato da un contesto di visualizzazione a un contesto di sfondo. In questo modo si tradurrebbe in un crash e potenziale danneggiamento dei dati.
Quando è necessario spostare un oggetto gestito da una coda all’altra, è possibile utilizzare NSManagedObjectID
che è thread-safe:
let managedObject = NSManagedObject(context: persistentContainer.viewContext)backgroundContext.perform { let object = try? backgroundContext.existingObject(with: managedObject.objectID)}
2: Salva un contesto oggetto gestito solo se necessario
Il salvataggio di un contesto oggetto gestito impegna tutte le modifiche correnti nell’archivio padre del contesto. Come puoi immaginare, questa non è un’operazione economica e dovrebbe essere utilizzata solo se necessario per garantire prestazioni nei dati principali.
Prima di tutto, è importante verificare se c’è anche qualcosa da salvare. Se non ci sono modifiche da eseguire, non c’è motivo di eseguire un salvataggio. Creando un metodo saveIfNeeded
ti permetti di creare facilmente un controllo per questo:
Considera attentamente quando salvare le tue modifiche
Oltre a usare saveIfNeeded
invece di save()
devi anche considerare se un salvataggio ha senso. Sebbene un contesto possa avere modifiche, non è sempre necessario eseguire il commit diretto di queste modifiche.
Ad esempio, se nel database sono importanti più elementi, è possibile salvare solo dopo aver importato tutti gli elementi nel contesto di sfondo. Un salvataggio è spesso seguito da aggiornamenti dell’interfaccia utente e più salvataggi uno dopo l’altro potrebbero facilmente comportare ricariche non necessarie. Oltre a ciò, tenere conto del fatto che le modifiche salvate in un contesto di sfondo vengono unite nel contesto della vista, bloccando anche la coda principale. Pertanto, sii consapevole!
3: Recupera solo ciò di cui hai bisogno
Il recupero dei dati è un’attività costosa e deve essere il più performante possibile per preparare la tua app per set di dati di grandi dimensioni. Il seguente codice è un errore spesso commesso:
Questo codice caricherà tutti gli oggetti inseriti in memoria mentre viene filtrato direttamente dopo per rimanere solo con il contenuto che ha un nome.
È molto più performante usare i predicati per recuperare solo gli oggetti necessari. Il filtro di cui sopra può essere scritto come seguito con un NSPredicate
:
Questo ha due vantaggi:
- Solo gli oggetti necessari vengono caricati in memoria
- Non è necessario eseguire iterazioni su tutti gli oggetti
I predicati sono molto flessibili e dovrebbero consentire di recuperare il set di dati desiderato nella maggior parte dei casi mantenendo le prestazioni nei dati principali.
4: Utilizzare i limiti di recupero
Seguendo l’esempio precedente è importante impostare i limiti di recupero quando si visualizzerà solo una parte del set di dati.
Ad esempio, supponiamo che siano necessari solo i primi 3 nomi di tutti gli elementi di contenuto. In questo caso, non sarebbe necessario caricare tutti gli elementi di contenuto con un nome in memoria. Potremmo impedirlo impostando un limite di recupero:
Questo codice restituirà solo i primi 3 elementi di contenuto con un nome.
5: Elimina molti oggetti contemporaneamente usando un NSBatchDeleteRequest
Invece di iterare su un set di dati eliminando ogni oggetto uno per uno è spesso più performante usare un NSBatchDeleteRequest
che funziona più velocemente mentre opera a livello SQL nell’archivio persistente stesso.
Puoi saperne di più sulle richieste di eliminazione batch nel mio post sul blog Usando NSBatchDeleteRequest per eliminare i batch nei dati principali.
6: Sapere come eseguire il debug del codice Core Data
Come con tutto il codice che scrivi, è importante sapere come ottimizzarlo ed eseguirlo una volta che non funziona come previsto. Ci sono molti modi di debug che sono meglio spiegati nel mio post sul blog dedicato: Core Data Debugging in Xcode usando gli argomenti di avvio.
Conclusione
La scrittura del codice di dati Core performant dall’inizio ti aiuta a preparare la tua app per i futuri set di dati di grandi dimensioni. Anche se la tua app potrebbe funzionare all’inizio, può facilmente rallentare una volta che il database e il modello crescono. Facendo uso di un contesto in background, richieste di recupero intelligenti e richieste di eliminazione batch, stai rendendo il tuo codice di dati core già più performante.
Se volete migliorare la vostra conoscenza Swift, ancora di più, controllare la pagina categoria Swift. Non esitate a contattarmi o tweet a me su Twitter se avete ulteriori suggerimenti o commenti.
Grazie!