10 principi fondamentali per usare CoreData senza far saltare la testa Ottobre 17, 2021 | Nessun commento o 10 lezioni di battaglia imparato lezioni CoreDataCoreData è un framework di gestione del grafo di oggettiCoreData può memorizzare i dati in diversi formatiUn paio di definizioni di terminiUsa NSPersistentContainerUsa le relazioniEvitare il più possibile il codice stringlyMantenete il vostro NSPersitentContainer.viewContext in sola letturaGuarda quei threadsUtilizza l’argomento di lancio per tracciare il contesto e il threadingUsa le query aggregateMantieni il codice relativo ai dati di base nelle categorieAdottate un modello di transazione per le vostre modifiche ai datiNon salvare il contesto dall’interno di un metodo della categoria del modello o 10 lezioni di battaglia imparato lezioni CoreData Olivier Destrebecq Follow 1 maggio, 2020 – 7 min read Ho usato CoreData su più progetti durante la mia carriera di sviluppatore su piattaforma Apple (la prima volta è stata in MacOS 10..5…), e nel corso degli anni ho imparato le cose nel modo più duro (aka: “sbattere la testa contro il muro per troppo tempo”). Così ho pensato di condividere il mio apprendimento con voi. Spero di risparmiarvi qualche livido sulla fronte. Nota che questa non è una guida su come impostare CoreData. Vuole essere un articolo con alcune buone pratiche che potete prendere per i vostri progetti. Ecco la rapida panoramica di Apple: “Usa Core Data per salvare i dati permanenti della tua applicazione per l’uso offline, per memorizzare nella cache i dati temporanei e per aggiungere funzionalità di annullamento alla tua applicazione su un singolo dispositivo.” Per dare un po’ più di dettagli, CoreData è la tecnologia di Apple per salvare i tuoi dati strutturati localmente. Si usa l’editor del modello per definire la struttura dei dati proprio come un editor di schema di database, poi si possono usare quegli oggetti nel codice per memorizzare i dati. CoreData è un framework di gestione del grafo di oggetti Quello che significa è che quando si crea il modello, si definiscono gli oggetti e le loro relazioni. Per esempio, se hai un’entità Company e un’entità Employee nel tuo modello di dati, queste due entità saranno collegate da una relazione che le collega. Probabilmente si chiama employees sull’entità Company e employer sull’entità Employee. Aggiungete semplicemente la proprietà di relazione sulle entità e questo è tutto. Non c’è bisogno di creare una tabella di unione come in un database. La bellezza di CoreData rispetto a un database tradizionale è che quando fai una query per ottenere la società X, sarai in grado di ottenere i dipendenti direttamente accedendo semplicemente a company.employees. Non c’è bisogno di costruire una join query o di ri-interrogare. CoreData può memorizzare i dati in diversi formati Sì, hai sentito bene, CoreData può salvare i tuoi dati in diversi formati/luoghi. Ognuno ha i suoi pro e contro. Store binario Store SQLite Store in memoria Puoi leggere questo articolo per avere i pro e i contro di ognuno. Io uso sempre SQLite, ma potrei vedere come un negozio in memoria sarebbe super veloce. Un paio di definizioni di termini NSManagedObject: questa è la classe base che rappresenta gli oggetti che hai memorizzato in CoreData. Richiesta di Fetch: questo è l’equivalente di una query nel mondo di CoreData. È rappresentata dalla classe NSFetchedObject. Usa NSPersistentContainer Fino a iOS 10.0, impostare lo stack CoreData comportava un bel po’ di codice boilerplate. Dovevate cavarvela da soli per definire la strategia da usare per gestire l’uso multithread di CoreData. Con NSPersistentContainer, Apple incapsula la maggior parte del codice boilerplate. Vi risparmia il dibattito su come impostare al meglio il vostro stack per prestazioni ottimali. Questo articolo contiene tutte le informazioni necessarie per usarlo correttamente. Usa le relazioni Lo dico sempre perché in due occasioni, per progetti che ho ereditato, il modello CoreData era impostato come un database. Non erano definite relazioni. Invece, l’ID dell’oggetto correlato era memorizzato e quindi era necessaria un’ulteriore richiesta di fetch per ottenere l’oggetto correlato. Penso che molti sviluppatori si avvicinino a CoreData come a un database e commettono questo errore. Questo finisce per rendere l’intero progetto molto più complesso e molto più lento. Seguite questo articolo per iniziare. Evitare il più possibile il codice stringly Il codice stringly si riferisce al codice che utilizza stringhe per accedere ai dati. Su iOS, avete alcune API stringly: UserDefaults, CoreData sono alcune che mi vengono in mente. Se torniamo all’esempio dell’azienda, qui sotto ci sono due opzioni per creare un’azienda e impostare il suo nome: //stringly version to create and edit a companylet company = NSEntityDescription.insertNewObject(forEntityName: "Company", in: managedObjectContext)company.set("my less than great corporation", forKey: "name")//non stringly versionlet company = Company(in: managedObjectContext)company.name = "My super corporation" A prima vista, entrambe queste opzioni funzionano, compilano, non c’è niente di sbagliato con queste… Il problema con il codice stringly è che il compilatore non può aiutarti. Immaginate di dover accedere al name dell’azienda in più punti del codice, e realisticamente ne avrete bisogno di almeno due: uno per impostarlo e uno per leggerlo, ora dovete digitare quella stringa name in due posti. Certo puoi creare una variabile per memorizzare la stringa e usare quella variabile, ma questo richiede uno sforzo extra, non sarà digitato controllato e sappiamo tutti che gli sviluppatori sono pigri… Quindi di solito lascia quella stringa in più posti. Poi vuoi rinominarla e devi darle la caccia con la ricerca e la sostituzione… Se ne sbagli una, la tua app andrà in crash. Ecco l’elenco delle caratteristiche del compilatore di cui non beneficerai con la sintassi stringly: auto-completamento refactor avviso del compilatore in caso di errori di ortografia errore del compilatore se rimuovi/rinomina questa entità o proprietà nel tuo modello Per poter usare la versione nondel codice dovrai semplicemente chiedere al compilatore di generare classi per le tue entità (puoi seguire le istruzioni di Apple sotto “Select a Code Generation Option”). Questo è ora il default quando si crea una nuova Entità. Mantenete il vostro NSPersitentContainer.viewContext in sola lettura Per ragioni di performance, non volete eseguire lunghe operazioni sul viewContext, poiché esso verrà eseguito sul thread principale e potenzialmente bloccherà l’UI. La soluzione è di prendere l’abitudine di fare modifiche ai vostri oggetti solo all’interno di un blocco che programmate con il metodo NSPersistentContainer.performBackgroundTask(). Quando salvi le tue modifiche in quel blocco, queste verranno fuse di nuovo in NSPersitentContainer.viewContext e potrai aggiornare la tua UI. Guarda quei threads Uno dei principali problemi con CoreData è usarlo in modo thread-safe. Anche se una volta che hai capito il concetto, è abbastanza facile. Quello che si riduce a è che qualsiasi oggetto dati che ottieni da CoreData può essere usato solo sul thread a cui è collegato il managedobjectContext dell’oggetto. Un modo per assicurarsi di usare il thread giusto per accedere al vostro oggetto è quello di usare il NSManagedObjectContext.performBlockAndWait che assicurerà che il vostro codice sia usato sul thread corretto. Una volta in quel blocco, dovrete usare NSManagedObject.objectID e NSManagedObjectContext.existingObject(withId:) per passare l’oggetto attraverso i thread e il contesto. Se non lo fate, molto probabilmente finirete con dei deadlock. Immaginate: Si programma un compito dal thread principale con Nel caso in cui qualcosa programmato dal thread principale NSManagedObjectContext.performBlockAndWait(). A questo punto, il thread principale è bloccato in attesa del completamento di questo blocco. In questo blocco, si passa l’oggetto foo dal contesto principale al metodo bar() e lo si dispensa ad un altro thread. Per essere un buon cittadino CoreData bar() otterrà il contesto attaccato a pippo e spedirà il suo compito usando foo.managedObjectContext.performBlockAndWait() Boom, sei in stallo perché questo contesto viene eseguito solo sul thread principale e il thread principale sta aspettando questo completo. Un’altra convenzione che aiuta è il passaggio di oggetti tra i controllori di vista solo se esistono nel contesto del thread principale. Utilizza l’argomento di lancio per tracciare il contesto e il threading Se segui i suggerimenti di cui sopra, questo dovrebbe limitare le possibilità che un’istanza NSManagedObject venga chiamata sul thread sbagliato. Ma questo può ancora accadere. Quindi assicurati, quando sei in sviluppo, di attivare l’asserzione di debug del thread -com.apple.CoreData.ConcurrencyDebug 1 Questo articolo ti dirà esattamente come farlo e ti parlerà di altri flag che puoi impostare per un maggiore debug. Usa le query aggregate CoreData offre groupBy, sum, e count come funzioni aggregate che possono essere eseguite durante una query. L’impostazione può essere un po’ complessa, ma ne vale la pena. L’alternativa è creare una query per ottenere tutti gli oggetti che corrispondono ai tuoi criteri e poi eseguire un ciclo for attraverso tutte le entità e fare i calcoli da soli. Più facile da codificare, ma molto meno efficiente. Quando esegui una richiesta di fetch aggregato, CoreData la traduce in una query di database ed esegue i calcoli direttamente a livello di database. Lo rende super veloce. Questo articolo è una buona introduzione su come farlo. Mantieni il codice relativo ai dati di base nelle categorie Una volta che hai la tua sottoclasse NSManagedObject creata da Xcode, molto probabilmente troverai che hai del codice che dovrebbe essere nella classe che rappresenta il tuo oggetto (per calcolare i valori, importare i dati dal tuo server, ecc). Ma hai chiesto a CoreData di creare le classi per te… Quindi dovrai creare una categoria per l’oggetto che ha bisogno di un comportamento extra. Metteteci solo il codice relativo a CoreData in modo da poterlo mantenere pulito. Adottate un modello di transazione per le vostre modifiche ai dati Potrete modificare i vostri oggetti quanto volete, ma per persistere queste modifiche dovete chiamare NSManagedObjectContext.save(). Vi troverete rapidamente a dimenticare di chiamare save() e avrete delle modifiche penzolanti nel vostro contesto. A questo punto tutte le scommesse sono annullate, la prossima volta che chiamerete save(), non sarete sicuri di cosa state commettendo. Adottate un modello di transazione per evitare di lasciarvi dietro un contesto sporco. Per farlo, create una categoria su NSPersistentContainer in questo modo: extension NSPersistentContainer {@objc func performBackgroundSaveTask(_ block: @escaping (NSManagedObjectContext) -> Void) {performBackgroundTask { (context: NSManagedObjectContext) inblock(context)context.save()}}} Assicuratevi d’ora in poi che ogni modifica che fate sia eseguita in un blocco programmato con performBackgroundSaveTask(). In questo modo hai la garanzia che le modifiche siano salvate quando le fai. Non salvare il contesto dall’interno di un metodo della categoria del modello Non chiamare mai save() dall’interno di una categoria di entità. Non c’è niente di peggio che chiamare un metodo sul vostro oggetto senza rendersi conto che salverà l’intero contesto con esso. Impegnare alcune modifiche che potresti non voler salvare. Chiamare save dovrebbe essere fatto nel punto in cui le modifiche all’oggetto sono iniziate. Articles