skriva Core Data code med prestanda i åtanke hjälper till att förbereda din app för framtiden. Din databas kan vara liten i början men kan lätt växa, vilket resulterar i långsamma frågor och minskad upplevelse för användaren.
sedan jag började skriva Collect by WeTransfer-appen 2017 har jag skrivit en hel del kärndata relaterad kod och berört den nästan varje dag. Med miljontals användare lägga massor av innehåll, utför kärndata relaterad kod har blivit en viktig färdighet i vårt team.
under åren har vi utvecklat massor av insikter, som jag gärna delar med dig genom 5 tips du borde veta.
- 1: Använd ett bakgrundshanterat objektkontext
- viktigt: passera inte nsmanagedobject-instanser mellan köer
- 2: spara bara ett hanterat objektkontext om det behövs
- Tänk noga när du ska spara dina ändringar
- 3: Bara hämta vad du behöver
- 4: Använd hämtningsgränser
- 5: Ta bort många objekt samtidigt med en NSBatchDeleteRequest
- 6: vet hur man felsöker Kärndatakod
- slutsats
1: Använd ett bakgrundshanterat objektkontext
en sak som vi inte gjorde från början är att använda ett bakgrundshanterat objektkontext. Vi använde bara vykontexten för att utföra alla Kärndatarelaterade uppgifter: infoga nytt innehåll, Ta bort innehåll, hämta innehåll etc.
i början var vår app relativt liten. Att bara använda vykontexten var inte riktigt ett problem och resulterade inte i några synliga prestandastraff relaterade till kärndata. Självklart, när vår app började växa, insåg vi att vykontexten var associerad med huvudkön. Långsamma frågor blockerade vårt användargränssnitt och vår app blev mindre svarande.
i allmänhet är bästa praxis att utföra databehandling på en bakgrundskö eftersom det kan vara CPU-intensivt. Exempel som att importera JSON till kärndata kan annars blockera vykontexten och resultera i att användargränssnittet inte svarar.
lösningen är att använda ett bakgrundshanterat objektkontext. De senaste API: erna gör det enkelt att skapa ett nytt sammanhang från din ihållande Behållare:
let backgroundContext = persistentContainer.newBackgroundContext()
jag rekommenderar den här metoden över NSManagedObjectContext(concurrenyType:)
initializer eftersom den automatiskt kommer att associeras med NSPersistentStoreCoordinator
och den kommer att ställas in för att konsumera NSManagedObjectContextDidSave
sändningar också. Detta håller ditt bakgrundskontext synkroniserat med vykontexten.
du kan spara detta bakgrundskontext på en anpassad persistent behållarunderklass. På så sätt kan du återanvända ditt bakgrundskontext och du behöver bara hantera två sammanhang. Detta håller din Kärndatastruktur enkel att förstå och det förhindrar att du har flera synkroniserade sammanhang.
om du bara behöver använda bakgrundskontexten på några få ställen kan du också välja att använda metoden performBackgroundTask(_:)
som skapar ett bakgrundskontext på plats:
persistentContainer.performBackgroundTask { (backgroundContext) in // .. Core Data Code}
denna metod skapar dock en ny NSManagedObjectContext
varje gång den anropas. Du kanske vill överväga att använda det delade bakgrundskontexten om du skickar oftare till ett bakgrundskontext.
viktigt: passera inte nsmanagedobject-instanser mellan köer
att skriva flertrådad Kärndatakod är mycket mer komplex än att använda en enda vykontext. Anledningen till detta är att du inte bara kan skicka en NSManagedObject
instansieras från en vy sammanhang till en bakgrund sammanhang. Om du gör det skulle det leda till en krasch och potentiell datakorruption.
när det är nödvändigt att flytta ett hanterat objekt från en kö till en annan kan du använda NSManagedObjectID
som är trådsäker:
let managedObject = NSManagedObject(context: persistentContainer.viewContext)backgroundContext.perform { let object = try? backgroundContext.existingObject(with: managedObject.objectID)}
2: spara bara ett hanterat objektkontext om det behövs
Spara ett hanterat objektkontext förbinder alla aktuella ändringar i kontextens överordnade butik. Som du kan föreställa dig är detta inte en billig operation och den bör endast användas om det behövs för att säkerställa prestanda i kärndata.
först och främst är det viktigt att kontrollera om det finns något att spara. Om det inte finns några ändringar att begå, finns det ingen anledning att utföra en spara. Genom att skapa en saveIfNeeded
– metod tillåter du dig själv att enkelt bygga in en kontroll för detta:
Tänk noga när du ska spara dina ändringar
förutom att använda saveIfNeeded
istället för save()
måste du också överväga om en spara är meningsfull. Även om ett sammanhang kan ha förändringar är det inte alltid nödvändigt att direkt begå dessa förändringar.
om du till exempel är viktiga flera objekt i databasen kanske du bara vill spara efter att du har importerat alla objekt i ditt bakgrundskontext. En spara följs ofta av UI-uppdateringar och flera sparar efter varandra kan enkelt resultera i onödiga omladdningar. Förutom det, ta hänsyn till att sparade ändringar i ett bakgrundskontext slås samman i vykontexten och blockerar också huvudkön inom kort. Var därför medveten!
3: Bara hämta vad du behöver
hämta data är en dyr uppgift och måste vara så performant som möjligt för att göra din app förberedd för stora datamängder. Följande kod är ett ofta gjort misstag:
den här koden laddar alla infogade objekt i minnet medan den filtreras direkt efter för att bara förbli med innehåll som har ett namn.
det är mycket mer performant att använda predikat för att bara hämta de objekt som behövs. Ovanstående filter kan skrivas som följt med en NSPredicate
:
detta har två fördelar:
- endast de nödvändiga objekten laddas i minnet
- du behöver inte iterera över alla objekt
predikat är mycket flexibla och bör låta dig hämta önskad dataset i de flesta fall samtidigt som du behåller prestanda i kärndata.
4: Använd hämtningsgränser
uppföljning av föregående exempel är det viktigt att ställa in hämtningsgränser när du bara ska visa en del av datasetet.
säg till exempel att du bara behöver de första 3 namnen på alla innehållsobjekt. I det här fallet skulle det vara onödigt att ladda alla innehållsobjekt som har ett namn i minnet. Vi kan förhindra detta genom att ställa in en hämtningsgräns:
den här koden returnerar bara de första 3 innehållsobjekten med ett namn.
5: Ta bort många objekt samtidigt med en NSBatchDeleteRequest
istället för att iterera över en dataset som raderar varje objekt en efter en är det ofta mer performant att använda en NSBatchDeleteRequest
som går snabbare eftersom den fungerar på SQL-nivå i den ihållande butiken själv.
du kan lära dig mer om batch-raderingsförfrågningar i mitt blogginlägg med NSBatchDeleteRequest för att radera partier i kärndata.
6: vet hur man felsöker Kärndatakod
som med all kod du skriver är det viktigt att veta hur man optimerar och felsöker den när den inte fungerar som förväntat. Det finns många sätt att felsöka som bäst förklaras i mitt dedikerade blogginlägg: Core Data Debugging i Xcode med lanseringsargument.
slutsats
skriva performant Core Data code från början hjälper dig att förbereda din app för framtida stora datamängder. Även om din app kan fungera i början kan den lätt sakta ner när din databas och modell växer. Genom att använda ett bakgrundskontext, smarta hämtningsförfrågningar och batch-raderingsförfrågningar gör du din Kärndatakod redan mer prestanda.
om du vill förbättra din Swift-kunskap, ännu mer, kolla in Swift-kategorisidan. Tveka inte att kontakta mig eller tweet till mig på Twitter om du har några ytterligare tips eller feedback.
tack!