10 kernprincipes om CoreData te gebruiken zonder je hoofd eraf te knallen oktober 17, 2021 | Geen reacties of 10 slag geleerde CoreData lessenCoreData is een object graph management frameworkCoreData kan de gegevens in verschillende formaten opslaanEen paar begripsdefinitiesGebruik NSPersistentContainerGebruik relatiesVermijd stringly code zo veel mogelijkHoud je NSPersitentContainer.viewContext read-onlyWatch those threadsGebruik het launch-argument om context en threadingGebruik aggregate queriesBewaar CoreData-gerelateerde code in categorieënOpteer een transactiemodel voor je wijzigingen aan je gegevensBewaar de context niet vanuit een modelcategorie methode of 10 slag geleerde CoreData lessen Olivier Destrebecq Volg 1 mei, 2020 – 7 min read Ik heb CoreData over meerdere projecten gebruikt tijdens mijn carrière als Apple-platformontwikkelaar (de eerste keer was in MacOS 10.5…), en in de loop der jaren heb ik dingen op de harde manier geleerd (ook wel: “veel te lang met mijn hoofd tegen de muur bonken”). Dus ik dacht, ik deel mijn kennis met jullie. Hopelijk bespaar ik je een paar blauwe plekken op je voorhoofd. Noteer dat dit geen how-to gids is voor het opzetten van CoreData. Het is bedoeld als een artikel met wat goede praktijkvoorbeelden die je kunt oppikken voor je projecten. Hier is het korte overzicht van Apple: “Gebruik Core Data om de permanente gegevens van uw applicatie op te slaan voor offline gebruik, om tijdelijke gegevens in de cache op te slaan en om ongedaanmakingsfunctionaliteit toe te voegen aan uw app op één apparaat.” Om wat meer in detail te treden: CoreData is de technologie van Apple om uw gestructureerde gegevens lokaal op te slaan. Je gebruikt de model-editor om je gegevensstructuur te definiëren, net als een databaseschema-editor, en vervolgens kun je die objecten in je code gebruiken om je gegevens op te slaan. CoreData is een object graph management framework Wat dit betekent, is dat wanneer je je model maakt, je je objecten en hun relaties definieert. Als je bijvoorbeeld een Bedrijf-entiteit en een Werknemer-entiteit in je datamodel hebt, zullen die twee entiteiten worden gekoppeld door een relatie die die twee met elkaar verbindt. Waarschijnlijk met de naam employees op de Company entiteit en employer op de Employee entiteit. U voegt eenvoudig de relatie eigenschap toe aan de entiteiten en dat is het. U hoeft geen join tabel te maken zoals in een database. Het mooie van CoreData vergeleken met een traditionele database is dat wanneer u een query doet om Bedrijf X te krijgen, u in staat zult zijn om de werknemers direct te krijgen door simpelweg company.employees te benaderen. U hoeft geen join query te maken of een nieuwe query uit te voeren. CoreData kan de gegevens in verschillende formaten opslaan Ja, u hoort het goed, CoreData kan uw gegevens in verschillende formaten/plaatsen opslaan. Elk heeft zijn voors en tegens. Binaire opslag SQLite opslag In geheugen opslag U kunt dit artikel lezen om de voors en tegens van elk te krijgen. Ik gebruik altijd SQLite, maar ik zou kunnen zien hoe een in-memory store supersnel zou zijn. Een paar begripsdefinities NSManagedObject: dit is de basisklasse die de objecten vertegenwoordigt die je in CoreData hebt opgeslagen. Fetch request: dit is het equivalent van een query in CoreData’s wereld. Het wordt vertegenwoordigd door de klasse NSFetchedObject. Gebruik NSPersistentContainer Tot iOS 10.0 kwam er bij het opzetten van de CoreData-stack nogal wat boilerplate-code kijken. Je moest zelf bepalen welke strategie je moest gebruiken om om te gaan met multi-threaded gebruik van CoreData. Met NSPersistentContainer kapselt Apple het grootste deel van de boilerplate code in. Dat bespaart u de discussie over hoe u uw stack het beste kunt inrichten voor optimale prestaties. Dit artikel bevat alle informatie die u nodig hebt om het correct te gebruiken. Gebruik relaties Ik noem dit altijd omdat bij twee gelegenheden, voor projecten die ik heb geërfd, het CoreData-model was opgezet als een database. Er werden geen relaties gedefinieerd. In plaats daarvan werd de ID van het gerelateerde object opgeslagen en dus was een extra fetch request nodig om het gerelateerde object op te halen. Ik denk dat veel ontwikkelaars CoreData benaderen als een database en deze fout begaan. Dit eindigt het hele project een stuk complexer en een stuk trager te maken. Volg dit artikel om je op weg te helpen. Vermijd stringly code zo veel mogelijk Stringly code verwijst naar code die strings gebruikt om toegang te krijgen tot gegevens. Op iOS, heb je een paar stringly APIs: UserDefaults, CoreData zijn een paar die bij me opkomen. Als we teruggaan naar het bedrijfsvoorbeeld, staan hieronder twee opties om een bedrijf te maken en de naam ervan in te stellen: //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" Op het eerste gezicht werken beide opties, ze compileren, er is niets mis met deze… Het probleem met stringly code is dat de compiler je niet kan helpen. Stel je voor dat je op meerdere plaatsen in de code toegang moet hebben tot de name van het bedrijf, en realistisch gezien heb je er minstens twee nodig: een om het in te stellen en een om het te lezen, nu moet je die name string op twee plaatsen intypen. Natuurlijk kun je een variabele maken om de string op te slaan en die variabele gebruiken, maar dat kost extra moeite, het wordt niet gecontroleerd getypt en we weten allemaal dat ontwikkelaars lui zijn… Dus dan blijft die string meestal op meerdere plaatsen staan. Dan wil je het hernoemen en moet je het opjagen met search and replace… Als je er een mist, crasht je app. Hier is de lijst van compiler functies waar je niet van profiteert met de stringly syntaxis: auto-complete refactor compiler warning in case of misspelling compiler error if you remove/renamed this entity or property in your model Om in staat te zijn om de nietstringly versie van de code te gebruiken moet u gewoon de compiler vragen om classes te genereren voor uw entiteiten (u kunt Apple’s instructies volgen onder “Select a Code Generation Option”). Dit is nu de standaard bij het maken van een nieuwe Entity. Houd je NSPersitentContainer.viewContext read-only Om prestatieredenen wil je geen lange bewerkingen op de viewContext uitvoeren, omdat deze dan op de main thread wordt uitgevoerd en mogelijk de UI blokkeert. De oplossing is om er een gewoonte van te maken alleen wijzigingen in je objecten aan te brengen binnen een blok dat je inplant met de methode NSPersistentContainer.performBackgroundTask(). Wanneer u uw wijzigingen in dat blok opslaat, worden deze weer samengevoegd in NSPersitentContainer.viewContext en kunt u uw UI bijwerken. Watch those threads Een van de grootste problemen met CoreData is het gebruik ervan op een thread-safe manier. Maar als je het concept eenmaal doorhebt, is het vrij eenvoudig. Waar het op neerkomt is dat elk data-object dat je van CoreData krijgt, alleen kan worden gebruikt op de thread waaraan de managedobjectContext van het object is gekoppeld. Eén manier om ervoor te zorgen dat u de juiste thread gebruikt om toegang te krijgen tot uw object, is het gebruik van NSManagedObjectContext.performBlockAndWait, dat ervoor zorgt dat uw code op de juiste thread wordt gebruikt. Eenmaal in dat blok, moet u NSManagedObject.objectID en NSManagedObjectContext.existingObject(withId:) gebruiken om het object over threads en context heen door te geven. Als u dit niet doet, zult u hoogstwaarschijnlijk eindigen met deadlocks. Stel je voor: U plant een taak vanuit de hoofddraad met In het geval dat iets gepland is vanuit de hoofddraad NSManagedObjectContext.performBlockAndWait(). Op dit punt is de hoofddraad geblokkeerd, wachtend tot dit blok is voltooid. Binnen dit blok geef je het object foo van de hoofdcontext door aan de methode bar() en dispatch je het naar een andere draad. Om een goede CoreData burger te zijn, zal bar() de context aan foo koppelen en zijn taak met foo.managedObjectContext.performBlockAndWait() verzenden Boom, je bent vastgelopen omdat deze context alleen op de hoofddraad wordt uitgevoerd en de hoofddraad wacht op deze voltooiing. Een andere conventie die helpt is het doorgeven van objecten tussen view controllers alleen als ze bestaan in de context van de hoofddraad. Gebruik het launch-argument om context en threading Als je de bovenstaande tips volgt, zou dit de kans moeten beperken dat een NSManagedObject instantie op de verkeerde thread wordt aangeroepen. Maar dit kan nog steeds gebeuren. Zorg er dus voor dat u bij ontwikkeling de thread debugging assertion -com.apple.CoreData.ConcurrencyDebug 1 Dit artikel vertelt u precies hoe u dit moet doen en vertelt u over andere vlaggen die u kunt instellen voor meer debugging. Gebruik aggregate queries CoreData biedt groupBy, sum, en count als aggregate functies die kunnen worden uitgevoerd tijdens een query. Het opzetten ervan kan een beetje ingewikkeld zijn, maar de moeite waard. Het alternatief is om een query te maken om alle objecten te krijgen die aan uw criteria voldoen en dan een for-lus door alle entiteiten te laten lopen en zelf de wiskunde te doen. Makkelijker te coderen, maar een stuk minder efficiënt. Wanneer je een aggregate fetch request uitvoert, vertaalt CoreData het in een database query en voert de wiskunde direct op databaseniveau uit. Het maakt het supersnel. Dit artikel is een goede intro over hoe je het moet doen. Bewaar CoreData-gerelateerde code in categorieën Als je eenmaal je NSManagedObject-subklasse hebt gemaakt door Xcode, zul je waarschijnlijk merken dat je code hebt die in de klasse zou moeten zitten die je object vertegenwoordigt (voor het berekenen van waarden, het importeren van gegevens van je server, enz.) Maar je hebt CoreData gevraagd om de klassen voor je te maken… Dus zul je een categorie moeten maken voor het object dat extra gedrag nodig heeft. Zet daar alleen code in die met CoreData te maken heeft, zodat je het schoon kunt houden. Opteer een transactiemodel voor je wijzigingen aan je gegevens Je kunt je objecten zoveel wijzigen als je wilt, maar om die wijzigingen te persisteren moet je NSManagedObjectContext.save() aanroepen. Je zult merken dat je snel vergeet save() aan te roepen en je zult een aantal bungelende wijzigingen in je context hebben. Op dit punt zijn alle weddenschappen uitgeschakeld, de volgende keer dat je save() aanroept, zul je niet zeker weten wat je vastlegt. Opteer een transactiemodel om te voorkomen dat je een vuile context achterlaat. Om dat te doen, maak je een categorie aan op NSPersistentContainer zoals dit: extension NSPersistentContainer {@objc func performBackgroundSaveTask(_ block: @escaping (NSManagedObjectContext) -> Void) {performBackgroundTask { (context: NSManagedObjectContext) inblock(context)context.save()}}} Zorg er vanaf nu voor dat alle wijzigingen die je maakt, worden uitgevoerd in een blok dat is gepland met performBackgroundSaveTask(). Op deze manier bent u er zeker van dat de wijzigingen worden opgeslagen wanneer u ze aanbrengt. Bewaar de context niet vanuit een modelcategorie methode Nooit save() aanroepen vanuit een entiteitcategorie. Er is niets erger dan een methode op je object aan te roepen zonder te beseffen dat het de hele context zal opslaan. Sommige veranderingen wil je misschien niet opslaan. Het aanroepen van save zou moeten gebeuren op het punt waar de wijzigingen aan het object worden gestart. Articles